Skip to content

Commit be65e1f

Browse files
committed
Write autosaves into a sub-directory with timestamps to make autosaves friendly for multiple-instance mapping
1 parent de87e1c commit be65e1f

5 files changed

Lines changed: 82 additions & 4 deletions

File tree

docs/Manual.md

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -86,4 +86,6 @@ If you have a multi-monitor setup, when maximized with F11, WAE fills the monito
8686

8787
WAE automatically saves a backup of your map every few minutes to prevent data loss in case of crash of the system or editor.
8888

89-
**Auto-save does not overwrite your open map file**, but instead the save is written as `autosave.map` inside WAE's directory. If you encounter a crash or other issues that would cause work to be lost, you can find the `autosave.map` file from WAE's directory, copy it and continue your work from it.
89+
**Auto-save does not overwrite your open map file**, but instead the save is written to a subdirectory called `AutoSaves` inside WAE's directory. If you encounter a crash or other issues that would cause work to be lost, you can find the latest autosave file from WAE's directory, copy it and continue your work from it.
90+
91+
Auto-saves older than one day are purged automatically. The latest 5 auto-saves are never purged automatically.

src/TSMapEditor/Misc/AutosaveTimer.cs

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,8 @@
11
using Rampastring.Tools;
22
using System;
3+
using System.Collections.Generic;
34
using System.IO;
5+
using System.Linq;
46
using TSMapEditor.Models;
57
using TSMapEditor.Settings;
68

@@ -16,11 +18,16 @@ public AutosaveTimer(Map map)
1618

1719
private readonly Map map;
1820

21+
private const string AutoSavesDirectory = "AutoSaves";
22+
private const string MapFileExtension = ".map";
23+
1924
public TimeSpan AutoSaveTime { get; set; }
2025

2126
private void DoSave()
2227
{
23-
map.AutoSave(Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), "autosave.map"));
28+
var now = DateTime.Now;
29+
string timestamp = $"{now.Year}_{now.Month:D2}_{now.Day:D2}_{now.Hour:D2}_{now.Second:D2}";
30+
map.AutoSave(Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), AutoSavesDirectory, $"autosave_{timestamp}{MapFileExtension}"));
2431
}
2532

2633
public string Update(TimeSpan elapsedTime)
@@ -51,5 +58,62 @@ public string Update(TimeSpan elapsedTime)
5158

5259
return null;
5360
}
61+
62+
63+
/// <summary>
64+
/// Purges old auto-saves from the autosaves directory.
65+
/// </summary>
66+
public static void Purge()
67+
{
68+
Logger.Log("Purging old auto-saves.");
69+
70+
string path = Path.Combine(Path.GetDirectoryName(Environment.ProcessPath), AutoSavesDirectory);
71+
72+
if (!Directory.Exists(path))
73+
{
74+
Logger.Log("Auto-saves directory does not exist!");
75+
return;
76+
}
77+
78+
string[] filePaths = Directory.GetFiles(path);
79+
List<FileInfo> mapFileInfos = new List<FileInfo>();
80+
foreach (string filePath in filePaths)
81+
{
82+
if (!filePath.EndsWith(MapFileExtension))
83+
continue;
84+
85+
FileInfo fileInfo = new FileInfo(filePath);
86+
87+
if (fileInfo.CreationTime < DateTime.Now.AddDays(-1))
88+
mapFileInfos.Add(fileInfo);
89+
}
90+
91+
// Leave the latest 5 files. Purge everything else.
92+
mapFileInfos = mapFileInfos.OrderBy(fileInfo => fileInfo.CreationTime).Reverse().ToList();
93+
const int leaveCount = 5;
94+
int purgeCount = mapFileInfos.Count - leaveCount;
95+
96+
if (purgeCount <= 0)
97+
{
98+
Logger.Log("There are not enough old auto-saves to purge.");
99+
return;
100+
}
101+
102+
Logger.Log($"Found {purgeCount} autosaves to purge.");
103+
104+
for (int i = leaveCount; i < mapFileInfos.Count; i++)
105+
{
106+
var autosavePath = mapFileInfos[i].FullName;
107+
108+
try
109+
{
110+
File.Delete(autosavePath);
111+
}
112+
catch (IOException ex)
113+
{
114+
Logger.Log($"Failed to delete auto-save {Path.GetFileName(autosavePath)}. Exception message: " + ex.Message);
115+
}
116+
}
117+
}
54118
}
55119
}

src/TSMapEditor/Models/Map.cs

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ public class Map : IMap
3939
public event EventHandler<CellLightingEventArgs> CellLightingModified;
4040
public event EventHandler MapManuallySaved;
4141
public event EventHandler MapAutoSaved;
42+
public event EventHandler MapSaveFailed;
4243
public event EventHandler PreSave;
4344
public event EventHandler PostSave;
4445

@@ -385,7 +386,16 @@ private void Write(string filePath = null)
385386

386387
string savePath = filePath ?? LoadedINI.FileName;
387388

388-
LoadedINI.WriteIniFile(savePath);
389+
try
390+
{
391+
Directory.CreateDirectory(Path.GetDirectoryName(savePath));
392+
LoadedINI.WriteIniFile(savePath);
393+
}
394+
catch (IOException ex)
395+
{
396+
Logger.Log($"Saving map failed! Path: {savePath}, exception message: {ex.Message}");
397+
MapSaveFailed?.Invoke(ex, EventArgs.Empty);
398+
}
389399

390400
PostSave?.Invoke(this, EventArgs.Empty);
391401
}

src/TSMapEditor/Rendering/GameClass.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
using System.Text;
99
using System.Threading;
1010
using TSMapEditor.CCEngine;
11+
using TSMapEditor.Misc;
1112
using TSMapEditor.Settings;
1213
using TSMapEditor.UI;
1314

@@ -51,6 +52,7 @@ public GameClass()
5152

5253
Constants.Init();
5354
new UserSettings();
55+
AutosaveTimer.Purge();
5456

5557
graphics = new GraphicsDeviceManager(this);
5658
graphics.HardwareModeSwitch = false;

src/TSMapEditor/UI/UIManager.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -353,9 +353,9 @@ private void InitAutoSaveAndSaveNotifications()
353353
};
354354

355355
map.MapAutoSaved += (s, e) => notificationManager.AddNotification("Map auto-saved.");
356+
map.MapSaveFailed += (s, e) => notificationManager.AddNotification("Saving map failed! Please see the log file for details.");
356357
}
357358

358-
359359
private void RefreshWindowTitle()
360360
{
361361
string baseTitle = "C&C World-Altering Editor (WAE) - {0}";

0 commit comments

Comments
 (0)