Home | History | Annotate | Download | only in common
      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 
     22 using namespace SDKSample::Common;
     23 
     24 using namespace Concurrency;
     25 using namespace Platform;
     26 using namespace Platform::Collections;
     27 using namespace Windows::Foundation;
     28 using namespace Windows::Foundation::Collections;
     29 using namespace Windows::Storage;
     30 using namespace Windows::Storage::FileProperties;
     31 using namespace Windows::Storage::Streams;
     32 using namespace Windows::UI::Xaml;
     33 using namespace Windows::UI::Xaml::Controls;
     34 using namespace Windows::UI::Xaml::Interop;
     35 
     36 namespace
     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>
     53 IMap<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>
     61 private ref class WeakFrame sealed
     62 {
     63 private:
     64     WeakReference _frameReference;
     65 
     66 internal:
     67     WeakFrame(Frame^ frame) { _frameReference = frame; }
     68     property Frame^ ResolvedFrame
     69     {
     70         Frame^ get(void) { return _frameReference.Resolve<Frame>(); }
     71     };
     72 };
     73 
     74 namespace
     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>
     97 void 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>
    125 void 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>
    154 IMap<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 
    180 void 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 
    189 void 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>
    202 task<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>
    254 task<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 
    295 namespace
    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