Files
ClubPenguinClient/MareSynchronos/Managers/FileCacheManager.cs
2022-06-22 01:28:42 +02:00

188 lines
7.1 KiB
C#

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Diagnostics;
using System.IO;
using System.Linq;
using System.Runtime.CompilerServices;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Logging;
using MareSynchronos.Factories;
using MareSynchronos.FileCacheDB;
namespace MareSynchronos.Managers
{
internal class FileCacheManager : IDisposable
{
private const int MinutesForScan = 10;
private readonly FileCacheFactory _fileCacheFactory;
private readonly IpcManager _ipcManager;
private readonly Configuration _pluginConfiguration;
private CancellationTokenSource? _scanCancellationTokenSource;
private System.Timers.Timer? _scanScheduler;
private Task? _scanTask;
private Stopwatch? _timerStopWatch;
public FileCacheManager(FileCacheFactory fileCacheFactory, IpcManager ipcManager, Configuration pluginConfiguration)
{
_fileCacheFactory = fileCacheFactory;
_ipcManager = ipcManager;
_pluginConfiguration = pluginConfiguration;
if (_ipcManager.CheckPenumbraApi()
&& _pluginConfiguration.AcceptedAgreement
&& !string.IsNullOrEmpty(_pluginConfiguration.CacheFolder)
&& _pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri)
&& !string.IsNullOrEmpty(_ipcManager.PenumbraModDirectory()))
{
StartInitialScan();
}
}
public long CurrentFileProgress { get; private set; }
public bool IsScanRunning => !_scanTask?.IsCompleted ?? false;
public TimeSpan TimeToNextScan => TimeSpan.FromMinutes(MinutesForScan).Subtract(_timerStopWatch?.Elapsed ?? TimeSpan.FromMinutes(MinutesForScan));
public long TotalFiles { get; private set; }
public void Dispose()
{
PluginLog.Debug("Disposing File Cache Manager");
_scanScheduler?.Stop();
_scanCancellationTokenSource?.Cancel();
}
public void StartInitialScan()
{
_scanCancellationTokenSource = new CancellationTokenSource();
_scanTask = Task.Run(() => StartFileScan(_scanCancellationTokenSource.Token));
}
private async Task StartFileScan(CancellationToken ct)
{
_scanCancellationTokenSource = new CancellationTokenSource();
var penumbraDir = _ipcManager.PenumbraModDirectory()!;
PluginLog.Debug("Getting files from " + penumbraDir);
var scannedFiles = new ConcurrentDictionary<string, bool>(
Directory.EnumerateFiles(penumbraDir, "*.*", SearchOption.AllDirectories)
.Select(s => s.ToLowerInvariant())
.Where(f => f.Contains(@"\chara\") && (f.EndsWith(".tex") || f.EndsWith(".mdl") || f.EndsWith(".mtrl")))
.Select(p => new KeyValuePair<string, bool>(p, false)));
List<FileCache> fileCaches;
await using (FileCacheContext db = new())
{
fileCaches = db.FileCaches.ToList();
}
TotalFiles = scannedFiles.Count;
var fileCachesToUpdate = new ConcurrentBag<FileCache>();
var fileCachesToDelete = new ConcurrentBag<FileCache>();
var fileCachesToAdd = new ConcurrentBag<FileCache>();
PluginLog.Debug("Getting file list from Database");
// scan files from database
Parallel.ForEach(fileCaches, new ParallelOptions()
{
MaxDegreeOfParallelism = _pluginConfiguration.MaxParallelScan,
CancellationToken = ct,
},
cache =>
{
if (ct.IsCancellationRequested) return;
if (!File.Exists(cache.Filepath))
{
fileCachesToDelete.Add(cache);
}
else
{
if (scannedFiles.ContainsKey(cache.Filepath))
{
scannedFiles[cache.Filepath] = true;
}
FileInfo fileInfo = new(cache.Filepath);
if (fileInfo.LastWriteTimeUtc.Ticks == long.Parse(cache.LastModifiedDate)) return;
_fileCacheFactory.UpdateFileCache(cache);
fileCachesToUpdate.Add(cache);
}
var files = CurrentFileProgress;
Interlocked.Increment(ref files);
CurrentFileProgress = files;
});
if (ct.IsCancellationRequested) return;
// scan new files
Parallel.ForEach(scannedFiles.Where(c => c.Value == false), new ParallelOptions()
{
MaxDegreeOfParallelism = _pluginConfiguration.MaxParallelScan,
CancellationToken = ct
},
file =>
{
fileCachesToAdd.Add(_fileCacheFactory.Create(file.Key));
var files = CurrentFileProgress;
Interlocked.Increment(ref files);
CurrentFileProgress = files;
});
await using (FileCacheContext db = new())
{
if (fileCachesToAdd.Any() || fileCachesToUpdate.Any() || fileCachesToDelete.Any())
{
db.FileCaches.AddRange(fileCachesToAdd);
db.FileCaches.UpdateRange(fileCachesToUpdate);
db.FileCaches.RemoveRange(fileCachesToDelete);
await db.SaveChangesAsync(ct);
}
}
PluginLog.Debug("Scan complete");
TotalFiles = 0;
CurrentFileProgress = 0;
if (!_pluginConfiguration.InitialScanComplete)
{
_pluginConfiguration.InitialScanComplete = true;
_pluginConfiguration.Save();
_timerStopWatch = Stopwatch.StartNew();
StartScheduler();
}
else if (_timerStopWatch == null)
{
StartScheduler();
_timerStopWatch = Stopwatch.StartNew();
}
}
private void StartScheduler()
{
PluginLog.Debug("Scheduling next scan for in " + MinutesForScan + " minutes");
_scanScheduler = new System.Timers.Timer(TimeSpan.FromMinutes(MinutesForScan).TotalMilliseconds)
{
AutoReset = false,
Enabled = false,
};
_scanScheduler.AutoReset = true;
_scanScheduler.Elapsed += (_, _) =>
{
_timerStopWatch?.Stop();
if (_scanTask?.IsCompleted ?? false)
{
PluginLog.Warning("Scanning task is still running, not re-initiating.");
return;
}
PluginLog.Debug("Initiating periodic scan for mod changes");
Task.Run(() => _scanTask = StartFileScan(_scanCancellationTokenSource!.Token));
_timerStopWatch = Stopwatch.StartNew();
};
_scanScheduler.Start();
}
}
}