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 ();