using System; using System.Collections.Generic; using System.IO; using System.Runtime.Serialization; using System.Threading.Tasks; using Windows.Storage; using Windows.Storage.Streams; using Windows.UI.Xaml; using Windows.UI.Xaml.Controls; namespace ModernKeePass.Common { /// /// SuspensionManager captures global session state to simplify process lifetime management /// for an application. Note that session state will be automatically cleared under a variety /// of conditions and should only be used to store information that would be convenient to /// carry across sessions, but that should be discarded when an application crashes or is /// upgraded. /// internal static class SuspensionManager { private static Dictionary _sessionState = new Dictionary(); private static readonly List _knownTypes = new List(); private const string sessionStateFilename = "_sessionState.xml"; /// /// Provides access to global session state for the current session. This state is /// serialized by and restored by /// , so values must be serializable by /// and should be as compact as possible. Strings /// and other self-contained data types are strongly recommended. /// public static Dictionary SessionState => _sessionState; /// /// List of custom types provided to the when /// reading and writing session state. Initially empty, additional types may be /// added to customize the serialization process. /// public static List KnownTypes => _knownTypes; /// /// 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. public static async Task SaveAsync() { try { // Save the navigation state for all registered frames foreach (var weakFrameReference in _registeredFrames) { Frame frame; if (weakFrameReference.TryGetTarget(out frame)) { SaveFrameNavigationState(frame); } } // Serialize the session state synchronously to avoid asynchronous access to shared // state MemoryStream sessionData = new MemoryStream(); DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary), _knownTypes); serializer.WriteObject(sessionData, _sessionState); // Get an output stream for the SessionState file and write the state asynchronously StorageFile file = await ApplicationData.Current.LocalFolder.CreateFileAsync(sessionStateFilename, CreationCollisionOption.ReplaceExisting); using (Stream fileStream = await file.OpenStreamForWriteAsync()) { sessionData.Seek(0, SeekOrigin.Begin); await sessionData.CopyToAsync(fileStream); } } catch (Exception e) { throw new SuspensionManagerException(e); } } /// /// 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. /// /// An optional key that identifies the type of session. /// This can be used to distinguish between multiple application launch scenarios. /// An asynchronous task that reflects when session state has been read. The /// content of should not be relied upon until this task /// completes. public static async Task RestoreAsync(String sessionBaseKey = null) { _sessionState = new Dictionary(); try { // Get the input stream for the SessionState file StorageFile file = await ApplicationData.Current.LocalFolder.GetFileAsync(sessionStateFilename); using (IInputStream inStream = await file.OpenSequentialReadAsync()) { // Deserialize the Session State DataContractSerializer serializer = new DataContractSerializer(typeof(Dictionary), _knownTypes); _sessionState = (Dictionary)serializer.ReadObject(inStream.AsStreamForRead()); } // Restore any registered frames to their saved state foreach (var weakFrameReference in _registeredFrames) { Frame frame; if (weakFrameReference.TryGetTarget(out frame) && (string)frame.GetValue(FrameSessionBaseKeyProperty) == sessionBaseKey) { frame.ClearValue(FrameSessionStateProperty); RestoreFrameNavigationState(frame); } } } catch (Exception e) { throw new SuspensionManagerException(e); } } private static DependencyProperty FrameSessionStateKeyProperty = DependencyProperty.RegisterAttached("_FrameSessionStateKey", typeof(String), typeof(SuspensionManager), null); private static DependencyProperty FrameSessionBaseKeyProperty = DependencyProperty.RegisterAttached("_FrameSessionBaseKeyParams", typeof(String), typeof(SuspensionManager), null); private static DependencyProperty FrameSessionStateProperty = DependencyProperty.RegisterAttached("_FrameSessionState", typeof(Dictionary), typeof(SuspensionManager), null); private static List> _registeredFrames = new List>(); /// /// 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. /// An optional key that identifies the type of session. /// This can be used to distinguish between multiple application launch scenarios. public static void RegisterFrame(Frame frame, String sessionStateKey, String sessionBaseKey = null) { if (frame.GetValue(FrameSessionStateKeyProperty) != null) { throw new InvalidOperationException("Frames can only be registered to one session state key"); } if (frame.GetValue(FrameSessionStateProperty) != null) { throw new InvalidOperationException("Frames must be either be registered before accessing frame session state, or not registered at all"); } if (!string.IsNullOrEmpty(sessionBaseKey)) { frame.SetValue(FrameSessionBaseKeyProperty, sessionBaseKey); sessionStateKey = sessionBaseKey + "_" + sessionStateKey; } // 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.Add(new WeakReference(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. public static void 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) SessionState.Remove((String)frame.GetValue(FrameSessionStateKeyProperty)); _registeredFrames.RemoveAll((weakFrameReference) => { Frame testFrame; return !weakFrameReference.TryGetTarget(out testFrame) || testFrame == frame; }); } /// /// 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 /// . public static Dictionary SessionStateForFrame(Frame frame) { var frameState = (Dictionary)frame.GetValue(FrameSessionStateProperty); if (frameState == null) { var frameSessionKey = (String)frame.GetValue(FrameSessionStateKeyProperty); if (frameSessionKey != null) { // Registered frames reflect the corresponding session state if (!_sessionState.ContainsKey(frameSessionKey)) { _sessionState[frameSessionKey] = new Dictionary(); } frameState = (Dictionary)_sessionState[frameSessionKey]; } else { // Frames that aren't registered have transient state frameState = new Dictionary(); } frame.SetValue(FrameSessionStateProperty, frameState); } return frameState; } private static void RestoreFrameNavigationState(Frame frame) { var frameState = SessionStateForFrame(frame); if (frameState.ContainsKey("Navigation")) { frame.SetNavigationState((String)frameState["Navigation"]); } } private static void SaveFrameNavigationState(Frame frame) { var frameState = SessionStateForFrame(frame); frameState["Navigation"] = frame.GetNavigationState(); } } public class SuspensionManagerException : Exception { public SuspensionManagerException() { } public SuspensionManagerException(Exception e) : base("SuspensionManager failed", e) { } } }