Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
58 changes: 57 additions & 1 deletion grzyClothTool/Helpers/SaveHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -150,7 +150,7 @@ public static async Task SaveAsync()

var saveFileName = GetSaveFileName(isExternalProject);
var autoSavePath = Path.Combine(projectFolder, saveFileName);
await File.WriteAllTextAsync(autoSavePath, json);
await WriteAutoSaveWithBackupAsync(autoSavePath, json);

LogHelper.Log($"Auto-saved to {autoSavePath} in {timer.ElapsedMilliseconds}ms");
}
Expand All @@ -173,6 +173,62 @@ public static async Task SaveAsync()
}
}

private static async Task WriteAutoSaveWithBackupAsync(string autoSavePath, string json)
{
var directory = Path.GetDirectoryName(autoSavePath);
if (string.IsNullOrWhiteSpace(directory))
{
throw new InvalidOperationException("Auto-save path directory is invalid.");
}

var fileName = Path.GetFileName(autoSavePath);
var tempPath = Path.Combine(directory, $"{fileName}.{Guid.NewGuid():N}.tmp");
var backupPath = $"{autoSavePath}.bak";
var hadOriginal = false;

try
{
await File.WriteAllTextAsync(tempPath, json);

if (File.Exists(autoSavePath))
{
hadOriginal = true;

if (File.Exists(backupPath))
{
File.Delete(backupPath);
}

File.Move(autoSavePath, backupPath);
}

// Atomic rename in same directory.
File.Move(tempPath, autoSavePath, overwrite: true);
}
catch
{
if (File.Exists(tempPath))
{
File.Delete(tempPath);
}

// If rotation already happened and new write failed, restore the previous autosave.
if (hadOriginal && !File.Exists(autoSavePath) && File.Exists(backupPath))
{
File.Move(backupPath, autoSavePath, overwrite: true);
}

throw;
}
finally
{
if (File.Exists(tempPath))
{
File.Delete(tempPath);
}
}
}

public static void SetUnsavedChanges(bool status)
{
HasUnsavedChanges = status;
Expand Down