Compiles but runs strange
This commit is contained in:
@@ -1,15 +1,13 @@
|
||||
using System.Collections.Concurrent;
|
||||
using System.Security.Cryptography;
|
||||
using System.Text.Json;
|
||||
using FileSnapshot;
|
||||
using TinfoilVibeServer.Models;
|
||||
|
||||
namespace TinfoilVibeServer.Services;
|
||||
|
||||
/// <summary>
|
||||
/// Keeps an in‑memory snapshot, watches the filesystem for changes, and
|
||||
/// only re‑processes a file if its hash changed. The snapshot is
|
||||
/// automatically re‑generated when the configuration changes.
|
||||
/// only re‑processes a file if its hash changed.
|
||||
/// </summary>
|
||||
public sealed class SnapshotService : IDisposable
|
||||
{
|
||||
@@ -17,67 +15,47 @@ public sealed class SnapshotService : IDisposable
|
||||
private readonly string _jsonPath;
|
||||
private readonly string _snapshotPath;
|
||||
private readonly FileSystemWatcher _watcher;
|
||||
|
||||
// path -> CachedFile
|
||||
private readonly ConcurrentDictionary<string, CachedFile> _cache = new();
|
||||
|
||||
private string? _currentSnapshotHash;
|
||||
|
||||
public SnapshotService(ConfigManager config)
|
||||
{
|
||||
_config = config;
|
||||
_jsonPath = Path.Combine(AppContext.BaseDirectory, config.Settings.SnapshotFile);
|
||||
_snapshotPath = Path.Combine(AppContext.BaseDirectory, config.Settings.SnapshotBackupFile);
|
||||
_jsonPath = Path.Combine(AppContext.BaseDirectory, _config.Settings.SnapshotFile);
|
||||
_snapshotPath = Path.Combine(AppContext.BaseDirectory, _config.Settings.SnapshotBackupFile);
|
||||
|
||||
// Initial snapshot
|
||||
BuildSnapshot();
|
||||
|
||||
// Persist a copy for quick load on next run
|
||||
BuildSnapshot(); // initial scan
|
||||
File.WriteAllText(_snapshotPath, JsonSerializer.Serialize(GetSnapshot()));
|
||||
|
||||
// File system watcher
|
||||
_watcher = new FileSystemWatcher
|
||||
{
|
||||
Path = string.Join(Path.PathSeparator, config.Settings.RootDirectories),
|
||||
Path = string.Join(Path.PathSeparator, _config.Settings.RootDirectories),
|
||||
IncludeSubdirectories = true,
|
||||
NotifyFilter = NotifyFilters.FileName | NotifyFilters.DirectoryName |
|
||||
NotifyFilters.Size | NotifyFilters.LastWrite
|
||||
};
|
||||
|
||||
_watcher.Created += OnChanged;
|
||||
_watcher.Changed += OnChanged;
|
||||
_watcher.Deleted += OnChanged;
|
||||
_watcher.Renamed += OnRenamed;
|
||||
_watcher.EnableRaisingEvents = true;
|
||||
|
||||
// React to config changes
|
||||
_config.OnChange += cfg =>
|
||||
{
|
||||
// Re‑initialise the watcher with the new root directories
|
||||
_watcher.Path = string.Join(Path.PathSeparator, cfg.RootDirectories);
|
||||
_watcher.EnableRaisingEvents = true;
|
||||
BuildSnapshot(); // rebuild everything
|
||||
BuildSnapshot(); // rebuild everything
|
||||
PersistSnapshot();
|
||||
};
|
||||
}
|
||||
|
||||
private sealed record CachedFile(string Path, string Hash, TitleInfo? Title);
|
||||
#region FileSystemWatcher
|
||||
|
||||
#region File system change handlers
|
||||
|
||||
private void OnChanged(object? _, FileSystemEventArgs e) =>
|
||||
ThrottleSnapshotUpdate();
|
||||
|
||||
private void OnRenamed(object? _, RenamedEventArgs e)
|
||||
{
|
||||
// Treat rename as delete + create
|
||||
OnChanged(_, new FileSystemEventArgs(WatcherChangeTypes.Deleted, e.OldFullPath, e.OldName));
|
||||
OnChanged(_, new FileSystemEventArgs(WatcherChangeTypes.Created, e.FullPath, e.Name));
|
||||
}
|
||||
private void OnChanged(object? _, FileSystemEventArgs e) => ThrottleSnapshotUpdate();
|
||||
private void OnRenamed(object? _, RenamedEventArgs e) => ThrottleSnapshotUpdate();
|
||||
|
||||
private void ThrottleSnapshotUpdate()
|
||||
{
|
||||
// Debounce: only trigger once in a short window
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await Task.Delay(250);
|
||||
@@ -87,9 +65,8 @@ public sealed class SnapshotService : IDisposable
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// Full rebuild – called on start‑up and on config change.
|
||||
/// </summary>
|
||||
#region Snapshot logic
|
||||
|
||||
private void BuildSnapshot()
|
||||
{
|
||||
var cfg = _config.Settings;
|
||||
@@ -104,58 +81,33 @@ public sealed class SnapshotService : IDisposable
|
||||
if (!(cfg.WhitelistExtensions.Contains(ext) || cfg.RomExtensions.Contains(ext)))
|
||||
continue;
|
||||
|
||||
// 1) compute hash
|
||||
var hash = ComputeHash(file);
|
||||
|
||||
// 2) decide if we need to re‑process
|
||||
// Cache hit?
|
||||
if (_cache.TryGetValue(file, out var cached) && cached.Hash == hash)
|
||||
{
|
||||
// nothing changed – use cached title info
|
||||
entries.Add(new FileEntry(file, new FileInfo(file).Length, hash, cached.Title));
|
||||
continue;
|
||||
}
|
||||
|
||||
// 3) extract title if applicable
|
||||
TitleInfo? title = null;
|
||||
// Extract title if possible
|
||||
NcaMetadataDto? title = null;
|
||||
if (cfg.RomExtensions.Contains(ext))
|
||||
{
|
||||
title = NSPExtactor.ExtractFromFile(file);
|
||||
}
|
||||
else
|
||||
{
|
||||
title = ArchiveHandler.TryExtractTitleInfo(file);
|
||||
}
|
||||
|
||||
// 4) update cache
|
||||
_cache[file] = new CachedFile(file, hash, title);
|
||||
|
||||
// 5) add to snapshot
|
||||
entries.Add(new FileEntry(file, new FileInfo(file).Length, hash, title));
|
||||
}
|
||||
}
|
||||
|
||||
// Replace the entire snapshot
|
||||
lock (_cache)
|
||||
{
|
||||
// the snapshot itself is not stored in _cache – it's only used for the JSON
|
||||
}
|
||||
// we keep entries in a local variable for now
|
||||
_currentSnapshotHash = ComputeSnapshotHash(entries);
|
||||
File.WriteAllText(_jsonPath, JsonSerializer.Serialize(entries));
|
||||
}
|
||||
|
||||
private static string ComputeSnapshotHash(IEnumerable<FileEntry> entries)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(entries);
|
||||
using var sha256 = SHA256.Create();
|
||||
return BitConverter.ToString(sha256.ComputeHash(System.Text.Encoding.UTF8.GetBytes(json))).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
private void UpdateSnapshot()
|
||||
{
|
||||
BuildSnapshot();
|
||||
PersistSnapshot();
|
||||
}
|
||||
private void UpdateSnapshot() => BuildSnapshot();
|
||||
|
||||
private void PersistSnapshot()
|
||||
{
|
||||
@@ -171,21 +123,35 @@ public sealed class SnapshotService : IDisposable
|
||||
|
||||
private static string ComputeHash(string filePath)
|
||||
{
|
||||
using var sha256 = SHA256.Create();
|
||||
using var sha = SHA256.Create();
|
||||
using var stream = File.OpenRead(filePath);
|
||||
var hash = sha256.ComputeHash(stream);
|
||||
var hash = sha.ComputeHash(stream);
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
private static string ComputeSnapshotHash(IEnumerable<FileEntry> entries)
|
||||
{
|
||||
var json = JsonSerializer.Serialize(entries);
|
||||
using var sha = SHA256.Create();
|
||||
var hash = sha.ComputeHash(System.Text.Encoding.UTF8.GetBytes(json));
|
||||
return BitConverter.ToString(hash).Replace("-", "").ToLowerInvariant();
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
public IReadOnlyList<FileEntry> GetSnapshot()
|
||||
{
|
||||
var json = File.ReadAllText(_jsonPath);
|
||||
return JsonSerializer.Deserialize<IReadOnlyList<FileEntry>>(json)!;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
public void RebuildSnapshot()
|
||||
{
|
||||
_watcher.Dispose();
|
||||
_config.Dispose();
|
||||
// Build a fresh snapshot and persist it.
|
||||
BuildSnapshot(); // private method inside the same class
|
||||
PersistSnapshot(); // private method inside the same class
|
||||
}
|
||||
|
||||
public void Dispose() => _watcher.Dispose();
|
||||
|
||||
private sealed record CachedFile(string Path, string Hash, NcaMetadataDto? Title);
|
||||
}
|
||||
Reference in New Issue
Block a user