Ver código fonte

Recover from unexpected save

Krzysztof Krysiński 6 meses atrás
pai
commit
a710f3839c

+ 5 - 2
src/PixiEditor/Data/Localization/Languages/en.json

@@ -908,6 +908,9 @@
   "TEMPERATURE_VALUE": "Temperature",
   "TINT_VALUE": "Tint",
   "FAILED_DOWNLOADING_UPDATE_TITLE": "Failed to download update",
-  "FAILED_DOWNLOADING_UPDATE": "Failed to download the update. Try again later."
-
+  "FAILED_DOWNLOADING_UPDATE": "Failed to download the update. Try again later.",
+  "UNEXPECTED_SHUTDOWN": "Unexpected shutdown",
+  "UNEXPECTED_SHUTDOWN_MSG": "PixiEditor was unexpectedly shut down. We've loaded latest autosave of your files.",
+  "OK": "OK",
+  "OPEN_AUTOSAVES": "Browse Autosaves"
 }

+ 2 - 1
src/PixiEditor/Models/DocumentModels/Autosave/AutosaveHistoryEntry.cs

@@ -1,9 +1,10 @@
 namespace PixiEditor.Models.DocumentModels.Autosave;
 
-internal class AutosaveHistoryEntry(DateTime dateTime, AutosaveHistoryType type, AutosaveHistoryResult result, Guid tempFileGuid)
+internal class AutosaveHistoryEntry(DateTime dateTime, AutosaveHistoryType type, AutosaveHistoryResult result, Guid tempFileGuid, string? originalPath)
 {
     public DateTime DateTime { get; set; } = dateTime;
     public AutosaveHistoryType Type { get; set; } = type;
     public AutosaveHistoryResult Result { get; set; } = result;
     public Guid TempFileGuid { get; set; } = tempFileGuid;
+    public string? OriginalPath { get; set; } = originalPath;
 }

+ 6 - 1
src/PixiEditor/ViewModels/Document/AutosaveDocumentViewModel.cs

@@ -78,7 +78,12 @@ internal class AutosaveDocumentViewModel : ObservableObject
     public bool AutosaveOnClose()
     {
         if (Document.AllChangesSaved)
+        {
+            AddAutosaveHistoryEntry(
+                AutosaveHistoryType.OnClose,
+                AutosaveHistoryResult.NothingToSave);
             return true;
+        }
 
         try
         {
@@ -114,7 +119,7 @@ internal class AutosaveDocumentViewModel : ObservableObject
             currentSession = historySessions[^1];
         }
 
-        AutosaveHistoryEntry entry = new(DateTime.Now, type, result, autosaveFileGuid);
+        AutosaveHistoryEntry entry = new(DateTime.Now, type, result, autosaveFileGuid, Document.FullFilePath);
         currentSession.AutosaveEntries.Add(entry);
 
         IPreferences.Current.UpdateLocalPreference(PreferencesConstants.AutosaveHistory, historySessions);

+ 55 - 29
src/PixiEditor/ViewModels/SubViewModels/FileViewModel.cs

@@ -320,7 +320,8 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
     /// <summary>
     /// Opens a .pixi file from path, creates a document from it, and adds it to the system
     /// </summary>
-    public void OpenRecoveredDotPixi(string? originalPath, string? autosavePath, Guid? autosaveGuid, byte[] dotPixiBytes)
+    public void OpenRecoveredDotPixi(string? originalPath, string? autosavePath, Guid? autosaveGuid,
+        byte[] dotPixiBytes)
     {
         DocumentViewModel document = Importer.ImportDocument(dotPixiBytes, originalPath);
         document.MarkAsUnsaved();
@@ -647,7 +648,8 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
 
         // Todo sure, no session saving, but shouldn't we still load backups in case of unexpected shutdown?
         // it probably should be handled elsewhere
-        if (!preferences.GetPreference<bool>(PreferencesConstants.SaveSessionStateEnabled, PreferencesConstants.SaveSessionStateDefault))
+        if (!preferences.GetPreference<bool>(PreferencesConstants.SaveSessionStateEnabled,
+                PreferencesConstants.SaveSessionStateDefault))
             return;
 
         var history =
@@ -669,18 +671,13 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
             select entryGroup.OrderBy(a => a.DateTime).ToList()
         ).ToList();
 
-        /*bool shutdownWasUnexpected = lastSession.AutosaveEntries.All(a => a.Type != AutosaveHistoryType.OnClose);
+        bool shutdownWasUnexpected = lastSession.AutosaveEntries.All(a => a.Type != AutosaveHistoryType.OnClose);
         if (shutdownWasUnexpected)
         {
-            List<List<AutosaveHistoryEntry>> lastBackups = (
-                from entry in lastSession.AutosaveEntries
-                group entry by entry.TempFileGuid into entryGroup
-                select entryGroup.OrderBy(a => a.DateTime).ToList()
-                ).ToList();
-            // todo notify about files getting recovered after unexpected shutdown
-            // also separate this out into a function
+            LoadFromUnexpectedShutdown(lastSession);
+
             return;
-        }*/
+        }
 
         foreach (var documentHistory in perDocumentHistories)
         {
@@ -690,6 +687,7 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
                 if (lastEntry.Type != AutosaveHistoryType.OnClose)
                 {
                     // unexpected shutdown happened, this file wasn't saved on close, but we supposedly have a backup
+                    LoadFromAutosave(lastEntry);
                 }
                 else
                 {
@@ -700,7 +698,11 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
                             break;
                         case AutosaveHistoryResult.SavedUserFile:
                         case AutosaveHistoryResult.NothingToSave:
-                            // load from user file
+                            if (lastEntry.OriginalPath != null)
+                            {
+                                OpenFromPath(lastEntry.OriginalPath);
+                            }
+
                             break;
                         default:
                             throw new ArgumentOutOfRangeException();
@@ -714,40 +716,64 @@ internal class FileViewModel : SubViewModel<ViewModelMain>
         }
 
         Owner.AutosaveViewModel.CleanupAutosavedFilesAndHistory();
+    }
 
