Files
ClubPenguinClient/MareSynchronos/FileCache/FileCacheManager.cs
rootdarkarchon b2276a1883 Client rework for API change and paradigm shift (#39)
* most of the groups refactoring on client

* register OnMethods for group stuff

* start implementing client (still pretty broken)

* finish implementing new api first iteration

* idk rework everything for pair shit (still WIP); goal is to remove PairedClients and GroupPairClients from ApiController

* move everything to PairManager, remove dictionaries from APiController

* remove admin stuff from client, cleanup

* adjust reconnection handling, add new settings, todo still to remove access from old stuff that's marked obsolete from config

* add back adding servers, fix intro ui

* fix obsolete calls

* adjust config namespace

* add UI for setting animation/sound permissions to syncshells

* add ConfigurationService to hot reload config on change from external

* move transient data cache to configuration

* add deleting service to ui

* fix saving of transient resources

* fix group pair user assignments

* halt scanner when penumbra inactive, add visible/online/offline split to individual pairs and tags

* add presence to syncshell ui

* move fullpause from config to server config

* fixes in code style

* more codestyle

* show info icon on player in shells, don't show icon when no changes from default state are made, add online notifs

* fixes to intro UI

---------

Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
2023-01-29 15:13:53 +01:00

226 lines
8.4 KiB
C#

using MareSynchronos.Managers;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Utils;
using System.Collections.Concurrent;
using System.Globalization;
using System.Text;
namespace MareSynchronos.FileCache;
public class FileCacheManager : IDisposable
{
private const string _penumbraPrefix = "{penumbra}";
private const string _cachePrefix = "{cache}";
private readonly IpcManager _ipcManager;
private readonly ConfigurationService _configService;
private readonly string _csvPath;
private string CsvBakPath => _csvPath + ".bak";
private readonly ConcurrentDictionary<string, FileCacheEntity> _fileCaches = new(StringComparer.Ordinal);
public const string CsvSplit = "|";
private readonly object _fileWriteLock = new();
public FileCacheManager(IpcManager ipcManager, ConfigurationService configService)
{
_ipcManager = ipcManager;
_configService = configService;
_csvPath = Path.Combine(configService.ConfigurationDirectory, "FileCache.csv");
lock (_fileWriteLock)
{
if (File.Exists(CsvBakPath))
{
File.Move(CsvBakPath, _csvPath, overwrite: true);
}
}
if (File.Exists(_csvPath))
{
var entries = File.ReadAllLines(_csvPath);
foreach (var entry in entries)
{
var splittedEntry = entry.Split(CsvSplit, StringSplitOptions.None);
try
{
var hash = splittedEntry[0];
var path = splittedEntry[1];
var time = splittedEntry[2];
_fileCaches[path] = ReplacePathPrefixes(new FileCacheEntity(hash, path, time));
}
catch (Exception)
{
Logger.Warn($"Failed to initialize entry {entry}, ignoring");
}
}
}
}
public void WriteOutFullCsv()
{
StringBuilder sb = new();
foreach (var entry in _fileCaches.OrderBy(f => f.Value.PrefixedFilePath, StringComparer.OrdinalIgnoreCase))
{
sb.AppendLine(entry.Value.CsvEntry);
}
if (File.Exists(_csvPath))
{
File.Copy(_csvPath, CsvBakPath, overwrite: true);
}
lock (_fileWriteLock)
{
try
{
File.WriteAllText(_csvPath, sb.ToString());
File.Delete(CsvBakPath);
}
catch
{
File.WriteAllText(CsvBakPath, sb.ToString());
}
}
}
public List<FileCacheEntity> GetAllFileCaches() => _fileCaches.Values.ToList();
public FileCacheEntity? GetFileCacheByHash(string hash)
{
if (_fileCaches.Any(f => string.Equals(f.Value.Hash, hash, StringComparison.Ordinal)))
{
return GetValidatedFileCache(_fileCaches.FirstOrDefault(f => string.Equals(f.Value.Hash, hash, StringComparison.Ordinal)).Value);
}
return null;
}
public (FileState, FileCacheEntity) ValidateFileCacheEntity(FileCacheEntity fileCache)
{
fileCache = ReplacePathPrefixes(fileCache);
FileInfo fi = new(fileCache.ResolvedFilepath);
if (!fi.Exists)
{
return (FileState.RequireDeletion, fileCache);
}
if (!string.Equals(fi.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture), fileCache.LastModifiedDateTicks, StringComparison.Ordinal))
{
return (FileState.RequireUpdate, fileCache);
}
return (FileState.Valid, fileCache);
}
public FileCacheEntity? GetFileCacheByPath(string path)
{
var cleanedPath = path.Replace("/", "\\", StringComparison.OrdinalIgnoreCase).ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), "", StringComparison.OrdinalIgnoreCase);
var entry = _fileCaches.FirstOrDefault(f => f.Value.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.OrdinalIgnoreCase)).Value;
if (entry == null)
{
Logger.Debug("Found no entries for " + cleanedPath);
return CreateFileEntry(path);
}
var validatedCacheEntry = GetValidatedFileCache(entry);
return validatedCacheEntry;
}
public FileCacheEntity? CreateCacheEntry(string path)
{
Logger.Verbose("Creating cache entry for " + path);
FileInfo fi = new(path);
if (!fi.Exists) return null;
var fullName = fi.FullName.ToLowerInvariant();
if (!fullName.Contains(_configService.Current.CacheFolder.ToLowerInvariant(), StringComparison.Ordinal)) return null;
string prefixedPath = fullName.Replace(_configService.Current.CacheFolder.ToLowerInvariant(), _cachePrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
return CreateFileCacheEntity(fi, prefixedPath, fi.Name.ToUpper(CultureInfo.InvariantCulture));
}
public FileCacheEntity? CreateFileEntry(string path)
{
Logger.Verbose("Creating file entry for " + path);
FileInfo fi = new(path);
if (!fi.Exists) return null;
var fullName = fi.FullName.ToLowerInvariant();
if (!fullName.Contains(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), StringComparison.Ordinal)) return null;
string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), _penumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
return CreateFileCacheEntity(fi, prefixedPath);
}
private FileCacheEntity? CreateFileCacheEntity(FileInfo fileInfo, string prefixedPath, string? hash = null)
{
hash ??= Crypto.GetFileHash(fileInfo.FullName);
var entity = new FileCacheEntity(hash, prefixedPath, fileInfo.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture));
entity = ReplacePathPrefixes(entity);
_fileCaches[prefixedPath] = entity;
lock (_fileWriteLock)
{
File.AppendAllLines(_csvPath, new[] { entity.CsvEntry });
}
var result = GetFileCacheByPath(fileInfo.FullName);
Logger.Debug("Creating file cache for " + fileInfo.FullName + " success: " + (result != null));
return result;
}
private FileCacheEntity? GetValidatedFileCache(FileCacheEntity fileCache)
{
var resulingFileCache = ReplacePathPrefixes(fileCache);
resulingFileCache = Validate(resulingFileCache);
return resulingFileCache;
}
private FileCacheEntity? Validate(FileCacheEntity fileCache)
{
var file = new FileInfo(fileCache.ResolvedFilepath);
if (!file.Exists)
{
_fileCaches.Remove(fileCache.PrefixedFilePath, out _);
return null;
}
if (!string.Equals(file.LastWriteTimeUtc.Ticks.ToString(), fileCache.LastModifiedDateTicks, StringComparison.Ordinal))
{
UpdateHash(fileCache);
}
return fileCache;
}
public void RemoveHash(FileCacheEntity entity)
{
Logger.Verbose("Removing " + entity.ResolvedFilepath);
_fileCaches.Remove(entity.PrefixedFilePath, out _);
}
public void UpdateHash(FileCacheEntity fileCache)
{
Logger.Verbose("Updating hash for " + fileCache.ResolvedFilepath);
fileCache.Hash = Crypto.GetFileHash(fileCache.ResolvedFilepath);
fileCache.LastModifiedDateTicks = new FileInfo(fileCache.ResolvedFilepath).LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture);
_fileCaches.Remove(fileCache.PrefixedFilePath, out _);
_fileCaches[fileCache.PrefixedFilePath] = fileCache;
}
private FileCacheEntity ReplacePathPrefixes(FileCacheEntity fileCache)
{
if (fileCache.PrefixedFilePath.StartsWith(_penumbraPrefix, StringComparison.OrdinalIgnoreCase))
{
fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(_penumbraPrefix, _ipcManager.PenumbraModDirectory(), StringComparison.Ordinal));
}
else if (fileCache.PrefixedFilePath.StartsWith(_cachePrefix, StringComparison.OrdinalIgnoreCase))
{
fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(_cachePrefix, _configService.Current.CacheFolder, StringComparison.Ordinal));
}
return fileCache;
}
public string ResolveFileReplacement(string gamePath)
{
return _ipcManager.PenumbraResolvePath(gamePath);
}
public void Dispose()
{
WriteOutFullCsv();
}
}