From dd53bc547f3cac1a9cd74aef084f73b437563b91 Mon Sep 17 00:00:00 2001 From: Huy Nguyen Date: Sat, 13 Dec 2025 10:38:43 +1030 Subject: [PATCH] When a file is locked during hash calculation, if retries fails then do not throw exception out but rather return null hash --- TinfoilVibeServer/Services/SnapshotService.cs | 47 ++++++++++++++----- 1 file changed, 35 insertions(+), 12 deletions(-) diff --git a/TinfoilVibeServer/Services/SnapshotService.cs b/TinfoilVibeServer/Services/SnapshotService.cs index 266499b..281993e 100644 --- a/TinfoilVibeServer/Services/SnapshotService.cs +++ b/TinfoilVibeServer/Services/SnapshotService.cs @@ -221,8 +221,23 @@ public sealed class SnapshotService : IDisposable, ISnapshotService, IHostedServ _watchers.Remove(fileSystemWatchers); } - private void OnChanged(object? _, FileSystemEventArgs e) => ThrottleSnapshotUpdate(e); - private void OnRenamed(object? _, RenamedEventArgs e) => ThrottleSnapshotUpdate(e); + private void OnChanged(object? _, FileSystemEventArgs e) + { + var fileInfo = new FileInfo(e.FullPath); + if (_options.ArchiveExtensions.Contains(fileInfo.Extension) || _options.RomExtensions.Contains(fileInfo.Extension)) + { + ThrottleSnapshotUpdate(e); + } + } + + private void OnRenamed(object? _, RenamedEventArgs e) + { + var fileInfo = new FileInfo(e.FullPath); + if (_options.ArchiveExtensions.Contains(fileInfo.Extension) || _options.RomExtensions.Contains(fileInfo.Extension)) + { + ThrottleSnapshotUpdate(e); + } + } /// /// Rebuild the snapshot, if rebuild in process, cancel it and restart @@ -421,7 +436,7 @@ public sealed class SnapshotService : IDisposable, ISnapshotService, IHostedServ return fileInfo.LastWriteTimeUtc; })) { - string hash; + string? hash; var ext = Path.GetExtension(file).ToLowerInvariant(); if (!(_options.ArchiveExtensions.Contains(ext) || _options.RomExtensions.Contains(ext))) @@ -474,12 +489,15 @@ public sealed class SnapshotService : IDisposable, ISnapshotService, IHostedServ Stopwatch stopwatch = Stopwatch.StartNew(); hash = ComputeFirstStreamHashAsync(file, cancellationToken).Result; stopwatch.Stop(); - _logger.LogDebug("Computed hash for {File} in {Time}ms", file, stopwatch.ElapsedMilliseconds); - if (_hashCache.TryGetValue(hash, out var value) && file == _cache[value].Path) + if (!string.IsNullOrEmpty(hash)) { - cancellationToken.ThrowIfCancellationRequested(); - yield return null; - continue; + _logger.LogDebug("Computed hash for {File} in {Time}ms", file, stopwatch.ElapsedMilliseconds); + if (_hashCache.TryGetValue(hash, out var value) && file == _cache[value].Path) + { + cancellationToken.ThrowIfCancellationRequested(); + yield return null; + continue; + } } IEnumerable<(string, long, NcaMetadataWithHash)>? titlesEnumerable = null; @@ -572,7 +590,7 @@ public sealed class SnapshotService : IDisposable, ISnapshotService, IHostedServ { if (_debouncerCache.TryGetValue(_jsonPath, out _)) { - _logger.LogInformation("Sliding debounce in progress, skipping snapshot persistence"); + _logger.LogDebug("Sliding debounce in progress, skipping snapshot persistence"); return Task.CompletedTask; } @@ -821,7 +839,7 @@ public sealed class SnapshotService : IDisposable, ISnapshotService, IHostedServ // File: TinfoilVibeServer/Services/SnapshotService.cs (inside SnapshotService class) - private async Task ComputeFirstStreamHashAsync(string filePath, CancellationToken cancellationToken = default) + private async Task ComputeFirstStreamHashAsync(string filePath, CancellationToken cancellationToken = default) { cancellationToken.ThrowIfCancellationRequested(); for (var attempt = 0; attempt < _options.MaxRetryCount; attempt++) @@ -832,7 +850,7 @@ public sealed class SnapshotService : IDisposable, ISnapshotService, IHostedServ { throw new IOException("File is locked"); } - + // Only treat NSP/XCI/XCZ as “first‑stream” files var ext = Path.GetExtension(filePath).ToLowerInvariant(); if (ext is not ".nsp" and not ".xci" and not ".xcz") @@ -868,11 +886,16 @@ public sealed class SnapshotService : IDisposable, ISnapshotService, IHostedServ } catch (IOException ex) when (attempt < _options.MaxRetryCount - 1) { - var delay = (int)((attempt+1) * _options.DebounceTimeoutMs * _options.RetryMultiplier); + var delay = (int)((attempt + 1) * _options.DebounceTimeoutMs * _options.RetryMultiplier); _logger.LogWarning(ex, "Attempt {Attempt} failed for {Path}. Retrying after {Delay}.", attempt + 1, filePath, delay); await Task.Delay(delay, cancellationToken); } + catch (IOException) + { + _logger.LogWarning("Attempt to load {Path} failed after {retries}", filePath, attempt + 1); + return null; + } } return string.Empty; -- 2.52.0