using System.Text.Json; namespace ClinicBackupSync; sealed class UploadStateStore { readonly Lock _gate = new(); readonly Dictionary _records = new(StringComparer.OrdinalIgnoreCase); public UploadStateStore() { Load(); } public bool IsUploaded(FileInfo file) { var known = Find(file); return known is { SameVersion: true }; } public KnownUpload? Find(FileInfo file) { lock (_gate) { if (!_records.TryGetValue(file.FullName, out var record)) { return null; } var uploadedAt = DateTime.SpecifyKind(DateTime.UnixEpoch, DateTimeKind.Utc); if (DateTime.TryParse( record.UploadedAt, System.Globalization.CultureInfo.InvariantCulture, System.Globalization.DateTimeStyles.RoundtripKind, out var parsed)) { uploadedAt = parsed.ToUniversalTime(); } var sameVersion = record.Length == file.Length && record.LastWriteTicks == file.LastWriteTimeUtc.Ticks; return new KnownUpload(uploadedAt, sameVersion); } } public void MarkUploaded(FileInfo file) { lock (_gate) { _records[file.FullName] = new UploadRecord( file.FullName, file.Length, file.LastWriteTimeUtc.Ticks, DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ssZ")); Save(); } } void Load() { if (!File.Exists(AppPaths.StateFile)) { return; } try { var json = File.ReadAllText(AppPaths.StateFile); var records = JsonSerializer.Deserialize>(json); if (records is null) { return; } foreach (var record in records) { if (!string.IsNullOrWhiteSpace(record.Path)) { _records[record.Path] = record; } } } catch (Exception ex) when (ex is IOException or JsonException or UnauthorizedAccessException) { AppLog.Write($"Could not read upload history: {ex.Message}"); } } void Save() { Directory.CreateDirectory(AppPaths.DataDirectory); var json = JsonSerializer.Serialize(_records.Values.ToList(), new JsonSerializerOptions { WriteIndented = true }); var temporary = AppPaths.StateFile + ".tmp"; File.WriteAllText(temporary, json); File.Move(temporary, AppPaths.StateFile, overwrite: true); } sealed record UploadRecord(string Path, long Length, long LastWriteTicks, string UploadedAt); } readonly record struct KnownUpload(DateTime UploadedAtUtc, bool SameVersion);