diff --git a/Pinta.Core/Classes/Document.cs b/Pinta.Core/Classes/Document.cs index c2143a62b8..e0a449b1bb 100644 --- a/Pinta.Core/Classes/Document.cs +++ b/Pinta.Core/Classes/Document.cs @@ -25,6 +25,7 @@ // THE SOFTWARE. using System; +using System.Diagnostics.CodeAnalysis; using System.Threading.Tasks; using Cairo; @@ -140,6 +141,7 @@ public Gio.File? File { /// /// Whether the document is associated with a file on disk. /// + [MemberNotNullWhen(true, nameof(File))] public bool HasFile => file is not null; /// Whether or not the document has been saved to the file that it is currently associated with in the @@ -445,4 +447,15 @@ private void OnSelectionChanged () public event EventHandler? Renamed; public event LayerCloneEvent? LayerCloned; public event EventHandler? SelectionChanged; + + private bool is_autosave_dirty; + public bool IsAutosaveDirty { + get => is_autosave_dirty; + set => is_autosave_dirty = value; + } + private uint autosave_files_index; + public uint AutosaveFilesIndex { + get => autosave_files_index; + set => autosave_files_index = value; + } } diff --git a/Pinta.Core/Classes/DocumentHistory.cs b/Pinta.Core/Classes/DocumentHistory.cs index 5a25ed7047..7a8d546408 100644 --- a/Pinta.Core/Classes/DocumentHistory.cs +++ b/Pinta.Core/Classes/DocumentHistory.cs @@ -73,8 +73,10 @@ public void PushNewItem (BaseHistoryItem newItem) history.Add (newItem); Pointer = history.Count - 1; - if (newItem.CausesDirty) + if (newItem.CausesDirty) { document.IsDirty = true; + document.IsAutosaveDirty = true; + } HistoryItemAdded?.Invoke (this, new HistoryItemAddedEventArgs (newItem)); } diff --git a/Pinta.Core/Managers/WorkspaceManager.cs b/Pinta.Core/Managers/WorkspaceManager.cs index 97e0b03328..8ae3f52ed1 100644 --- a/Pinta.Core/Managers/WorkspaceManager.cs +++ b/Pinta.Core/Managers/WorkspaceManager.cs @@ -161,7 +161,8 @@ public sealed class WorkspaceManager : IWorkspaceService public WorkspaceManager ( SystemManager systemManager, ChromeManager chromeManager, - ImageConverterManager imageFormats) + ImageConverterManager imageFormats, + SettingsManager settings) { open_documents = []; OpenDocuments = new ReadOnlyCollection (open_documents); @@ -169,6 +170,8 @@ public WorkspaceManager ( chrome_manager = chromeManager; image_formats = imageFormats; + + StartAutosaveTimer(settings); } public int ActiveDocumentIndex @@ -465,6 +468,89 @@ private Task ShowFilePermissionErrorDialog ( return chrome_manager.ShowMessageDialog (parent, message, details); } + private uint autosave_timer_id; + private uint autosave_files; + private int autosave_history; + private readonly HashSet autosaveFiles = new(); + private void StartAutosaveTimer (SettingsManager settings) + { + int interval = settings.GetSetting("autosave-interval", 0); + if (interval > 0) { + autosave_files = Math.Max(1, (uint)settings.GetSetting("autosave-files", 1)); + autosave_history = settings.GetSetting("autosave-history", 0); + autosave_timer_id = GLib.Functions.TimeoutAdd(GLib.Constants.PRIORITY_DEFAULT, (uint)interval * 1000, OnAutosaveTick); + } + } + private bool OnAutosaveTick () + { + if (!HasOpenDocuments) + return true; + + var format = PintaCore.ImageFormats.GetFormatByExtension ("ora"); + if (format?.Exporter is null) { + autosave_timer_id = 0; + return false; + } + + foreach (var doc in OpenDocuments) { + if (!doc.HasFile) + continue; + if (!doc.IsAutosaveDirty) + continue; + + string autosaveFile = doc.File.GetParseName() + ".pinta_autosave_" + doc.AutosaveFilesIndex + ".ora"; + doc.AutosaveFilesIndex += 1; + if ( doc.AutosaveFilesIndex == autosave_files ) doc.AutosaveFilesIndex = 0; + + format.Exporter.Export (doc, Gio.FileHelper.NewForPath (autosaveFile), PintaCore.Chrome.MainWindow); + + autosaveFiles.Add(autosaveFile); + DumpLastHistorySteps(doc); + doc.IsAutosaveDirty = false; + } + if ( autosave_history != 0 ) { + Console.WriteLine($"Date: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); + } + + return true; + } + public void StopAutosave () { + if (autosave_timer_id != 0) + { + GLib.Source.Remove(autosave_timer_id); + } + foreach (var file_string in autosaveFiles) + { + try + { + var file = Gio.FileHelper.NewForPath(file_string); + if (file.QueryExists(null)) + file.Delete(null); + } + catch + { + // ignore cleanup failures + } + } + autosaveFiles.Clear(); + } + private void DumpLastHistorySteps(Document doc) + { + if ( autosave_history != 0 ) { + Console.WriteLine("=== Last History Steps ==="); + + var history = doc.History; + var n = history.Items.Count(); + for (var i = Math.Max(0, n - autosave_history); i < n; i++) + { + var item = history.Items.ElementAt(i); + Console.WriteLine($"[{i}] {item.Text}"); + } + + Console.WriteLine("=========================="); + } + } + public event EventHandler? LayerAdded; public event EventHandler? LayerRemoved; public event EventHandler? SelectedLayerChanged; diff --git a/Pinta.Core/PintaCore.cs b/Pinta.Core/PintaCore.cs index abdba7e115..3a85197d36 100644 --- a/Pinta.Core/PintaCore.cs +++ b/Pinta.Core/PintaCore.cs @@ -78,7 +78,7 @@ static PintaCore () // --- Services that depend on other services ImageConverterManager imageFormats = new (settings); - WorkspaceManager workspace = new (system, chrome, imageFormats); + WorkspaceManager workspace = new (system, chrome, imageFormats, settings); ToolManager tools = new (workspace, chrome); PaletteManager palette = new (settings, paletteFormats); ActionManager actions = new (chrome, imageFormats, paletteFormats, palette, recentFiles, system, tools, workspace); diff --git a/Pinta/Actions/File/ExitAction.cs b/Pinta/Actions/File/ExitAction.cs index 746c59b7a4..870a0e89d2 100644 --- a/Pinta/Actions/File/ExitAction.cs +++ b/Pinta/Actions/File/ExitAction.cs @@ -67,6 +67,8 @@ private void Activated (object sender, EventArgs e) return; } + PintaCore.Workspace.StopAutosave(); + // Let everyone know we are quitting actions.App.RaiseBeforeQuit ();