-        /*foreach (var file in files)
+    private void LoadFromUnexpectedShutdown(AutosaveHistorySession lastSession)
+    {
+        List<List<AutosaveHistoryEntry>> lastBackups = (
+            from entry in lastSession.AutosaveEntries
+            group entry by entry.TempFileGuid
+            into entryGroup
+            select entryGroup.OrderBy(a => a.DateTime).ToList()
+        ).ToList();
+
+        try
         {
-            try
+            foreach (var backup in lastBackups)
             {
-                if (file.AutosavePath != null)
+                AutosaveHistoryEntry lastEntry = backup[^1];
+
+                bool loadFromUserFile = false;
+
+                if (lastEntry.OriginalPath != null && File.Exists(lastEntry.OriginalPath))
                 {
-                    var document = OpenFromPath(file.AutosavePath, false);
-                    document.FullFilePath = file.OriginalPath;
+                    DateTime saveFileWriteTime = File.GetLastWriteTime(lastEntry.OriginalPath);
+                    DateTime autosaveWriteTime = lastEntry.DateTime;
 
-                    if (file.AutosavePath != null)
-                    {
-                        document.AutosaveViewModel.SetTempFileGuidAndLastSavedPath(
-                            AutosaveHelper.GetAutosaveGuid(file.AutosavePath)!.Value, file.AutosavePath);
-                    }
+                    loadFromUserFile = saveFileWriteTime > autosaveWriteTime;
+                }
+
+                if (loadFromUserFile)
+                {
+                    OpenFromPath(lastEntry.OriginalPath);
                 }
                 else
                 {
-                    OpenFromPath(file.OriginalPath);
+                    LoadFromAutosave(lastEntry);
                 }
             }
-            catch (Exception e)
+
+            OptionsDialog<LocalizedString> dialog = new OptionsDialog<LocalizedString>("UNEXPECTED_SHUTDOWN",
+                new LocalizedString("UNEXPECTED_SHUTDOWN_MSG"),
+                MainWindow.Current!)
             {
-                CrashHelper.SendExceptionInfo(e);
-            }
-        }*/
+                { "OPEN_AUTOSAVES", _ => { IOperatingSystem.Current.OpenFolder(Paths.PathToUnsavedFilesFolder); } },
+                "OK"
+            };
+            dialog.ShowDialog(true);
+        }
+        catch (Exception e)
+        {
+            CrashHelper.SendExceptionInfo(e);
+        }
     }
 
     private void LoadFromAutosave(AutosaveHistoryEntry entry)
     {
         string path = AutosaveHelper.GetAutosavePath(entry.TempFileGuid);
-        if (path == null)
+        if (path == null || !File.Exists(path))
         {
-            // TODO: Notify
+            // TODO: Notice user when non-blocking notification system is implemented
             return;
         }
 

+ 9 - 6
src/PixiEditor/ViewModels/ViewModelMain.cs

@@ -91,6 +91,8 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
     public Guid CurrentSessionId { get; } = Guid.NewGuid();
     public DateTime LaunchDateTime { get; } = DateTime.Now;
 
+    public event Action<DocumentViewModel> BeforeDocumentClosed;
+
     public ViewModelMain()
     {
         Current = this;
@@ -166,6 +168,7 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
         ExtensionsSubViewModel = services.GetService<ExtensionsViewModel>(); // Must be last
 
         DocumentManagerSubViewModel.ActiveDocumentChanged += OnActiveDocumentChanged;
+        BeforeDocumentClosed += OnBeforeDocumentClosed;
     }
 
     public bool DocumentIsNotNull(object property)
@@ -181,7 +184,6 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
     [RelayCommand]
     public async Task CloseWindow()
     {
-        AutosaveAllForNextSession();
         UserWantsToClose = await DisposeAllDocumentsWithSaveConfirmation();
 
         if (UserWantsToClose)
@@ -236,15 +238,12 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
         return true;
     }
 
-    public void AutosaveAllForNextSession()
+    private void OnBeforeDocumentClosed(DocumentViewModel document)
     {
         if (!AutosaveViewModel.SaveSessionStateEnabled || DebugSubViewModel.ModifiedEditorData)
             return;
 
-        foreach (DocumentViewModel document in DocumentManagerSubViewModel.Documents)
-        {
-            document.AutosaveViewModel.AutosaveOnClose();
-        }
+        document.AutosaveViewModel.AutosaveOnClose();
     }
 
     /// <summary>
@@ -265,6 +264,7 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
 
 
         ConfirmationType result = ConfirmationType.No;
+        bool saved = false;
         if (!document.AllChangesSaved)
         {
             result = await ConfirmationDialog.Show(ConfirmationDialogMessage, ConfirmationDialogTitle);
@@ -272,11 +272,14 @@ internal partial class ViewModelMain : ViewModelBase, ICommandsHandler
             {
                 if (!await FileSubViewModel.SaveDocument(document, false))
                     return false;
+
+                saved = true;
             }
         }
 
         if (result != ConfirmationType.Canceled)
         {
+            BeforeDocumentClosed?.Invoke(document);
             if (!DocumentManagerSubViewModel.Documents.Remove(document))
                 throw new InvalidOperationException(
                     "Trying to close a document that's not in the documents collection. Likely, the document wasn't added there after creation by mistake.");