-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathFileTracker.cs
More file actions
134 lines (123 loc) · 4.98 KB
/
FileTracker.cs
File metadata and controls
134 lines (123 loc) · 4.98 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
using Amethyst.Common.Extensions;
using K4os.Hash.xxHash;
using Newtonsoft.Json;
using System.Reflection;
using System.Text;
namespace Amethyst.Common.Tracking
{
/// <summary>
/// Tracks changes to files.
/// </summary>
public class FileTracker
{
public static Version AssemblyVersion => Assembly.GetEntryAssembly()?.GetName()?.Version ?? new Version(1, 0, 0);
public static ulong CurrentVersion
{
get
{
Version version = AssemblyVersion;
return (ulong)((version.Major << 32) | (version.Minor << 16) | version.Build);
}
}
public DirectoryInfo InputDirectory { get; private set; }
public FileInfo ChecksumFile { get; private set; }
public string[] SearchPatterns { get; private set; }
public string[] Filters { get; private set; }
public FileTracker(DirectoryInfo inputDirectory, FileInfo checksumFile, string[] searchPatterns, string[] filters)
{
ArgumentNullException.ThrowIfNull(inputDirectory);
ArgumentNullException.ThrowIfNull(checksumFile);
ArgumentNullException.ThrowIfNull(searchPatterns);
if (inputDirectory.Exists is false)
throw new DirectoryNotFoundException($"Input directory '{inputDirectory.FullName}' does not exist.");
InputDirectory = inputDirectory;
ChecksumFile = checksumFile;
SearchPatterns = searchPatterns;
Filters = filters;
}
public (FileChange[] Changes, Dictionary<string, ulong> NewChecksums) TrackChanges()
{
List<FileChange> changes = [];
// Load existing checksums
Dictionary<string, ulong> lastChecksums = LoadChecksums();
Dictionary<string, ulong> newChecksums = [];
if (lastChecksums.TryGetValue("__version", out var version))
{
// Version found, check if it matches current version
lastChecksums.Remove("__version");
if (version != CurrentVersion)
{
// Version mismatch, reset checksums
lastChecksums = [];
}
}
else
{
// No version found, assume old format and reset
lastChecksums = [];
}
// Collect all files matching the search patterns
IEnumerable<FileInfo> files = SearchPatterns
.SelectMany(p => InputDirectory.EnumerateFiles(p, SearchOption.AllDirectories))
.Select(f => f);
// Check each file for changes
foreach (var file in files)
{
if (Filters.Any() && !Filters.Any(f => Path.GetRelativePath(InputDirectory.FullName, file.FullName).StartsWith(f)))
continue;
string filePath = file.FullName.NormalizeSlashes();
#if DEBUG
string content = File.ReadAllText(file.FullName);
ulong hash = XXH64.DigestOf(Encoding.UTF8.GetBytes(content));
newChecksums[filePath] = hash;
if (!lastChecksums.TryGetValue(filePath, out var lastHash))
{
changes.Add(new FileChange(ChangeType.Added, filePath));
}
else if (lastHash != hash)
{
changes.Add(new FileChange(ChangeType.Modified, filePath));
}
#else
// In debug mode, treat all files as modified to simplify testing
changes.Add(new FileChange(ChangeType.Modified, filePath));
#endif
}
// Check for deleted files
foreach (var lastFile in lastChecksums.Keys)
{
if (!newChecksums.ContainsKey(lastFile))
{
changes.Add(new FileChange(ChangeType.Deleted, lastFile));
}
}
return (changes.ToArray(), newChecksums);
}
public Dictionary<string, ulong> LoadChecksums()
{
if (!ChecksumFile.Exists)
{
return [];
}
string json = File.ReadAllText(ChecksumFile.FullName);
try
{
return JsonConvert.DeserializeObject<Dictionary<string, ulong>>(json) ?? [];
}
catch
{
return [];
}
}
public void SaveChecksums(Dictionary<string, ulong> checksums)
{
// Always set the current version
checksums["__version"] = CurrentVersion;
var json = JsonConvert.SerializeObject(checksums, Formatting.Indented);
if (ChecksumFile.DirectoryName is not string checksumDirectory)
throw new Exception("Failed to get directory name for checksum file.");
Directory.CreateDirectory(checksumDirectory);
File.WriteAllText(ChecksumFile.FullName, json);
}
}
}