 b2276a1883
			
		
	
	b2276a1883
	
	
	
		
			
			* 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>
		
			
				
	
	
		
			226 lines
		
	
	
		
			8.4 KiB
		
	
	
	
		
			C#
		
	
	
	
	
	
			
		
		
	
	
			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();
 | |
|     }
 | |
| }
 |