suspensionmanager.cpp 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481
  1. //*********************************************************
  2. //
  3. // Copyright (c) Microsoft. All rights reserved.
  4. // THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF
  5. // ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY
  6. // IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR
  7. // PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT.
  8. //
  9. //*********************************************************
  10. //
  11. // SuspensionManager.cpp
  12. // Implementation of the SuspensionManager class
  13. //
  14. #include "pch.h"
  15. #include "SuspensionManager.h"
  16. #include <collection.h>
  17. #include <algorithm>
  18. using namespace SDKSample::Common;
  19. using namespace Concurrency;
  20. using namespace Platform;
  21. using namespace Platform::Collections;
  22. using namespace Windows::Foundation;
  23. using namespace Windows::Foundation::Collections;
  24. using namespace Windows::Storage;
  25. using namespace Windows::Storage::FileProperties;
  26. using namespace Windows::Storage::Streams;
  27. using namespace Windows::UI::Xaml;
  28. using namespace Windows::UI::Xaml::Controls;
  29. using namespace Windows::UI::Xaml::Interop;
  30. namespace
  31. {
  32. Map<String^, Object^>^ _sessionState = ref new Map<String^, Object^>();
  33. String^ sessionStateFilename = "_sessionState.dat";
  34. // Forward declarations for object object read / write support
  35. void WriteObject(Windows::Storage::Streams::DataWriter^ writer, Platform::Object^ object);
  36. Platform::Object^ ReadObject(Windows::Storage::Streams::DataReader^ reader);
  37. }
  38. /// <summary>
  39. /// Provides access to global session state for the current session. This state is serialized by
  40. /// <see cref="SaveAsync"/> and restored by <see cref="RestoreAsync"/> which require values to be
  41. /// one of the following: boxed values including integers, floating-point singles and doubles,
  42. /// wide characters, boolean, Strings and Guids, or Map<String^, Object^> where map values are
  43. /// subject to the same constraints. Session state should be as compact as possible.
  44. /// </summary>
  45. IMap<String^, Object^>^ SuspensionManager::SessionState::get(void)
  46. {
  47. return _sessionState;
  48. }
  49. /// <summary>
  50. /// Wrap a WeakReference as a reference object for use in a collection.
  51. /// </summary>
  52. private ref class WeakFrame sealed
  53. {
  54. private:
  55. WeakReference _frameReference;
  56. internal:
  57. WeakFrame(Frame^ frame) { _frameReference = frame; }
  58. property Frame^ ResolvedFrame
  59. {
  60. Frame^ get(void) { return _frameReference.Resolve<Frame>(); }
  61. };
  62. };
  63. namespace
  64. {
  65. std::vector<WeakFrame^> _registeredFrames;
  66. DependencyProperty^ FrameSessionStateKeyProperty =
  67. DependencyProperty::RegisterAttached("_FrameSessionStateKeyProperty",
  68. TypeName(String::typeid), TypeName(SuspensionManager::typeid), nullptr);
  69. DependencyProperty^ FrameSessionStateProperty =
  70. DependencyProperty::RegisterAttached("_FrameSessionStateProperty",
  71. TypeName(IMap<String^, Object^>::typeid), TypeName(SuspensionManager::typeid), nullptr);
  72. }
  73. /// <summary>
  74. /// Registers a <see cref="Frame"/> instance to allow its navigation history to be saved to
  75. /// and restored from <see cref="SessionState"/>. Frames should be registered once
  76. /// immediately after creation if they will participate in session state management. Upon
  77. /// registration if state has already been restored for the specified key
  78. /// the navigation history will immediately be restored. Subsequent invocations of
  79. /// <see cref="RestoreAsync(String)"/> will also restore navigation history.
  80. /// </summary>
  81. /// <param name="frame">An instance whose navigation history should be managed by
  82. /// <see cref="SuspensionManager"/></param>
  83. /// <param name="sessionStateKey">A unique key into <see cref="SessionState"/> used to
  84. /// store navigation-related information.</param>
  85. void SuspensionManager::RegisterFrame(Frame^ frame, String^ sessionStateKey)
  86. {
  87. if (frame->GetValue(FrameSessionStateKeyProperty) != nullptr)
  88. {
  89. throw ref new FailureException("Frames can only be registered to one session state key");
  90. }
  91. if (frame->GetValue(FrameSessionStateProperty) != nullptr)
  92. {
  93. throw ref new FailureException("Frames must be either be registered before accessing frame session state, or not registered at all");
  94. }
  95. // Use a dependency property to associate the session key with a frame, and keep a list of frames whose
  96. // navigation state should be managed
  97. frame->SetValue(FrameSessionStateKeyProperty, sessionStateKey);
  98. _registeredFrames.insert(_registeredFrames.begin(), ref new WeakFrame(frame));
  99. // Check to see if navigation state can be restored
  100. RestoreFrameNavigationState(frame);
  101. }
  102. /// <summary>
  103. /// Disassociates a <see cref="Frame"/> previously registered by <see cref="RegisterFrame"/>
  104. /// from <see cref="SessionState"/>. Any navigation state previously captured will be
  105. /// removed.
  106. /// </summary>
  107. /// <param name="frame">An instance whose navigation history should no longer be
  108. /// managed.</param>
  109. void SuspensionManager::UnregisterFrame(Frame^ frame)
  110. {
  111. // Remove session state and remove the frame from the list of frames whose navigation
  112. // state will be saved (along with any weak references that are no longer reachable)
  113. auto key = safe_cast<String^>(frame->GetValue(FrameSessionStateKeyProperty));
  114. if (SessionState->HasKey(key)) SessionState->Remove(key);
  115. _registeredFrames.erase(
  116. std::remove_if(_registeredFrames.begin(), _registeredFrames.end(), [=](WeakFrame^& e)
  117. {
  118. auto testFrame = e->ResolvedFrame;
  119. return testFrame == nullptr || testFrame == frame;
  120. }),
  121. _registeredFrames.end()
  122. );
  123. }
  124. /// <summary>
  125. /// Provides storage for session state associated with the specified <see cref="Frame"/>.
  126. /// Frames that have been previously registered with <see cref="RegisterFrame"/> have
  127. /// their session state saved and restored automatically as a part of the global
  128. /// <see cref="SessionState"/>. Frames that are not registered have transient state
  129. /// that can still be useful when restoring pages that have been discarded from the
  130. /// navigation cache.
  131. /// </summary>
  132. /// <remarks>Apps may choose to rely on <see cref="LayoutAwarePage"/> to manage
  133. /// page-specific state instead of working with frame session state directly.</remarks>
  134. /// <param name="frame">The instance for which session state is desired.</param>
  135. /// <returns>A collection of state subject to the same serialization mechanism as
  136. /// <see cref="SessionState"/>.</returns>
  137. IMap<String^, Object^>^ SuspensionManager::SessionStateForFrame(Frame^ frame)
  138. {
  139. auto frameState = safe_cast<IMap<String^, Object^>^>(frame->GetValue(FrameSessionStateProperty));
  140. if (frameState == nullptr)
  141. {
  142. auto frameSessionKey = safe_cast<String^>(frame->GetValue(FrameSessionStateKeyProperty));
  143. if (frameSessionKey != nullptr)
  144. {
  145. // Registered frames reflect the corresponding session state
  146. if (!_sessionState->HasKey(frameSessionKey))
  147. {
  148. _sessionState->Insert(frameSessionKey, ref new Map<String^, Object^>());
  149. }
  150. frameState = safe_cast<IMap<String^, Object^>^>(_sessionState->Lookup(frameSessionKey));
  151. }
  152. else
  153. {
  154. // Frames that aren't registered have transient state
  155. frameState = ref new Map<String^, Object^>();
  156. }
  157. frame->SetValue(FrameSessionStateProperty, frameState);
  158. }
  159. return frameState;
  160. }
  161. void SuspensionManager::RestoreFrameNavigationState(Frame^ frame)
  162. {
  163. auto frameState = SessionStateForFrame(frame);
  164. if (frameState->HasKey("Navigation"))
  165. {
  166. frame->SetNavigationState(safe_cast<String^>(frameState->Lookup("Navigation")));
  167. }
  168. }
  169. void SuspensionManager::SaveFrameNavigationState(Frame^ frame)
  170. {
  171. auto frameState = SessionStateForFrame(frame);
  172. frameState->Insert("Navigation", frame->GetNavigationState());
  173. }
  174. /// <summary>
  175. /// Save the current <see cref="SessionState"/>. Any <see cref="Frame"/> instances
  176. /// registered with <see cref="RegisterFrame"/> will also preserve their current
  177. /// navigation stack, which in turn gives their active <see cref="Page"/> an opportunity
  178. /// to save its state.
  179. /// </summary>
  180. /// <returns>An asynchronous task that reflects when session state has been saved.</returns>
  181. task<void> SuspensionManager::SaveAsync(void)
  182. {
  183. // Save the navigation state for all registered frames
  184. for (auto&& weakFrame : _registeredFrames)
  185. {
  186. auto frame = weakFrame->ResolvedFrame;
  187. if (frame != nullptr) SaveFrameNavigationState(frame);
  188. }
  189. // Serialize the session state synchronously to avoid asynchronous access to shared
  190. // state
  191. auto sessionData = ref new InMemoryRandomAccessStream();
  192. auto sessionDataWriter = ref new DataWriter(sessionData->GetOutputStreamAt(0));
  193. WriteObject(sessionDataWriter, _sessionState);
  194. // Once session state has been captured synchronously, begin the asynchronous process
  195. // of writing the result to disk
  196. return task<unsigned int>(sessionDataWriter->StoreAsync()).then([=](unsigned int)
  197. {
  198. return sessionDataWriter->FlushAsync();
  199. }).then([=](bool flushSucceeded)
  200. {
  201. (void)flushSucceeded; // Unused parameter
  202. return ApplicationData::Current->LocalFolder->CreateFileAsync(sessionStateFilename,
  203. CreationCollisionOption::ReplaceExisting);
  204. }).then([=](StorageFile^ createdFile)
  205. {
  206. return createdFile->OpenAsync(FileAccessMode::ReadWrite);
  207. }).then([=](IRandomAccessStream^ newStream)
  208. {
  209. return RandomAccessStream::CopyAndCloseAsync(
  210. sessionData->GetInputStreamAt(0), newStream->GetOutputStreamAt(0));
  211. }).then([=](UINT64 copiedBytes)
  212. {
  213. (void)copiedBytes; // Unused parameter
  214. return;
  215. });
  216. }
  217. /// <summary>
  218. /// Restores previously saved <see cref="SessionState"/>. Any <see cref="Frame"/> instances
  219. /// registered with <see cref="RegisterFrame"/> will also restore their prior navigation
  220. /// state, which in turn gives their active <see cref="Page"/> an opportunity restore its
  221. /// state.
  222. /// </summary>
  223. /// <param name="version">A version identifier compared to the session state to prevent
  224. /// incompatible versions of session state from reaching app code. Saved state with a
  225. /// different version will be ignored, resulting in an empty <see cref="SessionState"/>
  226. /// dictionary.</param>
  227. /// <returns>An asynchronous task that reflects when session state has been read. The
  228. /// content of <see cref="SessionState"/> should not be relied upon until this task
  229. /// completes.</returns>
  230. task<void> SuspensionManager::RestoreAsync(void)
  231. {
  232. _sessionState->Clear();
  233. task<StorageFile^> getFileTask(ApplicationData::Current->LocalFolder->GetFileAsync(sessionStateFilename));
  234. return getFileTask.then([=](StorageFile^ stateFile)
  235. {
  236. task<BasicProperties^> getBasicPropertiesTask(stateFile->GetBasicPropertiesAsync());
  237. return getBasicPropertiesTask.then([=](BasicProperties^ stateFileProperties)
  238. {
  239. auto size = unsigned int(stateFileProperties->Size);
  240. if (size != stateFileProperties->Size) throw ref new FailureException("Session state larger than 4GB");
  241. task<IRandomAccessStreamWithContentType^> openReadTask(stateFile->OpenReadAsync());
  242. return openReadTask.then([=](IRandomAccessStreamWithContentType^ stateFileStream)
  243. {
  244. auto stateReader = ref new DataReader(stateFileStream);
  245. return task<unsigned int>(stateReader->LoadAsync(size)).then([=](unsigned int bytesRead)
  246. {
  247. (void)bytesRead; // Unused parameter
  248. // Deserialize the Session State
  249. Object^ content = ReadObject(stateReader);
  250. _sessionState = (Map<String^, Object^>^)content;
  251. // Restore any registered frames to their saved state
  252. for (auto&& weakFrame : _registeredFrames)
  253. {
  254. auto frame = weakFrame->ResolvedFrame;
  255. if (frame != nullptr)
  256. {
  257. frame->ClearValue(FrameSessionStateProperty);
  258. RestoreFrameNavigationState(frame);
  259. }
  260. }
  261. }, task_continuation_context::use_current());
  262. });
  263. });
  264. });
  265. }
  266. #pragma region Object serialization for a known set of types
  267. namespace
  268. {
  269. // Codes used for identifying serialized types
  270. enum StreamTypes {
  271. NullPtrType = 0,
  272. // Supported IPropertyValue types
  273. UInt8Type, UInt16Type, UInt32Type, UInt64Type, Int16Type, Int32Type, Int64Type,
  274. SingleType, DoubleType, BooleanType, Char16Type, GuidType, StringType,
  275. // Additional supported types
  276. StringToObjectMapType,
  277. // Marker values used to ensure stream integrity
  278. MapEndMarker
  279. };
  280. void WriteString(DataWriter^ writer, String^ string)
  281. {
  282. writer->WriteByte(StringType);
  283. writer->WriteUInt32(writer->MeasureString(string));
  284. writer->WriteString(string);
  285. }
  286. void WriteProperty(DataWriter^ writer, IPropertyValue^ propertyValue)
  287. {
  288. switch (propertyValue->Type)
  289. {
  290. case PropertyType::UInt8:
  291. writer->WriteByte(UInt8Type);
  292. writer->WriteByte(propertyValue->GetUInt8());
  293. return;
  294. case PropertyType::UInt16:
  295. writer->WriteByte(UInt16Type);
  296. writer->WriteUInt16(propertyValue->GetUInt16());
  297. return;
  298. case PropertyType::UInt32:
  299. writer->WriteByte(UInt32Type);
  300. writer->WriteUInt32(propertyValue->GetUInt32());
  301. return;
  302. case PropertyType::UInt64:
  303. writer->WriteByte(UInt64Type);
  304. writer->WriteUInt64(propertyValue->GetUInt64());
  305. return;
  306. case PropertyType::Int16:
  307. writer->WriteByte(Int16Type);
  308. writer->WriteUInt16(propertyValue->GetInt16());
  309. return;
  310. case PropertyType::Int32:
  311. writer->WriteByte(Int32Type);
  312. writer->WriteUInt32(propertyValue->GetInt32());
  313. return;
  314. case PropertyType::Int64:
  315. writer->WriteByte(Int64Type);
  316. writer->WriteUInt64(propertyValue->GetInt64());
  317. return;
  318. case PropertyType::Single:
  319. writer->WriteByte(SingleType);
  320. writer->WriteSingle(propertyValue->GetSingle());
  321. return;
  322. case PropertyType::Double:
  323. writer->WriteByte(DoubleType);
  324. writer->WriteDouble(propertyValue->GetDouble());
  325. return;
  326. case PropertyType::Boolean:
  327. writer->WriteByte(BooleanType);
  328. writer->WriteBoolean(propertyValue->GetBoolean());
  329. return;
  330. case PropertyType::Char16:
  331. writer->WriteByte(Char16Type);
  332. writer->WriteUInt16(propertyValue->GetChar16());
  333. return;
  334. case PropertyType::Guid:
  335. writer->WriteByte(GuidType);
  336. writer->WriteGuid(propertyValue->GetGuid());
  337. return;
  338. case PropertyType::String:
  339. WriteString(writer, propertyValue->GetString());
  340. return;
  341. default:
  342. throw ref new InvalidArgumentException("Unsupported property type");
  343. }
  344. }
  345. void WriteStringToObjectMap(DataWriter^ writer, IMap<String^, Object^>^ map)
  346. {
  347. writer->WriteByte(StringToObjectMapType);
  348. writer->WriteUInt32(map->Size);
  349. for (auto&& pair : map)
  350. {
  351. WriteObject(writer, pair->Key);
  352. WriteObject(writer, pair->Value);
  353. }
  354. writer->WriteByte(MapEndMarker);
  355. }
  356. void WriteObject(DataWriter^ writer, Object^ object)
  357. {
  358. if (object == nullptr)
  359. {
  360. writer->WriteByte(NullPtrType);
  361. return;
  362. }
  363. auto propertyObject = dynamic_cast<IPropertyValue^>(object);
  364. if (propertyObject != nullptr)
  365. {
  366. WriteProperty(writer, propertyObject);
  367. return;
  368. }
  369. auto mapObject = dynamic_cast<IMap<String^, Object^>^>(object);
  370. if (mapObject != nullptr)
  371. {
  372. WriteStringToObjectMap(writer, mapObject);
  373. return;
  374. }
  375. throw ref new InvalidArgumentException("Unsupported data type");
  376. }
  377. String^ ReadString(DataReader^ reader)
  378. {
  379. int length = reader->ReadUInt32();
  380. String^ string = reader->ReadString(length);
  381. return string;
  382. }
  383. IMap<String^, Object^>^ ReadStringToObjectMap(DataReader^ reader)
  384. {
  385. auto map = ref new Map<String^, Object^>();
  386. auto size = reader->ReadUInt32();
  387. for (unsigned int index = 0; index < size; index++)
  388. {
  389. auto key = safe_cast<String^>(ReadObject(reader));
  390. auto value = ReadObject(reader);
  391. map->Insert(key, value);
  392. }
  393. if (reader->ReadByte() != MapEndMarker)
  394. {
  395. throw ref new InvalidArgumentException("Invalid stream");
  396. }
  397. return map;
  398. }
  399. Object^ ReadObject(DataReader^ reader)
  400. {
  401. auto type = reader->ReadByte();
  402. switch (type)
  403. {
  404. case NullPtrType:
  405. return nullptr;
  406. case UInt8Type:
  407. return reader->ReadByte();
  408. case UInt16Type:
  409. return reader->ReadUInt16();
  410. case UInt32Type:
  411. return reader->ReadUInt32();
  412. case UInt64Type:
  413. return reader->ReadUInt64();
  414. case Int16Type:
  415. return reader->ReadInt16();
  416. case Int32Type:
  417. return reader->ReadInt32();
  418. case Int64Type:
  419. return reader->ReadInt64();
  420. case SingleType:
  421. return reader->ReadSingle();
  422. case DoubleType:
  423. return reader->ReadDouble();
  424. case BooleanType:
  425. return reader->ReadBoolean();
  426. case Char16Type:
  427. return (char16_t)reader->ReadUInt16();
  428. case GuidType:
  429. return reader->ReadGuid();
  430. case StringType:
  431. return ReadString(reader);
  432. case StringToObjectMapType:
  433. return ReadStringToObjectMap(reader);
  434. default:
  435. throw ref new InvalidArgumentException("Unsupported property type");
  436. }
  437. }
  438. }
  439. #pragma endregion