//********************************************************* // // Copyright (c) Microsoft. All rights reserved. // THIS CODE IS PROVIDED *AS IS* WITHOUT WARRANTY OF // ANY KIND, EITHER EXPRESS OR IMPLIED, INCLUDING ANY // IMPLIED WARRANTIES OF FITNESS FOR A PARTICULAR // PURPOSE, MERCHANTABILITY, OR NON-INFRINGEMENT. // //********************************************************* // // SuspensionManager.cpp // Implementation of the SuspensionManager class // #include "pch.h" #include "SuspensionManager.h" #include #include using namespace SDKSample::Common; using namespace Concurrency; using namespace Platform; using namespace Platform::Collections; using namespace Windows::Foundation; using namespace Windows::Foundation::Collections; using namespace Windows::Storage; using namespace Windows::Storage::FileProperties; using namespace Windows::Storage::Streams; using namespace Windows::UI::Xaml; using namespace Windows::UI::Xaml::Controls; using namespace Windows::UI::Xaml::Interop; namespace { Map^ _sessionState = ref new Map(); String^ sessionStateFilename = "_sessionState.dat"; // Forward declarations for object object read / write support void WriteObject(Windows::Storage::Streams::DataWriter^ writer, Platform::Object^ object); Platform::Object^ ReadObject(Windows::Storage::Streams::DataReader^ reader); } /// /// Provides access to global session state for the current session. This state is serialized by /// and restored by which require values to be /// one of the following: boxed values including integers, floating-point singles and doubles, /// wide characters, boolean, Strings and Guids, or Map where map values are /// subject to the same constraints. Session state should be as compact as possible. /// IMap^ SuspensionManager::SessionState::get(void) { return _sessionState; } /// /// Wrap a WeakReference as a reference object for use in a collection. /// private ref class WeakFrame sealed { private: WeakReference _frameReference; internal: WeakFrame(Frame^ frame) { _frameReference = frame; } property Frame^ ResolvedFrame { Frame^ get(void) { return _frameReference.Resolve(); } }; }; namespace { std::vector _registeredFrames; DependencyProperty^ FrameSessionStateKeyProperty = DependencyProperty::RegisterAttached("_FrameSessionStateKeyProperty", TypeName(String::typeid), TypeName(SuspensionManager::typeid), nullptr); DependencyProperty^ FrameSessionStateProperty = DependencyProperty::RegisterAttached("_FrameSessionStateProperty", TypeName(IMap::typeid), TypeName(SuspensionManager::typeid), nullptr); } /// /// Registers a instance to allow its navigation history to be saved to /// and restored from . Frames should be registered once /// immediately after creation if they will participate in session state management. Upon /// registration if state has already been restored for the specified key /// the navigation history will immediately be restored. Subsequent invocations of /// will also restore navigation history. /// /// An instance whose navigation history should be managed by /// /// A unique key into used to /// store navigation-related information. void SuspensionManager::RegisterFrame(Frame^ frame, String^ sessionStateKey) { if (frame->GetValue(FrameSessionStateKeyProperty) != nullptr) { throw ref new FailureException("Frames can only be registered to one session state key"); } if (frame->GetValue(FrameSessionStateProperty) != nullptr) { throw ref new FailureException("Frames must be either be registered before accessing frame session state, or not registered at all"); } // Use a dependency property to associate the session key with a frame, and keep a list of frames whose // navigation state should be managed frame->SetValue(FrameSessionStateKeyProperty, sessionStateKey); _registeredFrames.insert(_registeredFrames.begin(), ref new WeakFrame(frame)); // Check to see if navigation state can be restored RestoreFrameNavigationState(frame); } /// /// Disassociates a previously registered by /// from . Any navigation state previously captured will be /// removed. /// /// An instance whose navigation history should no longer be /// managed. void SuspensionManager::UnregisterFrame(Frame^ frame) { // Remove session state and remove the frame from the list of frames whose navigation // state will be saved (along with any weak references that are no longer reachable) auto key = safe_cast(frame->GetValue(FrameSessionStateKeyProperty)); if (SessionState->HasKey(key)) SessionState->Remove(key); _registeredFrames.erase( std::remove_if(_registeredFrames.begin(), _registeredFrames.end(), [=](WeakFrame^& e) { auto testFrame = e->ResolvedFrame; return testFrame == nullptr || testFrame == frame; }), _registeredFrames.end() ); } /// /// Provides storage for session state associated with the specified . /// Frames that have been previously registered with have /// their session state saved and restored automatically as a part of the global /// . Frames that are not registered have transient state /// that can still be useful when restoring pages that have been discarded from the /// navigation cache. /// /// Apps may choose to rely on to manage /// page-specific state instead of working with frame session state directly. /// The instance for which session state is desired. /// A collection of state subject to the same serialization mechanism as /// . IMap^ SuspensionManager::SessionStateForFrame(Frame^ frame) { auto frameState = safe_cast^>(frame->GetValue(FrameSessionStateProperty)); if (frameState == nullptr) { auto frameSessionKey = safe_cast(frame->GetValue(FrameSessionStateKeyProperty)); if (frameSessionKey != nullptr) { // Registered frames reflect the corresponding session state if (!_sessionState->HasKey(frameSessionKey)) { _sessionState->Insert(frameSessionKey, ref new Map()); } frameState = safe_cast^>(_sessionState->Lookup(frameSessionKey)); } else { // Frames that aren't registered have transient state frameState = ref new Map(); } frame->SetValue(FrameSessionStateProperty, frameState); } return frameState; } void SuspensionManager::RestoreFrameNavigationState(Frame^ frame) { auto frameState = SessionStateForFrame(frame); if (frameState->HasKey("Navigation")) { frame->SetNavigationState(safe_cast(frameState->Lookup("Navigation"))); } } void SuspensionManager::SaveFrameNavigationState(Frame^ frame) { auto frameState = SessionStateForFrame(frame); frameState->Insert("Navigation", frame->GetNavigationState()); } /// /// Save the current . Any instances /// registered with will also preserve their current /// navigation stack, which in turn gives their active an opportunity /// to save its state. /// /// An asynchronous task that reflects when session state has been saved. task SuspensionManager::SaveAsync(void) { // Save the navigation state for all registered frames for (auto&& weakFrame : _registeredFrames) { auto frame = weakFrame->ResolvedFrame; if (frame != nullptr) SaveFrameNavigationState(frame); } // Serialize the session state synchronously to avoid asynchronous access to shared // state auto sessionData = ref new InMemoryRandomAccessStream(); auto sessionDataWriter = ref new DataWriter(sessionData->GetOutputStreamAt(0)); WriteObject(sessionDataWriter, _sessionState); // Once session state has been captured synchronously, begin the asynchronous process // of writing the result to disk return task(sessionDataWriter->StoreAsync()).then([=](unsigned int) { return sessionDataWriter->FlushAsync(); }).then([=](bool flushSucceeded) { (void)flushSucceeded; // Unused parameter return ApplicationData::Current->LocalFolder->CreateFileAsync(sessionStateFilename, CreationCollisionOption::ReplaceExisting); }).then([=](StorageFile^ createdFile) { return createdFile->OpenAsync(FileAccessMode::ReadWrite); }).then([=](IRandomAccessStream^ newStream) { return RandomAccessStream::CopyAndCloseAsync( sessionData->GetInputStreamAt(0), newStream->GetOutputStreamAt(0)); }).then([=](UINT64 copiedBytes) { (void)copiedBytes; // Unused parameter return; }); } /// /// Restores previously saved . Any instances /// registered with will also restore their prior navigation /// state, which in turn gives their active an opportunity restore its /// state. /// /// A version identifier compared to the session state to prevent /// incompatible versions of session state from reaching app code. Saved state with a /// different version will be ignored, resulting in an empty /// dictionary. /// An asynchronous task that reflects when session state has been read. The /// content of should not be relied upon until this task /// completes. task SuspensionManager::RestoreAsync(void) { _sessionState->Clear(); task getFileTask(ApplicationData::Current->LocalFolder->GetFileAsync(sessionStateFilename)); return getFileTask.then([=](StorageFile^ stateFile) { task getBasicPropertiesTask(stateFile->GetBasicPropertiesAsync()); return getBasicPropertiesTask.then([=](BasicProperties^ stateFileProperties) { auto size = unsigned int(stateFileProperties->Size); if (size != stateFileProperties->Size) throw ref new FailureException("Session state larger than 4GB"); task openReadTask(stateFile->OpenReadAsync()); return openReadTask.then([=](IRandomAccessStreamWithContentType^ stateFileStream) { auto stateReader = ref new DataReader(stateFileStream); return task(stateReader->LoadAsync(size)).then([=](unsigned int bytesRead) { (void)bytesRead; // Unused parameter // Deserialize the Session State Object^ content = ReadObject(stateReader); _sessionState = (Map^)content; // Restore any registered frames to their saved state for (auto&& weakFrame : _registeredFrames) { auto frame = weakFrame->ResolvedFrame; if (frame != nullptr) { frame->ClearValue(FrameSessionStateProperty); RestoreFrameNavigationState(frame); } } }, task_continuation_context::use_current()); }); }); }); } #pragma region Object serialization for a known set of types namespace { // Codes used for identifying serialized types enum StreamTypes { NullPtrType = 0, // Supported IPropertyValue types UInt8Type, UInt16Type, UInt32Type, UInt64Type, Int16Type, Int32Type, Int64Type, SingleType, DoubleType, BooleanType, Char16Type, GuidType, StringType, // Additional supported types StringToObjectMapType, // Marker values used to ensure stream integrity MapEndMarker }; void WriteString(DataWriter^ writer, String^ string) { writer->WriteByte(StringType); writer->WriteUInt32(writer->MeasureString(string)); writer->WriteString(string); } void WriteProperty(DataWriter^ writer, IPropertyValue^ propertyValue) { switch (propertyValue->Type) { case PropertyType::UInt8: writer->WriteByte(UInt8Type); writer->WriteByte(propertyValue->GetUInt8()); return; case PropertyType::UInt16: writer->WriteByte(UInt16Type); writer->WriteUInt16(propertyValue->GetUInt16()); return; case PropertyType::UInt32: writer->WriteByte(UInt32Type); writer->WriteUInt32(propertyValue->GetUInt32()); return; case PropertyType::UInt64: writer->WriteByte(UInt64Type); writer->WriteUInt64(propertyValue->GetUInt64()); return; case PropertyType::Int16: writer->WriteByte(Int16Type); writer->WriteUInt16(propertyValue->GetInt16()); return; case PropertyType::Int32: writer->WriteByte(Int32Type); writer->WriteUInt32(propertyValue->GetInt32()); return; case PropertyType::Int64: writer->WriteByte(Int64Type); writer->WriteUInt64(propertyValue->GetInt64()); return; case PropertyType::Single: writer->WriteByte(SingleType); writer->WriteSingle(propertyValue->GetSingle()); return; case PropertyType::Double: writer->WriteByte(DoubleType); writer->WriteDouble(propertyValue->GetDouble()); return; case PropertyType::Boolean: writer->WriteByte(BooleanType); writer->WriteBoolean(propertyValue->GetBoolean()); return; case PropertyType::Char16: writer->WriteByte(Char16Type); writer->WriteUInt16(propertyValue->GetChar16()); return; case PropertyType::Guid: writer->WriteByte(GuidType); writer->WriteGuid(propertyValue->GetGuid()); return; case PropertyType::String: WriteString(writer, propertyValue->GetString()); return; default: throw ref new InvalidArgumentException("Unsupported property type"); } } void WriteStringToObjectMap(DataWriter^ writer, IMap^ map) { writer->WriteByte(StringToObjectMapType); writer->WriteUInt32(map->Size); for (auto&& pair : map) { WriteObject(writer, pair->Key); WriteObject(writer, pair->Value); } writer->WriteByte(MapEndMarker); } void WriteObject(DataWriter^ writer, Object^ object) { if (object == nullptr) { writer->WriteByte(NullPtrType); return; } auto propertyObject = dynamic_cast(object); if (propertyObject != nullptr) { WriteProperty(writer, propertyObject); return; } auto mapObject = dynamic_cast^>(object); if (mapObject != nullptr) { WriteStringToObjectMap(writer, mapObject); return; } throw ref new InvalidArgumentException("Unsupported data type"); } String^ ReadString(DataReader^ reader) { int length = reader->ReadUInt32(); String^ string = reader->ReadString(length); return string; } IMap^ ReadStringToObjectMap(DataReader^ reader) { auto map = ref new Map(); auto size = reader->ReadUInt32(); for (unsigned int index = 0; index < size; index++) { auto key = safe_cast(ReadObject(reader)); auto value = ReadObject(reader); map->Insert(key, value); } if (reader->ReadByte() != MapEndMarker) { throw ref new InvalidArgumentException("Invalid stream"); } return map; } Object^ ReadObject(DataReader^ reader) { auto type = reader->ReadByte(); switch (type) { case NullPtrType: return nullptr; case UInt8Type: return reader->ReadByte(); case UInt16Type: return reader->ReadUInt16(); case UInt32Type: return reader->ReadUInt32(); case UInt64Type: return reader->ReadUInt64(); case Int16Type: return reader->ReadInt16(); case Int32Type: return reader->ReadInt32(); case Int64Type: return reader->ReadInt64(); case SingleType: return reader->ReadSingle(); case DoubleType: return reader->ReadDouble(); case BooleanType: return reader->ReadBoolean(); case Char16Type: return (char16_t)reader->ReadUInt16(); case GuidType: return reader->ReadGuid(); case StringType: return ReadString(reader); case StringToObjectMapType: return ReadStringToObjectMap(reader); default: throw ref new InvalidArgumentException("Unsupported property type"); } } } #pragma endregion