rough impl of FSW, goodbye periodic filescan

This commit is contained in:
rootdarkarchon
2024-02-13 00:56:27 +01:00
committed by Loporrit
parent 136257eecf
commit 739c02cf0b
11 changed files with 525 additions and 255 deletions

View File

@@ -2,12 +2,13 @@
using MareSynchronos.MareConfiguration;
using MareSynchronos.Services;
using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
namespace MareSynchronos.FileCache;
public sealed class PeriodicFileScanner : DisposableMediatorSubscriberBase
public sealed class CacheMonitor : DisposableMediatorSubscriberBase
{
private readonly MareConfigService _configService;
private readonly DalamudUtilService _dalamudUtil;
@@ -16,11 +17,10 @@ public sealed class PeriodicFileScanner : DisposableMediatorSubscriberBase
private readonly IpcManager _ipcManager;
private readonly PerformanceCollectorService _performanceCollector;
private long _currentFileProgress = 0;
private bool _fileScanWasRunning = false;
private CancellationTokenSource _scanCancellationTokenSource = new();
private TimeSpan _timeUntilNextScan = TimeSpan.Zero;
private readonly string[] _allowedExtensions = [".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".pbd", ".scd", ".skp", ".shpk"];
public PeriodicFileScanner(ILogger<PeriodicFileScanner> logger, IpcManager ipcManager, MareConfigService configService,
public CacheMonitor(ILogger<CacheMonitor> logger, IpcManager ipcManager, MareConfigService configService,
FileCacheManager fileDbManager, MareMediator mediator, PerformanceCollectorService performanceCollector, DalamudUtilService dalamudUtil,
FileCompactor fileCompactor) : base(logger, mediator)
{
@@ -30,38 +30,287 @@ public sealed class PeriodicFileScanner : DisposableMediatorSubscriberBase
_performanceCollector = performanceCollector;
_dalamudUtil = dalamudUtil;
_fileCompactor = fileCompactor;
Mediator.Subscribe<PenumbraInitializedMessage>(this, (_) => StartScan());
Mediator.Subscribe<PenumbraInitializedMessage>(this, (_) =>
{
StartPenumbraWatcher(_ipcManager.PenumbraModDirectory);
StartMareWatcher(configService.Current.CacheFolder);
InvokeScan();
});
Mediator.Subscribe<HaltScanMessage>(this, (msg) => HaltScan(msg.Source));
Mediator.Subscribe<ResumeScanMessage>(this, (msg) => ResumeScan(msg.Source));
Mediator.Subscribe<SwitchToMainUiMessage>(this, (_) => StartScan());
Mediator.Subscribe<DalamudLoginMessage>(this, (_) => StartScan());
Mediator.Subscribe<DalamudLoginMessage>(this, (_) =>
{
StartMareWatcher(configService.Current.CacheFolder);
StartPenumbraWatcher(_ipcManager.PenumbraModDirectory);
InvokeScan();
});
Mediator.Subscribe<PenumbraDirectoryChangedMessage>(this, (msg) => StartPenumbraWatcher(msg.ModDirectory));
if (_ipcManager.CheckPenumbraApi() && !string.IsNullOrEmpty(_ipcManager.PenumbraModDirectory))
StartPenumbraWatcher(_ipcManager.PenumbraModDirectory);
if (configService.Current.HasValidSetup())
{
StartMareWatcher(configService.Current.CacheFolder);
}
}
public long CurrentFileProgress => _currentFileProgress;
public long FileCacheSize { get; set; }
public ConcurrentDictionary<string, int> HaltScanLocks { get; set; } = new(StringComparer.Ordinal);
public bool IsScanRunning => CurrentFileProgress > 0 || TotalFiles > 0;
public string TimeUntilNextScan => _timeUntilNextScan.ToString(@"mm\:ss");
public long TotalFiles { get; private set; }
public long TotalFilesStorage { get; private set; }
private int TimeBetweenScans => _configService.Current.TimeSpanBetweenScansInSeconds;
public void HaltScan(string source)
{
if (!HaltScanLocks.ContainsKey(source)) HaltScanLocks[source] = 0;
HaltScanLocks[source]++;
}
if (IsScanRunning && HaltScanLocks.Any(f => f.Value > 0))
record WatcherChange(WatcherChangeTypes ChangeType, string? OldPath = null);
private readonly Dictionary<string, WatcherChange> _watcherChanges = new Dictionary<string, WatcherChange>(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, WatcherChange> _mareChanges = new Dictionary<string, WatcherChange>(StringComparer.OrdinalIgnoreCase);
public void StopMonitoring()
{
Logger.LogInformation("Stopping monitoring of Penumbra and Mare storage folders");
MareWatcher?.Dispose();
PenumbraWatcher?.Dispose();
MareWatcher = null;
PenumbraWatcher = null;
}
public void StartMareWatcher(string? marePath)
{
MareWatcher?.Dispose();
if (string.IsNullOrEmpty(marePath))
{
_scanCancellationTokenSource?.Cancel();
_fileScanWasRunning = true;
MareWatcher = null;
Logger.LogWarning("Mare file path is not set, cannot start the FSW for Mare.");
return;
}
RecalculateFileCacheSize();
Logger.LogDebug("Initializing Mare FSW on {path}", marePath);
MareWatcher = new()
{
Path = marePath,
InternalBufferSize = 8388608,
NotifyFilter = NotifyFilters.CreationTime
| NotifyFilters.LastWrite
| NotifyFilters.FileName
| NotifyFilters.DirectoryName
| NotifyFilters.Size,
Filter = "*.*",
IncludeSubdirectories = false
};
MareWatcher.Deleted += MareWatcher_FileChanged;
MareWatcher.Created += MareWatcher_FileChanged;
}
private void MareWatcher_FileChanged(object sender, FileSystemEventArgs e)
{
if (!_allowedExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
lock (_watcherChanges)
{
_mareChanges[e.FullPath] = new(e.ChangeType);
}
_ = MareWatcherExecution();
}
public void StartPenumbraWatcher(string? penumbraPath)
{
PenumbraWatcher?.Dispose();
if (string.IsNullOrEmpty(penumbraPath))
{
PenumbraWatcher = null;
Logger.LogWarning("Penumbra is not connected or the path is not set, cannot start FSW for Penumbra.");
return;
}
Logger.LogDebug("Initializing Penumbra FSW on {path}", penumbraPath);
PenumbraWatcher = new()
{
Path = penumbraPath,
InternalBufferSize = 8388608,
NotifyFilter = NotifyFilters.CreationTime
| NotifyFilters.LastWrite
| NotifyFilters.FileName
| NotifyFilters.DirectoryName
| NotifyFilters.Size,
Filter = "*.*",
IncludeSubdirectories = true
};
PenumbraWatcher.Deleted += Fs_Changed;
PenumbraWatcher.Created += Fs_Changed;
PenumbraWatcher.Changed += Fs_Changed;
PenumbraWatcher.Renamed += Fs_Renamed;
PenumbraWatcher.EnableRaisingEvents = true;
}
private void Fs_Changed(object sender, FileSystemEventArgs e)
{
if (Directory.Exists(e.FullPath)) return;
if (!_allowedExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
if (e.ChangeType is not (WatcherChangeTypes.Changed or WatcherChangeTypes.Deleted or WatcherChangeTypes.Created))
return;
lock (_watcherChanges)
{
_watcherChanges[e.FullPath] = new(e.ChangeType);
}
Logger.LogTrace("FSW {event}: {path}", e.ChangeType, e.FullPath);
_ = PenumbraWatcherExecution();
}
private void Fs_Renamed(object sender, RenamedEventArgs e)
{
if (Directory.Exists(e.FullPath))
{
var directoryFiles = Directory.GetFiles(e.FullPath, "*.*", SearchOption.AllDirectories);
lock (_watcherChanges)
{
foreach (var file in directoryFiles)
{
if (!_allowedExtensions.Any(ext => file.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) continue;
var oldPath = file.Replace(e.FullPath, e.OldFullPath, StringComparison.OrdinalIgnoreCase);
_watcherChanges.Remove(oldPath);
_watcherChanges[file] = new(WatcherChangeTypes.Renamed, oldPath);
Logger.LogTrace("FSW Renamed: {path} -> {new}", oldPath, file);
}
}
}
else
{
if (!_allowedExtensions.Any(ext => e.FullPath.EndsWith(ext, StringComparison.OrdinalIgnoreCase))) return;
lock (_watcherChanges)
{
_watcherChanges.Remove(e.OldFullPath);
_watcherChanges[e.FullPath] = new(WatcherChangeTypes.Renamed, e.OldFullPath);
}
Logger.LogTrace("FSW Renamed: {path} -> {new}", e.OldFullPath, e.FullPath);
}
_ = PenumbraWatcherExecution();
}
private CancellationTokenSource _penumbraFswCts = new();
private CancellationTokenSource _mareFswCts = new();
public FileSystemWatcher? PenumbraWatcher { get; private set; }
public FileSystemWatcher? MareWatcher { get; private set; }
private async Task MareWatcherExecution()
{
_mareFswCts = _mareFswCts.CancelRecreate();
var token = _mareFswCts.Token;
var delay = TimeSpan.FromSeconds(5);
Dictionary<string, WatcherChange> changes;
lock (_mareChanges)
changes = _mareChanges.ToDictionary(t => t.Key, t => t.Value, StringComparer.Ordinal);
try
{
do
{
await Task.Delay(delay, token).ConfigureAwait(false);
} while (HaltScanLocks.Any(f => f.Value > 0));
}
catch (TaskCanceledException)
{
return;
}
lock (_mareChanges)
{
foreach (var key in changes.Keys)
{
_mareChanges.Remove(key);
}
}
_ = RecalculateFileCacheSize();
if (changes.Any(c => c.Value.ChangeType == WatcherChangeTypes.Deleted))
{
var threadCount = Math.Clamp((int)(Environment.ProcessorCount / 2.0f), 2, 8);
Parallel.ForEach(changes, new ParallelOptions()
{
MaxDegreeOfParallelism = threadCount,
},
(change) =>
{
Logger.LogDebug("FSW Change: {change} = {val}", change.Key, change.Value);
_ = _fileDbManager.GetFileCacheByPath(change.Key);
});
_fileDbManager.WriteOutFullCsv();
}
}
public void InvokeScan(bool forced = false)
private async Task PenumbraWatcherExecution()
{
_penumbraFswCts = _penumbraFswCts.CancelRecreate();
var token = _penumbraFswCts.Token;
Dictionary<string, WatcherChange> changes;
lock (_watcherChanges)
changes = _watcherChanges.ToDictionary(t => t.Key, t => t.Value, StringComparer.Ordinal);
var delay = TimeSpan.FromSeconds(10);
try
{
do
{
await Task.Delay(delay, token).ConfigureAwait(false);
} while (HaltScanLocks.Any(f => f.Value > 0));
}
catch (TaskCanceledException)
{
return;
}
lock (_watcherChanges)
{
foreach (var key in changes.Keys)
{
_watcherChanges.Remove(key);
}
}
var threadCount = Math.Clamp((int)(Environment.ProcessorCount / 2.0f), 2, 8);
Parallel.ForEach(changes, new ParallelOptions()
{
MaxDegreeOfParallelism = threadCount,
},
(change) =>
{
Logger.LogDebug("FSW Change: {change} = {val}", change.Key, change.Value);
if (change.Value.ChangeType == WatcherChangeTypes.Deleted)
{
_fileDbManager.GetFileCacheByPath(change.Key);
}
else
{
if (change.Value.OldPath != null) _fileDbManager.GetFileCacheByPath(change.Value.OldPath);
_fileDbManager.CreateFileEntry(change.Key);
}
});
_fileDbManager.WriteOutFullCsv();
}
public void InvokeScan()
{
bool isForced = forced;
bool isForcedFromExternal = forced;
TotalFiles = 0;
_currentFileProgress = 0;
_scanCancellationTokenSource?.Cancel();
@@ -69,56 +318,36 @@ public sealed class PeriodicFileScanner : DisposableMediatorSubscriberBase
var token = _scanCancellationTokenSource.Token;
_ = Task.Run(async () =>
{
while (!token.IsCancellationRequested)
TotalFiles = 0;
_currentFileProgress = 0;
while (_dalamudUtil.IsOnFrameworkThread)
{
while (HaltScanLocks.Any(f => f.Value > 0) || !_ipcManager.CheckPenumbraApi() || _dalamudUtil.IsOnFrameworkThread)
{
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
}
isForced |= RecalculateFileCacheSize();
if (!_configService.Current.FileScanPaused || isForced)
{
isForced = false;
TotalFiles = 0;
_currentFileProgress = 0;
while (_dalamudUtil.IsOnFrameworkThread)
{
Logger.LogWarning("Scanner is on framework, waiting for leaving thread before continuing");
await Task.Delay(250, token).ConfigureAwait(false);
}
Thread scanThread = new(() =>
{
try
{
_performanceCollector.LogPerformance(this, "PeriodicFileScan", () => PeriodicFileScan(isForcedFromExternal, token));
}
catch (Exception ex)
{
Logger.LogError(ex, "Error during Periodic File Scan");
}
})
{
Priority = ThreadPriority.Lowest,
IsBackground = true
};
scanThread.Start();
while (scanThread.IsAlive)
{
await Task.Delay(250).ConfigureAwait(false);
}
if (isForcedFromExternal) isForcedFromExternal = false;
TotalFiles = 0;
_currentFileProgress = 0;
}
_timeUntilNextScan = TimeSpan.FromSeconds(TimeBetweenScans);
while (_timeUntilNextScan.TotalSeconds >= 0 || _dalamudUtil.IsOnFrameworkThread)
{
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
_timeUntilNextScan -= TimeSpan.FromSeconds(1);
}
Logger.LogWarning("Scanner is on framework, waiting for leaving thread before continuing");
await Task.Delay(250, token).ConfigureAwait(false);
}
Thread scanThread = new(() =>
{
try
{
_performanceCollector.LogPerformance(this, "FullFileScan", () => FullFileScan(token));
}
catch (Exception ex)
{
Logger.LogError(ex, "Error during Full File Scan");
}
})
{
Priority = ThreadPriority.Lowest,
IsBackground = true
};
scanThread.Start();
while (scanThread.IsAlive)
{
await Task.Delay(250).ConfigureAwait(false);
}
TotalFiles = 0;
_currentFileProgress = 0;
}, token);
}
@@ -165,28 +394,19 @@ public sealed class PeriodicFileScanner : DisposableMediatorSubscriberBase
HaltScanLocks[source]--;
if (HaltScanLocks[source] < 0) HaltScanLocks[source] = 0;
if (_fileScanWasRunning && HaltScanLocks.All(f => f.Value == 0))
{
_fileScanWasRunning = false;
InvokeScan(forced: true);
}
}
public void StartScan()
{
if (!_ipcManager.Initialized || !_configService.Current.HasValidSetup()) return;
Logger.LogTrace("Penumbra is active, configuration is valid, scan");
InvokeScan(forced: true);
}
protected override void Dispose(bool disposing)
{
base.Dispose(disposing);
_scanCancellationTokenSource?.Cancel();
PenumbraWatcher?.Dispose();
MareWatcher?.Dispose();
_penumbraFswCts?.CancelDispose();
_mareFswCts?.CancelDispose();
}
private void PeriodicFileScan(bool noWaiting, CancellationToken ct)
private void FullFileScan(CancellationToken ct)
{
TotalFiles = 1;
var penumbraDir = _ipcManager.PenumbraModDirectory;
@@ -210,7 +430,6 @@ public sealed class PeriodicFileScanner : DisposableMediatorSubscriberBase
var previousThreadPriority = Thread.CurrentThread.Priority;
Thread.CurrentThread.Priority = ThreadPriority.Lowest;
Logger.LogDebug("Getting files from {penumbra} and {storage}", penumbraDir, _configService.Current.CacheFolder);
string[] ext = [".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".pbd", ".scd", ".skp", ".shpk"];
Dictionary<string, string[]> penumbraFiles = new(StringComparer.Ordinal);
foreach (var folder in Directory.EnumerateDirectories(penumbraDir!))
@@ -221,7 +440,7 @@ public sealed class PeriodicFileScanner : DisposableMediatorSubscriberBase
[
.. Directory.GetFiles(folder, "*.*", SearchOption.AllDirectories)
.AsParallel()
.Where(f => ext.Any(e => f.EndsWith(e, StringComparison.OrdinalIgnoreCase))
.Where(f => _allowedExtensions.Any(e => f.EndsWith(e, StringComparison.OrdinalIgnoreCase))
&& !f.Contains(@"\bg\", StringComparison.OrdinalIgnoreCase)
&& !f.Contains(@"\bgcommon\", StringComparison.OrdinalIgnoreCase)
&& !f.Contains(@"\ui\", StringComparison.OrdinalIgnoreCase)),
@@ -309,7 +528,6 @@ public sealed class PeriodicFileScanner : DisposableMediatorSubscriberBase
Logger.LogWarning(ex, "Failed validating {path}", workload.ResolvedFilepath);
}
Interlocked.Increment(ref _currentFileProgress);
if (!noWaiting) Thread.Sleep(5);
}
Logger.LogTrace("Ending Worker Thread {i}", threadNr);
@@ -390,7 +608,6 @@ public sealed class PeriodicFileScanner : DisposableMediatorSubscriberBase
}
Interlocked.Increment(ref _currentFileProgress);
if (!noWaiting) Thread.Sleep(5);
});
Logger.LogTrace("Scanner added {notScanned} new files to db", allScannedFiles.Count(c => !c.Value));
@@ -406,6 +623,8 @@ public sealed class PeriodicFileScanner : DisposableMediatorSubscriberBase
{
_configService.Current.InitialScanComplete = true;
_configService.Save();
StartMareWatcher(_configService.Current.CacheFolder);
StartPenumbraWatcher(penumbraDir);
}
}
}

View File

@@ -3,6 +3,7 @@ using MareSynchronos.Interop;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Services.Mediator;
using MareSynchronos.Utils;
using Microsoft.Extensions.Hosting;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Globalization;
@@ -10,7 +11,7 @@ using System.Text;
namespace MareSynchronos.FileCache;
public sealed class FileCacheManager : IDisposable
public sealed class FileCacheManager : IHostedService
{
public const string CachePrefix = "{cache}";
public const string CsvSplit = "|";
@@ -30,101 +31,6 @@ public sealed class FileCacheManager : IDisposable
_configService = configService;
_mareMediator = mareMediator;
_csvPath = Path.Combine(configService.ConfigurationDirectory, "FileCache.csv");
lock (_fileWriteLock)
{
try
{
if (File.Exists(CsvBakPath))
{
File.Move(CsvBakPath, _csvPath, overwrite: true);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to move BAK to ORG, deleting BAK");
try
{
if (File.Exists(CsvBakPath))
File.Delete(CsvBakPath);
}
catch (Exception ex1)
{
_logger.LogWarning(ex1, "Could not delete bak file");
}
}
}
if (File.Exists(_csvPath))
{
bool success = false;
string[] entries = [];
int attempts = 0;
while (!success && attempts < 10)
{
try
{
entries = File.ReadAllLines(_csvPath);
success = true;
}
catch (Exception ex)
{
attempts++;
_logger.LogWarning(ex, "Could not open {file}, trying again", _csvPath);
Thread.Sleep(100);
}
}
if (!entries.Any())
{
_logger.LogWarning("Could not load entries from {path}, continuing with empty file cache", _csvPath);
}
Dictionary<string, bool> processedFiles = new(StringComparer.OrdinalIgnoreCase);
foreach (var entry in entries)
{
var splittedEntry = entry.Split(CsvSplit, StringSplitOptions.None);
try
{
var hash = splittedEntry[0];
if (hash.Length != 40) throw new InvalidOperationException("Expected Hash length of 40, received " + hash.Length);
var path = splittedEntry[1];
var time = splittedEntry[2];
if (processedFiles.ContainsKey(path))
{
_logger.LogWarning("Already processed {file}, ignoring", path);
continue;
}
processedFiles.Add(path, value: true);
long size = -1;
long compressed = -1;
if (splittedEntry.Length > 3)
{
if (long.TryParse(splittedEntry[3], CultureInfo.InvariantCulture, out long result))
{
size = result;
}
if (long.TryParse(splittedEntry[4], CultureInfo.InvariantCulture, out long resultCompressed))
{
compressed = resultCompressed;
}
}
AddHashedFile(ReplacePathPrefixes(new FileCacheEntity(hash, path, time, size, compressed)));
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to initialize entry {entry}, ignoring", entry);
}
}
if (processedFiles.Count != entries.Length)
{
WriteOutFullCsv();
}
}
}
private string CsvBakPath => _csvPath + ".bak";
@@ -151,13 +57,6 @@ public sealed class FileCacheManager : IDisposable
return CreateFileCacheEntity(fi, prefixedPath);
}
public void Dispose()
{
_logger.LogTrace("Disposing {type}", GetType());
WriteOutFullCsv();
GC.SuppressFinalize(this);
}
public List<FileCacheEntity> GetAllFileCaches() => _fileCaches.Values.SelectMany(v => v).ToList();
public List<FileCacheEntity> GetAllFileCachesByHash(string hash)
@@ -346,13 +245,14 @@ public sealed class FileCacheManager : IDisposable
public void WriteOutFullCsv()
{
StringBuilder sb = new();
foreach (var entry in _fileCaches.SelectMany(k => k.Value).OrderBy(f => f.PrefixedFilePath, StringComparer.OrdinalIgnoreCase))
{
sb.AppendLine(entry.CsvEntry);
}
lock (_fileWriteLock)
{
StringBuilder sb = new();
foreach (var entry in _fileCaches.SelectMany(k => k.Value).OrderBy(f => f.PrefixedFilePath, StringComparer.OrdinalIgnoreCase))
{
sb.AppendLine(entry.CsvEntry);
}
if (File.Exists(_csvPath))
{
File.Copy(_csvPath, CsvBakPath, overwrite: true);
@@ -458,4 +358,110 @@ public sealed class FileCacheManager : IDisposable
return fileCache;
}
public Task StartAsync(CancellationToken cancellationToken)
{
lock (_fileWriteLock)
{
try
{
if (File.Exists(CsvBakPath))
{
File.Move(CsvBakPath, _csvPath, overwrite: true);
}
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to move BAK to ORG, deleting BAK");
try
{
if (File.Exists(CsvBakPath))
File.Delete(CsvBakPath);
}
catch (Exception ex1)
{
_logger.LogWarning(ex1, "Could not delete bak file");
}
}
}
if (File.Exists(_csvPath))
{
bool success = false;
string[] entries = [];
int attempts = 0;
while (!success && attempts < 10)
{
try
{
entries = File.ReadAllLines(_csvPath);
success = true;
}
catch (Exception ex)
{
attempts++;
_logger.LogWarning(ex, "Could not open {file}, trying again", _csvPath);
Thread.Sleep(100);
}
}
if (!entries.Any())
{
_logger.LogWarning("Could not load entries from {path}, continuing with empty file cache", _csvPath);
}
Dictionary<string, bool> processedFiles = new(StringComparer.OrdinalIgnoreCase);
foreach (var entry in entries)
{
var splittedEntry = entry.Split(CsvSplit, StringSplitOptions.None);
try
{
var hash = splittedEntry[0];
if (hash.Length != 40) throw new InvalidOperationException("Expected Hash length of 40, received " + hash.Length);
var path = splittedEntry[1];
var time = splittedEntry[2];
if (processedFiles.ContainsKey(path))
{
_logger.LogWarning("Already processed {file}, ignoring", path);
continue;
}
processedFiles.Add(path, value: true);
long size = -1;
long compressed = -1;
if (splittedEntry.Length > 3)
{
if (long.TryParse(splittedEntry[3], CultureInfo.InvariantCulture, out long result))
{
size = result;
}
if (long.TryParse(splittedEntry[4], CultureInfo.InvariantCulture, out long resultCompressed))
{
compressed = resultCompressed;
}
}
AddHashedFile(ReplacePathPrefixes(new FileCacheEntity(hash, path, time, size, compressed)));
}
catch (Exception ex)
{
_logger.LogWarning(ex, "Failed to initialize entry {entry}, ignoring", entry);
}
}
if (processedFiles.Count != entries.Length)
{
WriteOutFullCsv();
}
}
return Task.CompletedTask;
}
public Task StopAsync(CancellationToken cancellationToken)
{
WriteOutFullCsv();
return Task.CompletedTask;
}
}