From bae8ab53bf51385c39a12d1b8978eaf36cc52c36 Mon Sep 17 00:00:00 2001 From: Costin Botescu Date: Sun, 14 Jun 2026 19:48:57 +0300 Subject: [PATCH 1/2] autosave --- Pinta.Core/Classes/Document.cs | 13 ++++ Pinta.Core/Classes/DocumentHistory.cs | 4 +- Pinta.Core/Managers/WorkspaceManager.cs | 86 ++++++++++++++++++++++++- Pinta.Core/PintaCore.cs | 2 +- Pinta/Actions/File/ExitAction.cs | 2 + 5 files changed, 104 insertions(+), 3 deletions(-) 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..76f97c8075 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,87 @@ 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; + } + + 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($"Date: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); + 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 (); From f449e0ddf7944fdbe1d5c54edf61499a6e0eef98 Mon Sep 17 00:00:00 2001 From: Costin Botescu Date: Mon, 15 Jun 2026 10:02:03 +0300 Subject: [PATCH 2/2] date, more explicit --- Pinta.Core/Managers/WorkspaceManager.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Pinta.Core/Managers/WorkspaceManager.cs b/Pinta.Core/Managers/WorkspaceManager.cs index 76f97c8075..8ae3f52ed1 100644 --- a/Pinta.Core/Managers/WorkspaceManager.cs +++ b/Pinta.Core/Managers/WorkspaceManager.cs @@ -508,6 +508,9 @@ private bool OnAutosaveTick () DumpLastHistorySteps(doc); doc.IsAutosaveDirty = false; } + if ( autosave_history != 0 ) { + Console.WriteLine($"Date: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); + } return true; } @@ -544,7 +547,6 @@ private void DumpLastHistorySteps(Document doc) Console.WriteLine($"[{i}] {item.Text}"); } - Console.WriteLine($"Date: {DateTime.Now:yyyy-MM-dd HH:mm:ss}"); Console.WriteLine("=========================="); } }