From 6341a663f2af09094e234f308b5a7edcfd89b326 Mon Sep 17 00:00:00 2001 From: cake Date: Tue, 18 Nov 2025 04:00:53 +0100 Subject: [PATCH] Refactored some functions --- LightlessSync/FileCache/FileCacheManager.cs | 139 +++++++++++++--- LightlessSync/Services/BroadcastService.cs | 2 - LightlessSync/Services/CharacterAnalyzer.cs | 63 +++++--- LightlessSync/Services/DalamudUtilService.cs | 162 +++++++++++-------- 4 files changed, 252 insertions(+), 114 deletions(-) diff --git a/LightlessSync/FileCache/FileCacheManager.cs b/LightlessSync/FileCache/FileCacheManager.cs index d970896..1b1471c 100644 --- a/LightlessSync/FileCache/FileCacheManager.cs +++ b/LightlessSync/FileCache/FileCacheManager.cs @@ -18,6 +18,7 @@ public sealed class FileCacheManager : IHostedService public const string PenumbraPrefix = "{penumbra}"; private const int FileCacheVersion = 1; private const string FileCacheVersionHeaderPrefix = "#lightless-file-cache-version:"; + private readonly SemaphoreSlim _fileWriteSemaphore = new(1, 1); private readonly LightlessConfigService _configService; private readonly LightlessMediator _lightlessMediator; private readonly string _csvPath; @@ -169,27 +170,53 @@ public sealed class FileCacheManager : IHostedService return CreateFileCacheEntity(fi, prefixedPath); } - public List GetAllFileCaches() => _fileCaches.Values.SelectMany(v => v.Values.Where(e => e != null)).ToList(); + public List GetAllFileCaches() => [.. _fileCaches.Values.SelectMany(v => v.Values.Where(e => e != null))]; public List GetAllFileCachesByHash(string hash, bool ignoreCacheEntries = false, bool validate = true) { - List output = []; - if (_fileCaches.TryGetValue(hash, out var fileCacheEntities)) + var output = new List(); + + if (!_fileCaches.TryGetValue(hash, out var fileCacheEntities)) + return output; + + foreach (var fileCache in fileCacheEntities.Values + .Where(c => !ignoreCacheEntries || !c.IsCacheEntry)) { - foreach (var fileCache in fileCacheEntities.Values.Where(c => !ignoreCacheEntries || !c.IsCacheEntry).ToList()) + if (!validate) { - if (!validate) - { - output.Add(fileCache); - } - else - { - var validated = GetValidatedFileCache(fileCache); - if (validated != null) - { - output.Add(validated); - } - } + output.Add(fileCache); + continue; + } + + var validated = GetValidatedFileCache(fileCache); + if (validated != null) + output.Add(validated); + } + + return output; + } + + public async Task> GetAllFileCachesByHashAsync(string hash, bool ignoreCacheEntries = false, bool validate = true,CancellationToken token = default) + { + var output = new List(); + + if (!_fileCaches.TryGetValue(hash, out var fileCacheEntities)) + return output; + + foreach (var fileCache in fileCacheEntities.Values.Where(c => !ignoreCacheEntries || !c.IsCacheEntry)) + { + token.ThrowIfCancellationRequested(); + + if (!validate) + { + output.Add(fileCache); + } + else + { + var validated = await GetValidatedFileCacheAsync(fileCache, token).ConfigureAwait(false); + + if (validated != null) + output.Add(validated); } } @@ -479,6 +506,44 @@ public sealed class FileCacheManager : IHostedService } } + public async Task WriteOutFullCsvAsync(CancellationToken cancellationToken = default) + { + await _fileWriteSemaphore.WaitAsync(cancellationToken).ConfigureAwait(false); + + try + { + var sb = new StringBuilder(); + sb.AppendLine(BuildVersionHeader()); + + foreach (var entry in _fileCaches.Values + .SelectMany(k => k.Values) + .OrderBy(f => f.PrefixedFilePath, StringComparer.OrdinalIgnoreCase)) + { + sb.AppendLine(entry.CsvEntry); + } + + if (File.Exists(_csvPath)) + { + File.Copy(_csvPath, CsvBakPath, overwrite: true); + } + + try + { + await File.WriteAllTextAsync(_csvPath, sb.ToString(), cancellationToken).ConfigureAwait(false); + + File.Delete(CsvBakPath); + } + catch + { + await File.WriteAllTextAsync(CsvBakPath, sb.ToString(), cancellationToken).ConfigureAwait(false); + } + } + finally + { + _fileWriteSemaphore.Release(); + } + } + private void EnsureCsvHeaderLocked() { if (!File.Exists(_csvPath)) @@ -601,6 +666,13 @@ public sealed class FileCacheManager : IHostedService return resultingFileCache; } + private async Task GetValidatedFileCacheAsync(FileCacheEntity fileCache, CancellationToken token = default) + { + var resultingFileCache = ReplacePathPrefixes(fileCache); + resultingFileCache = await ValidateAsync(resultingFileCache, token).ConfigureAwait(false); + return resultingFileCache; + } + private FileCacheEntity ReplacePathPrefixes(FileCacheEntity fileCache) { if (fileCache.PrefixedFilePath.StartsWith(PenumbraPrefix, StringComparison.OrdinalIgnoreCase)) @@ -623,6 +695,7 @@ public sealed class FileCacheManager : IHostedService RemoveHashedFile(fileCache.Hash, fileCache.PrefixedFilePath); return null; } + var file = new FileInfo(fileCache.ResolvedFilepath); if (!file.Exists) { @@ -630,7 +703,8 @@ public sealed class FileCacheManager : IHostedService return null; } - if (!string.Equals(file.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture), fileCache.LastModifiedDateTicks, StringComparison.Ordinal)) + var lastWriteTicks = file.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture); + if (!string.Equals(lastWriteTicks, fileCache.LastModifiedDateTicks, StringComparison.Ordinal)) { UpdateHashedFile(fileCache); } @@ -638,6 +712,33 @@ public sealed class FileCacheManager : IHostedService return fileCache; } + private async Task ValidateAsync(FileCacheEntity fileCache, CancellationToken token) + { + if (string.IsNullOrWhiteSpace(fileCache.ResolvedFilepath)) + { + _logger.LogWarning("FileCacheEntity has empty ResolvedFilepath for hash {hash}, prefixed path {prefixed}", fileCache.Hash, fileCache.PrefixedFilePath); + RemoveHashedFile(fileCache.Hash, fileCache.PrefixedFilePath); + return null; + } + + return await Task.Run(() => + { + var file = new FileInfo(fileCache.ResolvedFilepath); + if (!file.Exists) + { + RemoveHashedFile(fileCache.Hash, fileCache.PrefixedFilePath); + return null; + } + + if (!string.Equals(file.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture), fileCache.LastModifiedDateTicks, StringComparison.Ordinal)) + { + UpdateHashedFile(fileCache); + } + + return fileCache; + }, token).ConfigureAwait(false); + } + public async Task StartAsync(CancellationToken cancellationToken) { _logger.LogInformation("Starting FileCacheManager"); @@ -811,7 +912,7 @@ public sealed class FileCacheManager : IHostedService if (rewriteRequired) { - WriteOutFullCsv(); + await WriteOutFullCsvAsync(cancellationToken).ConfigureAwait(false); } } @@ -822,7 +923,7 @@ public sealed class FileCacheManager : IHostedService public async Task StopAsync(CancellationToken cancellationToken) { - WriteOutFullCsv(); + await WriteOutFullCsvAsync(cancellationToken).ConfigureAwait(false); await Task.CompletedTask.ConfigureAwait(false); } } \ No newline at end of file diff --git a/LightlessSync/Services/BroadcastService.cs b/LightlessSync/Services/BroadcastService.cs index 08ce324..ff59716 100644 --- a/LightlessSync/Services/BroadcastService.cs +++ b/LightlessSync/Services/BroadcastService.cs @@ -396,8 +396,6 @@ public class BroadcastService : IHostedService, IMediatorSubscriber return result; } - - public async void ToggleBroadcast() { diff --git a/LightlessSync/Services/CharacterAnalyzer.cs b/LightlessSync/Services/CharacterAnalyzer.cs index 8b87c99..379af5a 100644 --- a/LightlessSync/Services/CharacterAnalyzer.cs +++ b/LightlessSync/Services/CharacterAnalyzer.cs @@ -54,34 +54,47 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable var cancelToken = _analysisCts.Token; var allFiles = LastAnalysis.SelectMany(v => v.Value.Select(d => d.Value)).ToList(); - if (allFiles.Exists(c => !c.IsComputed || recalculate)) + + var remaining = allFiles.Where(c => !c.IsComputed || recalculate).ToList(); + + if (remaining.Count == 0) + return; + + TotalFiles = remaining.Count; + CurrentFile = 0; + + Logger.LogDebug("=== Computing {amount} remaining files ===", remaining.Count); + + Mediator.Publish(new HaltScanMessage(nameof(CharacterAnalyzer))); + + try { - var remaining = allFiles.Where(c => !c.IsComputed || recalculate).ToList(); - TotalFiles = remaining.Count; - CurrentFile = 1; - Logger.LogDebug("=== Computing {amount} remaining files ===", remaining.Count); - - Mediator.Publish(new HaltScanMessage(nameof(CharacterAnalyzer))); - try + foreach (var file in remaining) { - foreach (var file in remaining) - { - Logger.LogDebug("Computing file {file}", file.FilePaths[0]); - await file.ComputeSizes(_fileCacheManager, cancelToken).ConfigureAwait(false); - CurrentFile++; - } + cancelToken.ThrowIfCancellationRequested(); - _fileCacheManager.WriteOutFullCsv(); + var path = file.FilePaths.FirstOrDefault() ?? ""; + Logger.LogDebug("Computing file {file}", path); + await file.ComputeSizes(_fileCacheManager, cancelToken).ConfigureAwait(false); + + CurrentFile++; } - catch (Exception ex) - { - Logger.LogWarning(ex, "Failed to analyze files"); - } - finally - { - Mediator.Publish(new ResumeScanMessage(nameof(CharacterAnalyzer))); - } + + await _fileCacheManager.WriteOutFullCsvAsync(cancelToken).ConfigureAwait(false); + } + catch (OperationCanceledException) + { + Logger.LogInformation("File analysis cancelled"); + throw; + } + catch (Exception ex) + { + Logger.LogWarning(ex, "Failed to analyze files"); + } + finally + { + Mediator.Publish(new ResumeScanMessage(nameof(CharacterAnalyzer))); } RecalculateSummary(); @@ -113,7 +126,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable { token.ThrowIfCancellationRequested(); - var fileCacheEntries = _fileCacheManager.GetAllFileCachesByHash(fileEntry.Hash, ignoreCacheEntries: true, validate: false).ToList(); + var fileCacheEntries = (await _fileCacheManager.GetAllFileCachesByHashAsync(fileEntry.Hash, ignoreCacheEntries: true, validate: false, token).ConfigureAwait(false)).ToList(); if (fileCacheEntries.Count == 0) continue; var filePath = fileCacheEntries[0].ResolvedFilepath; @@ -230,7 +243,7 @@ public sealed class CharacterAnalyzer : MediatorSubscriberBase, IDisposable { var compressedsize = await fileCacheManager.GetCompressedFileData(Hash, token).ConfigureAwait(false); var normalSize = new FileInfo(FilePaths[0]).Length; - var entries = fileCacheManager.GetAllFileCachesByHash(Hash, ignoreCacheEntries: true, validate: false); + var entries = await fileCacheManager.GetAllFileCachesByHashAsync(Hash, ignoreCacheEntries: true, validate: false, token).ConfigureAwait(false); foreach (var entry in entries) { entry.Size = normalSize; diff --git a/LightlessSync/Services/DalamudUtilService.cs b/LightlessSync/Services/DalamudUtilService.cs index f476076..e418f92 100644 --- a/LightlessSync/Services/DalamudUtilService.cs +++ b/LightlessSync/Services/DalamudUtilService.cs @@ -675,76 +675,75 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber _lastGlobalBlockReason = string.Empty; } - if (_clientState.IsGPosing && !IsInGpose) - { - _logger.LogDebug("Gpose start"); - IsInGpose = true; - Mediator.Publish(new GposeStartMessage()); - } - else if (!_clientState.IsGPosing && IsInGpose) - { - _logger.LogDebug("Gpose end"); - IsInGpose = false; - Mediator.Publish(new GposeEndMessage()); - } + // Checks on conditions + var shouldBeInGpose = _clientState.IsGPosing; + var shouldBeInCombat = _condition[ConditionFlag.InCombat] && !IsInInstance && _playerPerformanceConfigService.Current.PauseInCombat; + var shouldBePerforming = _condition[ConditionFlag.Performing] && _playerPerformanceConfigService.Current.PauseWhilePerforming; + var shouldBeInInstance = _condition[ConditionFlag.BoundByDuty] && _playerPerformanceConfigService.Current.PauseInInstanceDuty; + var shouldBeInCutscene = _condition[ConditionFlag.WatchingCutscene]; - if ((_condition[ConditionFlag.InCombat]) && !IsInCombat && !IsInInstance && _playerPerformanceConfigService.Current.PauseInCombat) - { - _logger.LogDebug("Combat start"); - IsInCombat = true; - Mediator.Publish(new CombatStartMessage()); - Mediator.Publish(new HaltScanMessage(nameof(IsInCombat))); - } - else if ((!_condition[ConditionFlag.InCombat]) && IsInCombat && !IsInInstance && _playerPerformanceConfigService.Current.PauseInCombat) - { - _logger.LogDebug("Combat end"); - IsInCombat = false; - Mediator.Publish(new CombatEndMessage()); - Mediator.Publish(new ResumeScanMessage(nameof(IsInCombat))); - } - if (_condition[ConditionFlag.Performing] && !IsPerforming && _playerPerformanceConfigService.Current.PauseWhilePerforming) - { - _logger.LogDebug("Performance start"); - IsInCombat = true; - Mediator.Publish(new PerformanceStartMessage()); - Mediator.Publish(new HaltScanMessage(nameof(IsPerforming))); - } - else if (!_condition[ConditionFlag.Performing] && IsPerforming && _playerPerformanceConfigService.Current.PauseWhilePerforming) - { - _logger.LogDebug("Performance end"); - IsInCombat = false; - Mediator.Publish(new PerformanceEndMessage()); - Mediator.Publish(new ResumeScanMessage(nameof(IsPerforming))); - } - if ((_condition[ConditionFlag.BoundByDuty]) && !IsInInstance && _playerPerformanceConfigService.Current.PauseInInstanceDuty) - { - _logger.LogDebug("Instance start"); - IsInInstance = true; - Mediator.Publish(new InstanceOrDutyStartMessage()); - Mediator.Publish(new HaltScanMessage(nameof(IsInInstance))); - } - else if (((!_condition[ConditionFlag.BoundByDuty]) && IsInInstance && _playerPerformanceConfigService.Current.PauseInInstanceDuty) || ((_condition[ConditionFlag.BoundByDuty]) && IsInInstance && !_playerPerformanceConfigService.Current.PauseInInstanceDuty)) - { - _logger.LogDebug("Instance end"); - IsInInstance = false; - Mediator.Publish(new InstanceOrDutyEndMessage()); - Mediator.Publish(new ResumeScanMessage(nameof(IsInInstance))); - } + // Gpose + HandleStateTransition(() => IsInGpose, v => IsInGpose = v, shouldBeInGpose, "Gpose", + onEnter: () => + { + Mediator.Publish(new GposeStartMessage()); + }, + onExit: () => + { + Mediator.Publish(new GposeEndMessage()); + }); - if (_condition[ConditionFlag.WatchingCutscene] && !IsInCutscene) - { - _logger.LogDebug("Cutscene start"); - IsInCutscene = true; - Mediator.Publish(new CutsceneStartMessage()); - Mediator.Publish(new HaltScanMessage(nameof(IsInCutscene))); - } - else if (!_condition[ConditionFlag.WatchingCutscene] && IsInCutscene) - { - _logger.LogDebug("Cutscene end"); - IsInCutscene = false; - Mediator.Publish(new CutsceneEndMessage()); - Mediator.Publish(new ResumeScanMessage(nameof(IsInCutscene))); - } + // Combat + HandleStateTransition(() => IsInCombat, v => IsInCombat = v, shouldBeInCombat, "Combat", + onEnter: () => + { + Mediator.Publish(new CombatStartMessage()); + Mediator.Publish(new HaltScanMessage(nameof(IsInCombat))); + }, + onExit: () => + { + Mediator.Publish(new CombatEndMessage()); + Mediator.Publish(new ResumeScanMessage(nameof(IsInCombat))); + }); + + // Performance + HandleStateTransition(() => IsPerforming, v => IsPerforming = v, shouldBePerforming, "Performance", + onEnter: () => + { + Mediator.Publish(new PerformanceStartMessage()); + Mediator.Publish(new HaltScanMessage(nameof(IsPerforming))); + }, + onExit: () => + { + Mediator.Publish(new PerformanceEndMessage()); + Mediator.Publish(new ResumeScanMessage(nameof(IsPerforming))); + }); + + // Instance / Duty + HandleStateTransition(() => IsInInstance, v => IsInInstance = v, shouldBeInInstance, "Instance", + onEnter: () => + { + Mediator.Publish(new InstanceOrDutyStartMessage()); + Mediator.Publish(new HaltScanMessage(nameof(IsInInstance))); + }, + onExit: () => + { + Mediator.Publish(new InstanceOrDutyEndMessage()); + Mediator.Publish(new ResumeScanMessage(nameof(IsInInstance))); + }); + + // Cutscene + HandleStateTransition(() => IsInCutscene,v => IsInCutscene = v, shouldBeInCutscene, "Cutscene", + onEnter: () => + { + Mediator.Publish(new CutsceneStartMessage()); + Mediator.Publish(new HaltScanMessage(nameof(IsInCutscene))); + }, + onExit: () => + { + Mediator.Publish(new CutsceneEndMessage()); + Mediator.Publish(new ResumeScanMessage(nameof(IsInCutscene))); + }); if (IsInCutscene) { @@ -821,4 +820,31 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber _delayedFrameworkUpdateCheck = DateTime.UtcNow; }); } + + /// + /// Handler for the transition of different states of game + /// + /// Get state of condition + /// Set state of condition + /// Correction of the state of the condition + /// Condition name + /// Function for on entering the state + /// Function for on leaving the state + private void HandleStateTransition(Func getState, Action setState, bool shouldBeActive, string stateName, System.Action onEnter, System.Action onExit) + { + var isActive = getState(); + + if (shouldBeActive && !isActive) + { + _logger.LogDebug("{stateName} start", stateName); + setState(true); + onEnter(); + } + else if (!shouldBeActive && isActive) + { + _logger.LogDebug("{stateName} end", stateName); + setState(false); + onExit(); + } + } } \ No newline at end of file