Skip to content

Commit b3f2c6a

Browse files
committed
refactor file comparisons
1 parent f00a443 commit b3f2c6a

File tree

3 files changed

+149
-23
lines changed

3 files changed

+149
-23
lines changed

Services/BackupService.cs

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -228,10 +228,7 @@ private bool CreateBackup(Guid id)
228228

229229
if (Directory.Exists(lastBackupPath))
230230
{
231-
string lastBackupHash = _fileService.GetHash(lastBackupLocation);
232-
string currentHash = _fileService.GetHash(saveGame.SaveLocation);
233-
234-
if (lastBackupHash != currentHash)
231+
if (_fileService.HasChanges(lastBackupLocation, saveGame.SaveLocation))
235232
{
236233
string backupPath = Path.Combine(parentDirectory, DateTime.Now.Ticks.ToString());
237234

Services/FileService.cs

Lines changed: 145 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,112 @@ public FileService(Config config, LoggingService loggingService)
2828

2929
#region Public Methods
3030

31+
public bool HasChanges(string sourcePath, string targetPath)
32+
{
33+
bool hasChanges = false;
34+
35+
if (!File.Exists(sourcePath) && !Directory.Exists(sourcePath))
36+
{
37+
hasChanges = false; // Source doesn't exist, nothing to compare
38+
}
39+
else if (!File.Exists(targetPath) && !Directory.Exists(targetPath))
40+
{
41+
hasChanges = true; // Source exists but target doesn't, this is a change
42+
}
43+
else if (File.Exists(sourcePath) && File.Exists(targetPath))
44+
{
45+
// Both are files - compare sizes first
46+
FileInfo sourceFile = new(sourcePath);
47+
FileInfo targetFile = new(targetPath);
48+
49+
if (sourceFile.Length != targetFile.Length)
50+
{
51+
hasChanges = true;
52+
}
53+
else
54+
{
55+
string sourceHash = GetHash(sourcePath);
56+
string targetHash = GetHash(targetPath);
57+
hasChanges = sourceHash != targetHash;
58+
}
59+
}
60+
else if (Directory.Exists(sourcePath) && Directory.Exists(targetPath))
61+
{
62+
// Both are directories - quick checks first
63+
DirectoryInfo sourceDir = new(sourcePath);
64+
DirectoryInfo targetDir = new(targetPath);
65+
66+
FileInfo[] sourceFiles = sourceDir.GetFiles("*.*", SearchOption.AllDirectories)
67+
.Where(f => f.Name != _config.BackupScreenshotName)
68+
.ToArray();
69+
FileInfo[] targetFiles = targetDir.GetFiles("*.*", SearchOption.AllDirectories)
70+
.Where(f => f.Name != _config.BackupScreenshotName)
71+
.ToArray();
72+
73+
if (sourceFiles.Length != targetFiles.Length)
74+
{
75+
hasChanges = true;
76+
}
77+
else
78+
{
79+
long sourceTotalSize = sourceFiles.Sum(f => f.Length);
80+
long targetTotalSize = targetFiles.Sum(f => f.Length);
81+
82+
if (sourceTotalSize != targetTotalSize)
83+
{
84+
hasChanges = true;
85+
}
86+
else
87+
{
88+
string sourceHash = GetHash(sourcePath);
89+
string targetHash = GetHash(targetPath);
90+
hasChanges = sourceHash != targetHash;
91+
}
92+
}
93+
}
94+
else
95+
{
96+
// One is a file and one is a directory,
97+
// probably need to handle this...
98+
hasChanges = true;
99+
}
100+
101+
return hasChanges;
102+
}
103+
31104
public string GetHash(string path)
32105
{
33106
string hash = String.Empty;
34107
byte[] hashData = null;
35108

36-
using SHA256 mySHA256 = SHA256.Create();
37-
38109
if (Directory.Exists(path))
39110
{
40-
List<byte> allHashes = new();
111+
using MD5 myMD5 = MD5.Create();
112+
myMD5.Initialize();
41113

42114
DirectoryInfo dir = new(path);
43115
FileInfo[] files = dir.GetFiles("*.*", SearchOption.AllDirectories);
44116

117+
// Sort files to ensure deterministic ordering across different file systems
118+
Array.Sort(files, (a, b) => String.Compare(a.FullName, b.FullName, StringComparison.Ordinal));
119+
45120
foreach (FileInfo fileInfo in files)
46121
{
47122
if (fileInfo.Name != _config.BackupScreenshotName)
48123
{
49124
try
50125
{
51-
byte[] fileData = GetFileData(fileInfo.FullName);
52-
byte[] hashValue = mySHA256.ComputeHash(fileData);
53-
allHashes.AddRange(hashValue);
126+
byte[] fileHash = GetFileHash(fileInfo.FullName);
127+
if (fileHash != null)
128+
{
129+
// Use relative path from the base directory for consistency
130+
string relativePath = Path.GetRelativePath(path, fileInfo.FullName);
131+
byte[] pathBytes = System.Text.Encoding.UTF8.GetBytes(relativePath);
132+
myMD5.TransformBlock(pathBytes, 0, pathBytes.Length, null, 0);
133+
134+
// Include file hash
135+
myMD5.TransformBlock(fileHash, 0, fileHash.Length, null, 0);
136+
}
54137
}
55138
catch (Exception e)
56139
{
@@ -59,14 +142,15 @@ public string GetHash(string path)
59142
}
60143
}
61144

62-
hashData = mySHA256.ComputeHash(allHashes.ToArray());
145+
// Finalize the hash
146+
myMD5.TransformFinalBlock(Array.Empty<byte>(), 0, 0);
147+
hashData = myMD5.Hash;
63148
}
64149
else if (File.Exists(path))
65150
{
66151
try
67152
{
68-
byte[] fileData = GetFileData(path);
69-
hashData = mySHA256.ComputeHash(fileData);
153+
hashData = GetFileHash(path);
70154
}
71155
catch (Exception e)
72156
{
@@ -77,34 +161,78 @@ public string GetHash(string path)
77161
return String.Join("", hashData?.Select(x => x.ToString("x2")) ?? []);
78162
}
79163

164+
private byte[] GetFileHash(string filePath)
165+
{
166+
int attempts = 0;
167+
byte[] hashData = null;
168+
Exception lastError = null;
169+
170+
if (File.Exists(filePath))
171+
{
172+
while (hashData == null && attempts < 3)
173+
{
174+
try
175+
{
176+
using FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
177+
using MD5 md5 = MD5.Create();
178+
hashData = md5.ComputeHash(fileStream);
179+
}
180+
catch (Exception e)
181+
{
182+
lastError = e;
183+
attempts++;
184+
if (attempts < 3)
185+
{
186+
// Exponential backoff: 50ms, 100ms, 200ms
187+
int delayMs = 50 * (1 << (attempts - 1));
188+
Thread.Sleep(delayMs);
189+
}
190+
}
191+
}
192+
}
193+
194+
if (hashData == null && lastError != null)
195+
{
196+
_loggingService.LogError($"{nameof(FileService)}>{nameof(GetFileHash)} - {lastError}");
197+
}
198+
199+
return hashData;
200+
}
201+
80202
public byte[] GetFileData(string filePath)
81203
{
82204
int attempts = 0;
83205
byte[] fileData = null;
84-
string error = null;
206+
Exception lastError = null;
85207

86208
if (File.Exists(filePath))
87209
{
88-
while (fileData == null && ++attempts < 3)
210+
while (fileData == null && attempts < 3)
89211
{
90212
try
91213
{
92-
using FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.Read);
214+
using FileStream fileStream = File.Open(filePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite);
93215
using MemoryStream memoryStream = new();
94216
fileStream.CopyTo(memoryStream);
95217
fileData = memoryStream.ToArray();
96218
}
97219
catch (Exception e)
98220
{
99-
error = e.ToString();
100-
Thread.Sleep(100);
221+
lastError = e;
222+
attempts++;
223+
if (attempts < 3)
224+
{
225+
// Exponential backoff: 50ms, 100ms, 200ms
226+
int delayMs = 50 * (1 << (attempts - 1));
227+
Thread.Sleep(delayMs);
228+
}
101229
}
102230
}
103231
}
104232

105-
if (error != null)
233+
if (fileData == null && lastError != null)
106234
{
107-
_loggingService.LogError($"{nameof(FileService)}>{nameof(GetFileData)} - {error}");
235+
_loggingService.LogError($"{nameof(FileService)}>{nameof(GetFileData)} - {lastError}");
108236
}
109237

110238
return fileData;
@@ -145,4 +273,4 @@ public void CopyDirectory(string sourceDir, string destinationDir, bool recursiv
145273
}
146274

147275
#endregion
148-
}
276+
}

src/Components/SaveGame.vue

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,9 @@ const showBackupDialog = (backup) => {
208208
209209
onBeforeMount(async () => {
210210
supressChangeFlag = true;
211+
212+
sortMode.value = localStorage.getItem("backupsSortMode") ?? false;
213+
211214
await getSave();
212215
await getBackups();
213216
selectedBackup.value = null;
@@ -240,8 +243,6 @@ onBeforeMount(async () => {
240243
},
241244
false,
242245
);
243-
244-
sortMode.value = localStorage.getItem("backupsSortMode") ?? false;
245246
});
246247
247248
watch(() => props.id, async () => {

0 commit comments

Comments
 (0)