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