|
@@ -1,291 +1,151 @@
|
|
|
using System.IO;
|
|
|
-using System.Windows;
|
|
|
-using System.Windows.Threading;
|
|
|
using PixiEditor.Extensions.Common.UserPreferences;
|
|
|
using PixiEditor.Helpers;
|
|
|
using PixiEditor.Models.DocumentModels;
|
|
|
+using PixiEditor.Models.DocumentModels.Autosave;
|
|
|
+using PixiEditor.Models.DocumentModels.Autosave.Enums;
|
|
|
+using PixiEditor.Models.DocumentModels.Autosave.Structs;
|
|
|
using PixiEditor.Models.IO;
|
|
|
-using PixiEditor.Views.UserControls;
|
|
|
|
|
|
namespace PixiEditor.ViewModels.SubViewModels.Document;
|
|
|
|
|
|
|
|
|
internal class AutosaveDocumentViewModel : NotifyableObject
|
|
|
{
|
|
|
- private readonly DispatcherTimer savingTimer;
|
|
|
- private readonly DispatcherTimer busyTimer;
|
|
|
- private int savingFailed;
|
|
|
- private Guid tempGuid;
|
|
|
- private bool autosaveEnabled = true;
|
|
|
- private bool waitingForUpdateableChangeEnd = false;
|
|
|
- private LastAutosaveData? lastAutosaveData = null;
|
|
|
- private DateTime? autosaveLaunchDateTime = null;
|
|
|
-
|
|
|
- private DocumentViewModel Document { get; }
|
|
|
-
|
|
|
- private double AutosavePeriodMinutes { get; set; } = -1;
|
|
|
-
|
|
|
- private AutosaveStateData autosaveStateData;
|
|
|
- public AutosaveStateData AutosaveStateData
|
|
|
+ private AutosaveStateData? autosaveStateData;
|
|
|
+ public AutosaveStateData? AutosaveStateData
|
|
|
{
|
|
|
get => autosaveStateData;
|
|
|
set => SetProperty(ref autosaveStateData, value);
|
|
|
}
|
|
|
|
|
|
- public bool Enabled
|
|
|
+ private bool currentDocumentAutosaveEnabled = true;
|
|
|
+ public bool CurrentDocumentAutosaveEnabled
|
|
|
{
|
|
|
- get => autosaveEnabled;
|
|
|
+ get => currentDocumentAutosaveEnabled;
|
|
|
set
|
|
|
{
|
|
|
- if (autosaveEnabled == value)
|
|
|
+ if (currentDocumentAutosaveEnabled == value)
|
|
|
return;
|
|
|
|
|
|
- AutosavePeriodChanged(
|
|
|
- IPreferences.Current!.GetPreference(
|
|
|
- PreferencesConstants.AutosavePeriodMinutes,
|
|
|
- PreferencesConstants.AutosavePeriodDefault),
|
|
|
- value);
|
|
|
- SetProperty(ref autosaveEnabled, value);
|
|
|
+ SetProperty(ref currentDocumentAutosaveEnabled, value);
|
|
|
+ StopOrStartAutosaverIfNecessary();
|
|
|
}
|
|
|
}
|
|
|
|
|
|
- public string LastSavedPath { get; private set; }
|
|
|
+ private DocumentAutosaver? autosaver;
|
|
|
+ private DocumentViewModel Document { get; }
|
|
|
+ private Guid autosaveFileGuid = Guid.NewGuid();
|
|
|
+ public string AutosavePath => AutosaveHelper.GetAutosavePath(autosaveFileGuid);
|
|
|
|
|
|
- public static bool SaveStateEnabled => IPreferences.Current!.GetPreference(PreferencesConstants.SaveSessionStateEnabled, PreferencesConstants.SaveSessionStateDefault);
|
|
|
+ public string LastAutosavedPath { get; set; }
|
|
|
|
|
|
- private bool SaveToDocumentPath => IPreferences.Current!.GetPreference(PreferencesConstants.AutosaveToDocumentPath, PreferencesConstants.AutosaveToDocumentPathDefault);
|
|
|
+ private static bool SaveUserFileEnabled => IPreferences.Current!.GetPreference(PreferencesConstants.AutosaveToDocumentPath, PreferencesConstants.AutosaveToDocumentPathDefault);
|
|
|
+ private static double AutosavePeriod => IPreferences.Current!.GetPreference(PreferencesConstants.AutosavePeriodMinutes, PreferencesConstants.AutosavePeriodDefault);
|
|
|
+ private static bool AutosaveEnabledGlobally => IPreferences.Current!.GetPreference(PreferencesConstants.AutosaveEnabled, PreferencesConstants.AutosaveEnabledDefault);
|
|
|
|
|
|
public AutosaveDocumentViewModel(DocumentViewModel document, DocumentInternalParts internals)
|
|
|
{
|
|
|
Document = document;
|
|
|
- tempGuid = Guid.NewGuid();
|
|
|
-
|
|
|
- var dispatcher = Application.Current.Dispatcher;
|
|
|
-
|
|
|
- savingTimer = new DispatcherTimer(DispatcherPriority.Normal);
|
|
|
- savingTimer.Tick += (_, _) =>
|
|
|
- {
|
|
|
- savingTimer.Stop();
|
|
|
- TryAutosave();
|
|
|
- };
|
|
|
-
|
|
|
- busyTimer = new DispatcherTimer(DispatcherPriority.Normal, dispatcher) { Interval = TimeSpan.FromMilliseconds(80) };
|
|
|
- busyTimer.Tick += (_, _) =>
|
|
|
- {
|
|
|
- busyTimer!.Stop();
|
|
|
- Document.Busy = true;
|
|
|
- };
|
|
|
-
|
|
|
- internals.ChangeController.UpdateableChangeEnded += OnUpdateableChangeEnded;
|
|
|
-
|
|
|
- var preferences = IPreferences.Current;
|
|
|
-
|
|
|
- preferences!.AddCallback<double>(PreferencesConstants.AutosavePeriodMinutes, (v) => AutosavePeriodChanged(v, autosaveEnabled));
|
|
|
- AutosavePeriodChanged(preferences.GetPreference(PreferencesConstants.AutosavePeriodMinutes, PreferencesConstants.AutosavePeriodDefault), autosaveEnabled);
|
|
|
+ internals.ChangeController.UpdateableChangeEnded += ((_, _) => autosaver?.OnUpdateableChangeEnded());
|
|
|
+ IPreferences.Current!.AddCallback(PreferencesConstants.AutosaveEnabled, PreferenceUpdateCallback);
|
|
|
+ IPreferences.Current!.AddCallback(PreferencesConstants.AutosavePeriodMinutes, PreferenceUpdateCallback);
|
|
|
+ IPreferences.Current!.AddCallback(PreferencesConstants.AutosaveToDocumentPath, PreferenceUpdateCallback);
|
|
|
+ StopOrStartAutosaverIfNecessary();
|
|
|
}
|
|
|
|
|
|
- private AutosaveStateData CreateAutosaveStateData()
|
|
|
+ private void PreferenceUpdateCallback(object _)
|
|
|
{
|
|
|
- return new AutosaveStateData
|
|
|
- {
|
|
|
- LastAutosaveData = lastAutosaveData,
|
|
|
- AutosaveLaunchDateTime = autosaveLaunchDateTime ?? DateTime.Now,
|
|
|
- AutosaveInterval = TimeSpan.FromMinutes(AutosavePeriodMinutes),
|
|
|
- AutosaveState = AutosaveState.Paused
|
|
|
- };
|
|
|
+ StopOrStartAutosaverIfNecessary();
|
|
|
}
|
|
|
|
|
|
- public static void AutosaveOnClose()
|
|
|
+ private void StopAutosaver()
|
|
|
{
|
|
|
-
|
|
|
+ autosaver?.Dispose();
|
|
|
+ autosaver = null;
|
|
|
+ AutosaveStateData = null;
|
|
|
}
|
|
|
|
|
|
- public void TryAutosave(bool saveUserFileIfEnabled = true)
|
|
|
+ private void StopOrStartAutosaverIfNecessary()
|
|
|
{
|
|
|
- if (Document.AllChangesSaved)
|
|
|
- {
|
|
|
- RestartTimers();
|
|
|
-
|
|
|
- lastAutosaveData = new LastAutosaveData()
|
|
|
- {
|
|
|
- Time = DateTime.Now,
|
|
|
- BackupSaveResult = BackupAutosaveResult.NothingToSave,
|
|
|
- UserFileSaveResult = UserFileAutosaveResult.NothingToSave
|
|
|
- };
|
|
|
- AutosaveStateData = CreateAutosaveStateData() with { AutosaveState = AutosaveState.Idle };
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- if (Document.UpdateableChangeActive)
|
|
|
- {
|
|
|
- waitingForUpdateableChangeEnd = true;
|
|
|
+ StopAutosaver();
|
|
|
+ if (!AutosaveEnabledGlobally || !CurrentDocumentAutosaveEnabled)
|
|
|
return;
|
|
|
- }
|
|
|
-
|
|
|
- SafeAutosave(saveUserFileIfEnabled);
|
|
|
+
|
|
|
+ autosaver = new DocumentAutosaver(Document, TimeSpan.FromMinutes(AutosavePeriod), SaveUserFileEnabled);
|
|
|
+ autosaver.JobChanged += (_, _) => AutosaveStateData = autosaver.State;
|
|
|
+ AutosaveStateData = autosaver.State;
|
|
|
}
|
|
|
|
|
|
- public void PanicAutosaveFromDeadlockDetector()
|
|
|
+ public bool AutosaveOnClose()
|
|
|
{
|
|
|
- string filePath = Path.Join(Paths.PathToUnsavedFilesFolder, $"autosave-{tempGuid}.pixi");
|
|
|
- Directory.CreateDirectory(Directory.GetParent(filePath)!.FullName);
|
|
|
-
|
|
|
- var result = Exporter.TrySave(Document, filePath);
|
|
|
-
|
|
|
- if (result == SaveResult.Success)
|
|
|
- {
|
|
|
- LastSavedPath = filePath;
|
|
|
- }
|
|
|
- }
|
|
|
+ if (Document.AllChangesSaved)
|
|
|
+ return true;
|
|
|
|
|
|
- private async void SafeAutosave(bool saveUserFile)
|
|
|
- {
|
|
|
try
|
|
|
{
|
|
|
- await Autosave(saveUserFile);
|
|
|
+ string filePath = AutosavePath;
|
|
|
+ Directory.CreateDirectory(Directory.GetParent(filePath)!.FullName);
|
|
|
+ bool success = Exporter.TrySave(Document, filePath) == SaveResult.Success;
|
|
|
+ if (success)
|
|
|
+ AddAutosaveHistoryEntry(AutosaveHistoryType.OnClose);
|
|
|
+
|
|
|
+ return success;
|
|
|
}
|
|
|
catch (Exception e)
|
|
|
{
|
|
|
- savingFailed++;
|
|
|
-
|
|
|
- lastAutosaveData = new LastAutosaveData()
|
|
|
- {
|
|
|
- Time = DateTime.Now,
|
|
|
- BackupSaveResult = BackupAutosaveResult.Error,
|
|
|
- UserFileSaveResult = UserFileAutosaveResult.ExceptionWhileSaving
|
|
|
- };
|
|
|
- AutosaveStateData = CreateAutosaveStateData() with { AutosaveState = AutosaveState.Idle };
|
|
|
-
|
|
|
- busyTimer.Stop();
|
|
|
- Document.Busy = false;
|
|
|
-
|
|
|
- RestartTimers();
|
|
|
-
|
|
|
- if (savingFailed == 1)
|
|
|
- {
|
|
|
- CrashHelper.SendExceptionInfoToWebhook(e);
|
|
|
- }
|
|
|
+ return false;
|
|
|
}
|
|
|
}
|
|
|
-
|
|
|
- private async Task Autosave(bool saveUserFile)
|
|
|
- {
|
|
|
- AutosaveStateData = CreateAutosaveStateData() with { AutosaveState = AutosaveState.InProgress };
|
|
|
|
|
|
- string filePath = Path.Join(Paths.PathToUnsavedFilesFolder, $"autosave-{tempGuid}.pixi");
|
|
|
- Directory.CreateDirectory(Directory.GetParent(filePath)!.FullName);
|
|
|
-
|
|
|
- busyTimer.Start();
|
|
|
- var result = Exporter.TrySave(Document, filePath);
|
|
|
+ public void AddAutosaveHistoryEntry(AutosaveHistoryType type, AutosaveHistoryResult result)
|
|
|
+ {
|
|
|
+ List<AutosaveHistorySession>? historySessions = IPreferences.Current!.GetLocalPreference<List<AutosaveHistorySession>>(PreferencesConstants.AutosaveHistory);
|
|
|
+ if (historySessions is null)
|
|
|
+ historySessions = new();
|
|
|
|
|
|
- UserFileAutosaveResult userFileSaveResult = UserFileAutosaveResult.Disabled;
|
|
|
-
|
|
|
- if (result == SaveResult.Success)
|
|
|
+ AutosaveHistorySession currentSession;
|
|
|
+ if (historySessions.Count == 0 || historySessions[^1].SessionGuid != ViewModelMain.Current.CurrentSessionId)
|
|
|
{
|
|
|
- if (saveUserFile && SaveToDocumentPath && Document.FullFilePath != null)
|
|
|
- {
|
|
|
- userFileSaveResult = await CopyTempToUserFile(filePath);
|
|
|
- }
|
|
|
-
|
|
|
- lastAutosaveData = new LastAutosaveData
|
|
|
- {
|
|
|
- Time = DateTime.Now,
|
|
|
- BackupSaveResult = BackupAutosaveResult.Success,
|
|
|
- UserFileSaveResult = userFileSaveResult
|
|
|
- };
|
|
|
- AutosaveStateData = CreateAutosaveStateData() with { AutosaveState = AutosaveState.Idle, LastAutosaveData = lastAutosaveData };
|
|
|
-
|
|
|
- Document.MarkAsAutosaved();
|
|
|
- LastSavedPath = filePath;
|
|
|
+ currentSession = new AutosaveHistorySession(ViewModelMain.Current.CurrentSessionId, ViewModelMain.Current.LaunchDateTime);
|
|
|
+ historySessions.Add(currentSession);
|
|
|
}
|
|
|
else
|
|
|
{
|
|
|
- busyTimer.Stop();
|
|
|
- Document.Busy = false;
|
|
|
-
|
|
|
- lastAutosaveData = new LastAutosaveData()
|
|
|
- {
|
|
|
- Time = DateTime.Now,
|
|
|
- BackupSaveResult = BackupAutosaveResult.Error,
|
|
|
- UserFileSaveResult = userFileSaveResult
|
|
|
- };
|
|
|
- AutosaveStateData = CreateAutosaveStateData() with { AutosaveState = AutosaveState.Idle, LastAutosaveData = lastAutosaveData };
|
|
|
-
|
|
|
- savingFailed++;
|
|
|
-
|
|
|
- if (savingFailed < 3)
|
|
|
- {
|
|
|
- int savingFailedCopy = savingFailed;
|
|
|
- Task.Run(() => CrashHelper.SendExceptionInfoToWebhook(new Exception($"Failed to autosave for the {savingFailedCopy}. time due to {result}")));
|
|
|
- }
|
|
|
+ currentSession = historySessions[^1];
|
|
|
}
|
|
|
-
|
|
|
- busyTimer.Stop();
|
|
|
- Document.Busy = false;
|
|
|
|
|
|
- RestartTimers();
|
|
|
- }
|
|
|
-
|
|
|
- private async Task<UserFileAutosaveResult> CopyTempToUserFile(string tempPath)
|
|
|
- {
|
|
|
- if (!File.Exists(Document.FullFilePath))
|
|
|
- return UserFileAutosaveResult.NoUserFile;
|
|
|
+ AutosaveHistoryEntry entry = new(DateTime.Now, type, result, autosaveFileGuid);
|
|
|
+ currentSession.AutosaveEntries.Add(entry);
|
|
|
|
|
|
- var result = await Task.Run(Copy);
|
|
|
- Document.MarkAsSaved();
|
|
|
- return result;
|
|
|
-
|
|
|
- UserFileAutosaveResult Copy()
|
|
|
- {
|
|
|
- try
|
|
|
- {
|
|
|
- File.Copy(tempPath, Document.FullFilePath!, true);
|
|
|
- return UserFileAutosaveResult.Success;
|
|
|
- }
|
|
|
- catch (Exception e) when (e is UnauthorizedAccessException or DirectoryNotFoundException)
|
|
|
- {
|
|
|
- return UserFileAutosaveResult.NoUserFile;
|
|
|
- }
|
|
|
- catch
|
|
|
- {
|
|
|
- return UserFileAutosaveResult.ExceptionWhileSaving;
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
-
|
|
|
- private void RestartTimers()
|
|
|
- {
|
|
|
- savingTimer.Start();
|
|
|
+ IPreferences.Current.UpdateLocalPreference(PreferencesConstants.AutosaveHistory, historySessions);
|
|
|
}
|
|
|
|
|
|
- private void AutosavePeriodChanged(double minutes, bool documentEnabled)
|
|
|
+ public void PanicAutosaveFromDeadlockDetector()
|
|
|
{
|
|
|
- if ((int)minutes == -1 || !documentEnabled)
|
|
|
- {
|
|
|
- AutosavePeriodMinutes = minutes;
|
|
|
- AutosaveStateData = CreateAutosaveStateData();
|
|
|
- return;
|
|
|
- }
|
|
|
-
|
|
|
- var timerEnabled = savingTimer.IsEnabled || (int)AutosavePeriodMinutes == -1 || !Enabled;
|
|
|
+ /*
|
|
|
+ string filePath = Path.Join(Paths.PathToUnsavedFilesFolder, $"autosave-{tempGuid}.pixi");
|
|
|
+ Directory.CreateDirectory(Directory.GetParent(filePath)!.FullName);
|
|
|
|
|
|
- savingTimer.IsEnabled = false;
|
|
|
+ var result = Exporter.TrySave(Document, filePath);
|
|
|
|
|
|
- var timeSpan = TimeSpan.FromMinutes(minutes);
|
|
|
- savingTimer.Interval = timeSpan;
|
|
|
- AutosavePeriodMinutes = minutes;
|
|
|
-
|
|
|
- savingTimer.IsEnabled = timerEnabled;
|
|
|
+ if (result == SaveResult.Success)
|
|
|
+ {
|
|
|
+ LastSavedPath = filePath;
|
|
|
+ }*/
|
|
|
}
|
|
|
-
|
|
|
- private void OnUpdateableChangeEnded(object? sender, EventArgs args)
|
|
|
+
|
|
|
+ public void SetTempFileGuidAndLastSavedPath(Guid guid, string lastSavedPath)
|
|
|
{
|
|
|
-
|
|
|
+ autosaveFileGuid = guid;
|
|
|
+ LastAutosavedPath = lastSavedPath;
|
|
|
}
|
|
|
|
|
|
- public void SetTempFileGuidAndLastSavedPath(Guid guid, string lastSavedPath)
|
|
|
+ public void OnDocumentClosed()
|
|
|
{
|
|
|
- tempGuid = guid;
|
|
|
- LastSavedPath = lastSavedPath;
|
|
|
+ CurrentDocumentAutosaveEnabled = false;
|
|
|
+ IPreferences.Current!.RemoveCallback(PreferencesConstants.AutosaveEnabled, PreferenceUpdateCallback);
|
|
|
+ IPreferences.Current!.RemoveCallback(PreferencesConstants.AutosavePeriodMinutes, PreferenceUpdateCallback);
|
|
|
+ IPreferences.Current!.RemoveCallback(PreferencesConstants.AutosaveToDocumentPath, PreferenceUpdateCallback);
|
|
|
}
|
|
|
}
|