Compare commits

...

2 Commits

Author SHA1 Message Date
defnotken
e396d2cf46 1.11.11 - Disable Show groups for now
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 32s
2025-09-15 21:41:22 -05:00
c5e6c06005 1.11.10 (#31)
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 34s
Co-authored-by: CakeAndBanana <admin@cakeandbanana.nl>
Co-authored-by: defnotken <itsdefnotken@gmail.com>
Reviewed-on: #31
2025-09-16 04:14:32 +02:00
15 changed files with 246 additions and 114 deletions

View File

@@ -383,7 +383,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
scanThread.Start(); scanThread.Start();
while (scanThread.IsAlive) while (scanThread.IsAlive)
{ {
await Task.Delay(250).ConfigureAwait(false); await Task.Delay(250, token).ConfigureAwait(false);
} }
TotalFiles = 0; TotalFiles = 0;
_currentFileProgress = 0; _currentFileProgress = 0;
@@ -619,7 +619,7 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
return; return;
} }
if (entitiesToUpdate.Any() || entitiesToRemove.Any()) if (entitiesToUpdate.Count != 0 || entitiesToRemove.Count != 0)
{ {
foreach (var entity in entitiesToUpdate) foreach (var entity in entitiesToUpdate)
{ {

View File

@@ -21,7 +21,7 @@ public sealed class FileCacheManager : IHostedService
private readonly string _csvPath; private readonly string _csvPath;
private readonly ConcurrentDictionary<string, List<FileCacheEntity>> _fileCaches = new(StringComparer.Ordinal); private readonly ConcurrentDictionary<string, List<FileCacheEntity>> _fileCaches = new(StringComparer.Ordinal);
private readonly SemaphoreSlim _getCachesByPathsSemaphore = new(1, 1); private readonly SemaphoreSlim _getCachesByPathsSemaphore = new(1, 1);
private readonly object _fileWriteLock = new(); private readonly Lock _fileWriteLock = new();
private readonly IpcManager _ipcManager; private readonly IpcManager _ipcManager;
private readonly ILogger<FileCacheManager> _logger; private readonly ILogger<FileCacheManager> _logger;
public string CacheFolder => _configService.Current.CacheFolder; public string CacheFolder => _configService.Current.CacheFolder;
@@ -42,10 +42,7 @@ public sealed class FileCacheManager : IHostedService
FileInfo fi = new(path); FileInfo fi = new(path);
if (!fi.Exists) return null; if (!fi.Exists) return null;
_logger.LogTrace("Creating cache entry for {path}", path); _logger.LogTrace("Creating cache entry for {path}", path);
var fullName = fi.FullName.ToLowerInvariant(); return CreateFileEntity(_configService.Current.CacheFolder.ToLowerInvariant(), CachePrefix, fi);
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);
} }
public FileCacheEntity? CreateFileEntry(string path) public FileCacheEntity? CreateFileEntry(string path)
@@ -53,9 +50,14 @@ public sealed class FileCacheManager : IHostedService
FileInfo fi = new(path); FileInfo fi = new(path);
if (!fi.Exists) return null; if (!fi.Exists) return null;
_logger.LogTrace("Creating file entry for {path}", path); _logger.LogTrace("Creating file entry for {path}", path);
return CreateFileEntity(_ipcManager.Penumbra.ModDirectory!.ToLowerInvariant(), PenumbraPrefix, fi);
}
private FileCacheEntity? CreateFileEntity(string directory, string prefix, FileInfo fi)
{
var fullName = fi.FullName.ToLowerInvariant(); var fullName = fi.FullName.ToLowerInvariant();
if (!fullName.Contains(_ipcManager.Penumbra.ModDirectory!.ToLowerInvariant(), StringComparison.Ordinal)) return null; if (!fullName.Contains(_configService.Current.CacheFolder.ToLowerInvariant(), StringComparison.Ordinal)) return null;
string prefixedPath = fullName.Replace(_ipcManager.Penumbra.ModDirectory!.ToLowerInvariant(), PenumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal); string prefixedPath = fullName.Replace(directory, prefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
return CreateFileCacheEntity(fi, prefixedPath); return CreateFileCacheEntity(fi, prefixedPath);
} }
@@ -66,7 +68,7 @@ public sealed class FileCacheManager : IHostedService
List<FileCacheEntity> output = []; List<FileCacheEntity> output = [];
if (_fileCaches.TryGetValue(hash, out var fileCacheEntities)) if (_fileCaches.TryGetValue(hash, out var fileCacheEntities))
{ {
foreach (var fileCache in fileCacheEntities.Where(c => ignoreCacheEntries ? !c.IsCacheEntry : true).ToList()) foreach (var fileCache in fileCacheEntities.Where(c => !ignoreCacheEntries || !c.IsCacheEntry).ToList())
{ {
if (!validate) output.Add(fileCache); if (!validate) output.Add(fileCache);
else else
@@ -106,7 +108,7 @@ public sealed class FileCacheManager : IHostedService
var computedHash = Crypto.GetFileHash(fileCache.ResolvedFilepath); var computedHash = Crypto.GetFileHash(fileCache.ResolvedFilepath);
if (!string.Equals(computedHash, fileCache.Hash, StringComparison.Ordinal)) if (!string.Equals(computedHash, fileCache.Hash, StringComparison.Ordinal))
{ {
_logger.LogInformation("Failed to validate {file}, got hash {hash}, expected hash {hash}", fileCache.ResolvedFilepath, computedHash, fileCache.Hash); _logger.LogInformation("Failed to validate {file}, got hash {computedHash}, expected hash {hash}", fileCache.ResolvedFilepath, computedHash, fileCache.Hash);
brokenEntities.Add(fileCache); brokenEntities.Add(fileCache);
} }
} }
@@ -151,7 +153,7 @@ public sealed class FileCacheManager : IHostedService
{ {
if (_fileCaches.TryGetValue(hash, out var hashes)) if (_fileCaches.TryGetValue(hash, out var hashes))
{ {
var item = hashes.OrderBy(p => p.PrefixedFilePath.Contains(PenumbraPrefix) ? 0 : 1).FirstOrDefault(); var item = hashes.OrderBy(p => p.PrefixedFilePath.Contains(PenumbraPrefix, StringComparison.Ordinal) ? 0 : 1).FirstOrDefault();
if (item != null) return GetValidatedFileCache(item); if (item != null) return GetValidatedFileCache(item);
} }
return null; return null;
@@ -180,48 +182,66 @@ public sealed class FileCacheManager : IHostedService
try try
{ {
var cleanedPaths = new Dictionary<string, string>(StringComparer.OrdinalIgnoreCase); var allEntities = _fileCaches.SelectMany(f => f.Value).ToArray();
foreach (var p in paths)
var cacheDict = new ConcurrentDictionary<string, FileCacheEntity>(
StringComparer.OrdinalIgnoreCase);
Parallel.ForEach(allEntities, entity =>
{
cacheDict[entity.PrefixedFilePath] = entity;
});
var cleanedPaths = new ConcurrentDictionary<string, string>(StringComparer.OrdinalIgnoreCase);
var seenCleaned = new ConcurrentDictionary<string, byte>(StringComparer.OrdinalIgnoreCase);
Parallel.ForEach(paths, p =>
{ {
var cleaned = p.Replace("/", "\\", StringComparison.OrdinalIgnoreCase) var cleaned = p.Replace("/", "\\", StringComparison.OrdinalIgnoreCase)
.Replace(_ipcManager.Penumbra.ModDirectory!, _ipcManager.Penumbra.ModDirectory!.EndsWith('\\') ? PenumbraPrefix + '\\' : PenumbraPrefix, StringComparison.OrdinalIgnoreCase) .Replace(
.Replace(_configService.Current.CacheFolder, _configService.Current.CacheFolder.EndsWith('\\') ? CachePrefix + '\\' : CachePrefix, StringComparison.OrdinalIgnoreCase) _ipcManager.Penumbra.ModDirectory!,
_ipcManager.Penumbra.ModDirectory!.EndsWith('\\')
? PenumbraPrefix + '\\' : PenumbraPrefix,
StringComparison.OrdinalIgnoreCase)
.Replace(
_configService.Current.CacheFolder,
_configService.Current.CacheFolder.EndsWith('\\')
? CachePrefix + '\\' : CachePrefix,
StringComparison.OrdinalIgnoreCase)
.Replace("\\\\", "\\", StringComparison.Ordinal); .Replace("\\\\", "\\", StringComparison.Ordinal);
if (!cleanedPaths.ContainsValue(cleaned)) if (seenCleaned.TryAdd(cleaned, 0))
{ {
_logger.LogDebug("Adding to cleanedPaths: {cleaned}", cleaned); _logger.LogDebug("Adding to cleanedPaths: {cleaned}", cleaned);
cleanedPaths[p] = cleaned; cleanedPaths[p] = cleaned;
} else }
else
{ {
_logger.LogWarning("Duplicated found: {cleaned}", cleaned); _logger.LogWarning("Duplicate found: {cleaned}", cleaned);
}
} }
});
Dictionary<string, FileCacheEntity?> result = new(StringComparer.OrdinalIgnoreCase); var result = new ConcurrentDictionary<string, FileCacheEntity?>(StringComparer.OrdinalIgnoreCase);
var dict = _fileCaches.SelectMany(f => f.Value).Distinct() Parallel.ForEach(cleanedPaths, entry =>
.ToDictionary(d => d.PrefixedFilePath, d => d, StringComparer.OrdinalIgnoreCase);
foreach (var entry in cleanedPaths)
{ {
_logger.LogDebug("Checking if in cache: {path}", entry.Value); _logger.LogDebug("Checking if in cache: {path}", entry.Value);
if (dict.TryGetValue(entry.Value, out var entity)) if (cacheDict.TryGetValue(entry.Value, out var entity))
{ {
var validatedCache = GetValidatedFileCache(entity); var validatedCache = GetValidatedFileCache(entity);
result.Add(entry.Key, validatedCache); result[entry.Key] = validatedCache;
} }
else else
{ {
if (!entry.Value.Contains(CachePrefix, StringComparison.Ordinal)) if (!entry.Value.Contains(CachePrefix, StringComparison.Ordinal))
result.Add(entry.Key, CreateFileEntry(entry.Key)); result[entry.Key] = CreateFileEntry(entry.Key);
else else
result.Add(entry.Key, CreateCacheEntry(entry.Key)); result[entry.Key] = CreateCacheEntry(entry.Key);
}
} }
});
return result; return new Dictionary<string, FileCacheEntity?>(result, StringComparer.OrdinalIgnoreCase);
} }
finally finally
{ {
@@ -450,7 +470,7 @@ public sealed class FileCacheManager : IHostedService
{ {
attempts++; attempts++;
_logger.LogWarning(ex, "Could not open {file}, trying again", _csvPath); _logger.LogWarning(ex, "Could not open {file}, trying again", _csvPath);
Thread.Sleep(100); Task.Delay(100, cancellationToken);
} }
} }

View File

@@ -43,6 +43,7 @@ public class LightlessConfig : ILightlessConfiguration
public bool ShowCharacterNameInsteadOfNotesForVisible { get; set; } = false; public bool ShowCharacterNameInsteadOfNotesForVisible { get; set; } = false;
public bool ShowOfflineUsersSeparately { get; set; } = true; public bool ShowOfflineUsersSeparately { get; set; } = true;
public bool ShowSyncshellOfflineUsersSeparately { get; set; } = true; public bool ShowSyncshellOfflineUsersSeparately { get; set; } = true;
public bool ShowGroupedSyncshellsInAll { get; set; } = true;
public bool GroupUpSyncshells { get; set; } = true; public bool GroupUpSyncshells { get; set; } = true;
public bool ShowOnlineNotifications { get; set; } = false; public bool ShowOnlineNotifications { get; set; } = false;
public bool ShowOnlineNotificationsOnlyForIndividualPairs { get; set; } = true; public bool ShowOnlineNotificationsOnlyForIndividualPairs { get; set; } = true;

View File

@@ -14,4 +14,6 @@ public class PlayerPerformanceConfig : ILightlessConfiguration
public int TrisAutoPauseThresholdThousands { get; set; } = 250; public int TrisAutoPauseThresholdThousands { get; set; } = 250;
public List<string> UIDsToIgnore { get; set; } = new(); public List<string> UIDsToIgnore { get; set; } = new();
public bool PauseInInstanceDuty { get; set; } = false; public bool PauseInInstanceDuty { get; set; } = false;
public bool PauseWhilePerforming { get; set; } = true;
public bool PauseInCombat { get; set; } = true;
} }

View File

@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<Authors></Authors> <Authors></Authors>
<Company></Company> <Company></Company>
<Version>1.11.9</Version> <Version>1.11.11</Version>
<Description></Description> <Description></Description>
<Copyright></Copyright> <Copyright></Copyright>
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl> <PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>

View File

@@ -88,11 +88,19 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
_redrawOnNextApplication = true; _redrawOnNextApplication = true;
} }
}); });
Mediator.Subscribe<CombatOrPerformanceEndMessage>(this, (msg) => Mediator.Subscribe<CombatEndMessage>(this, (msg) =>
{ {
EnableSync(); EnableSync();
}); });
Mediator.Subscribe<CombatOrPerformanceStartMessage>(this, _ => Mediator.Subscribe<CombatStartMessage>(this, _ =>
{
DisableSync();
});
Mediator.Subscribe<PerformanceEndMessage>(this, (msg) =>
{
EnableSync();
});
Mediator.Subscribe<PerformanceStartMessage>(this, _ =>
{ {
DisableSync(); DisableSync();
}); });
@@ -137,11 +145,21 @@ public sealed class PairHandler : DisposableMediatorSubscriberBase
public void ApplyCharacterData(Guid applicationBase, CharacterData characterData, bool forceApplyCustomization = false) public void ApplyCharacterData(Guid applicationBase, CharacterData characterData, bool forceApplyCustomization = false)
{ {
if (_dalamudUtil.IsInCombatOrPerforming) if (_dalamudUtil.IsInCombat)
{ {
Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Warning, Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Warning,
"Cannot apply character data: you are in combat or performing music, deferring application"))); "Cannot apply character data: you are in combat, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat or performing", applicationBase); Logger.LogDebug("[BASE-{appBase}] Received data but player is in combat", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
SetUploading(isUploading: false);
return;
}
if (_dalamudUtil.IsPerforming)
{
Mediator.Publish(new EventMessage(new Event(PlayerName, Pair.UserData, nameof(PairHandler), EventSeverity.Warning,
"Cannot apply character data: you are performing music, deferring application")));
Logger.LogDebug("[BASE-{appBase}] Received data but player is performing", applicationBase);
_dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization); _dataReceivedInDowntime = new(applicationBase, characterData, forceApplyCustomization);
SetUploading(isUploading: false); SetUploading(isUploading: false);
return; return;

View File

@@ -944,9 +944,7 @@ public sealed partial class CharaDataManager : DisposableMediatorSubscriberBase
Logger.LogTrace("[{appId}] Computing local missing files", applicationId); Logger.LogTrace("[{appId}] Computing local missing files", applicationId);
Dictionary<string, string> modPaths; _fileHandler.ComputeMissingFiles(charaDataDownloadDto, out Dictionary<string, string> modPaths, out List<FileReplacementData> missingFiles);
List<FileReplacementData> missingFiles;
_fileHandler.ComputeMissingFiles(charaDataDownloadDto, out modPaths, out missingFiles);
Logger.LogTrace("[{appId}] Computing local missing files", applicationId); Logger.LogTrace("[{appId}] Computing local missing files", applicationId);
@@ -990,7 +988,7 @@ public sealed partial class CharaDataManager : DisposableMediatorSubscriberBase
{ {
_uploadCts = _uploadCts.CancelRecreate(); _uploadCts = _uploadCts.CancelRecreate();
var missingFiles = await _fileHandler.UploadFiles([.. missingFileList.Select(k => k.HashOrFileSwap)], UploadProgress, _uploadCts.Token).ConfigureAwait(false); var missingFiles = await _fileHandler.UploadFiles([.. missingFileList.Select(k => k.HashOrFileSwap)], UploadProgress, _uploadCts.Token).ConfigureAwait(false);
if (missingFiles.Any()) if (missingFiles.Count != 0)
{ {
Logger.LogInformation("Failed to upload {files}", string.Join(", ", missingFiles)); Logger.LogInformation("Failed to upload {files}", string.Join(", ", missingFiles));
return ($"Upload failed: {missingFiles.Count} missing or forbidden to upload local files.", false); return ($"Upload failed: {missingFiles.Count} missing or forbidden to upload local files.", false);

View File

@@ -236,7 +236,7 @@ public sealed class CharaDataNearbyManager : DisposableMediatorSubscriberBase
} }
} }
if (_charaDataConfigService.Current.NearbyDrawWisps && !_dalamudUtilService.IsInGpose && !_dalamudUtilService.IsInCombatOrPerforming) if (_charaDataConfigService.Current.NearbyDrawWisps && !_dalamudUtilService.IsInGpose && !_dalamudUtilService.IsInCombat && !_dalamudUtilService.IsPerforming && !_dalamudUtilService.IsInInstance)
await _dalamudUtilService.RunOnFrameworkThread(() => ManageWispsNearby(previousPoses)).ConfigureAwait(false); await _dalamudUtilService.RunOnFrameworkThread(() => ManageWispsNearby(previousPoses)).ConfigureAwait(false);
} }
@@ -253,7 +253,7 @@ public sealed class CharaDataNearbyManager : DisposableMediatorSubscriberBase
return; return;
} }
if (!_charaDataConfigService.Current.NearbyDrawWisps || _dalamudUtilService.IsInGpose || _dalamudUtilService.IsInCombatOrPerforming) if (!_charaDataConfigService.Current.NearbyDrawWisps || _dalamudUtilService.IsInGpose || _dalamudUtilService.IsInCombat || _dalamudUtilService.IsPerforming || _dalamudUtilService.IsInInstance)
ClearAllVfx(); ClearAllVfx();
var camera = CameraManager.Instance()->CurrentCamera; var camera = CameraManager.Instance()->CurrentCamera;

View File

@@ -162,7 +162,8 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
public bool IsLoggedIn { get; private set; } public bool IsLoggedIn { get; private set; }
public bool IsOnFrameworkThread => _framework.IsInFrameworkUpdateThread; public bool IsOnFrameworkThread => _framework.IsInFrameworkUpdateThread;
public bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51]; public bool IsZoning => _condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51];
public bool IsInCombatOrPerforming { get; private set; } = false; public bool IsInCombat { get; private set; } = false;
public bool IsPerforming { get; private set; } = false;
public bool IsInInstance { get; private set; } = false; public bool IsInInstance { get; private set; } = false;
public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles; public bool HasModifiedGameFiles => _gameData.HasModifiedGameDataFiles;
public uint ClassJobId => _classJobId!.Value; public uint ClassJobId => _classJobId!.Value;
@@ -670,19 +671,33 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
Mediator.Publish(new GposeEndMessage()); Mediator.Publish(new GposeEndMessage());
} }
if ((_condition[ConditionFlag.Performing] || _condition[ConditionFlag.InCombat]) && !IsInCombatOrPerforming && (_condition[ConditionFlag.BoundByDuty] && !_playerPerformanceConfigService.Current.PauseInInstanceDuty)) if ((_condition[ConditionFlag.InCombat]) && !IsInCombat && !IsInInstance && _playerPerformanceConfigService.Current.PauseInCombat)
{ {
_logger.LogDebug("Combat/Performance start"); _logger.LogDebug("Combat start");
IsInCombatOrPerforming = true; IsInCombat = true;
Mediator.Publish(new CombatOrPerformanceStartMessage()); Mediator.Publish(new CombatStartMessage());
Mediator.Publish(new HaltScanMessage(nameof(IsInCombatOrPerforming))); Mediator.Publish(new HaltScanMessage(nameof(IsInCombat)));
} }
else if ((!_condition[ConditionFlag.Performing] && !_condition[ConditionFlag.InCombat]) && IsInCombatOrPerforming && (_condition[ConditionFlag.BoundByDuty] && !_playerPerformanceConfigService.Current.PauseInInstanceDuty)) else if ((!_condition[ConditionFlag.InCombat]) && IsInCombat && !IsInInstance && _playerPerformanceConfigService.Current.PauseInCombat)
{ {
_logger.LogDebug("Combat/Performance end"); _logger.LogDebug("Combat end");
IsInCombatOrPerforming = false; IsInCombat = false;
Mediator.Publish(new CombatOrPerformanceEndMessage()); Mediator.Publish(new CombatEndMessage());
Mediator.Publish(new ResumeScanMessage(nameof(IsInCombatOrPerforming))); 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) if ((_condition[ConditionFlag.BoundByDuty]) && !IsInInstance && _playerPerformanceConfigService.Current.PauseInInstanceDuty)
{ {
@@ -752,7 +767,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
_classJobId = localPlayer.ClassJob.RowId; _classJobId = localPlayer.ClassJob.RowId;
} }
if (!IsInCombatOrPerforming) if (!IsInCombat || !IsPerforming || !IsInInstance)
Mediator.Publish(new FrameworkUpdateMessage()); Mediator.Publish(new FrameworkUpdateMessage());
Mediator.Publish(new PriorityFrameworkUpdateMessage()); Mediator.Publish(new PriorityFrameworkUpdateMessage());
@@ -781,7 +796,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
IsLodEnabled = lodEnabled; IsLodEnabled = lodEnabled;
} }
if (IsInCombatOrPerforming) if (IsInCombat || IsPerforming || IsInInstance)
Mediator.Publish(new FrameworkUpdateMessage()); Mediator.Publish(new FrameworkUpdateMessage());
Mediator.Publish(new DelayedFrameworkUpdateMessage()); Mediator.Publish(new DelayedFrameworkUpdateMessage());

View File

@@ -79,8 +79,10 @@ public record OpenPermissionWindow(Pair Pair) : MessageBase;
public record DownloadLimitChangedMessage() : SameThreadMessage; public record DownloadLimitChangedMessage() : SameThreadMessage;
public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : MessageBase; public record CensusUpdateMessage(byte Gender, byte RaceId, byte TribeId) : MessageBase;
public record TargetPairMessage(Pair Pair) : MessageBase; public record TargetPairMessage(Pair Pair) : MessageBase;
public record CombatOrPerformanceStartMessage : MessageBase; public record CombatStartMessage : MessageBase;
public record CombatOrPerformanceEndMessage : MessageBase; public record CombatEndMessage : MessageBase;
public record PerformanceStartMessage : MessageBase;
public record PerformanceEndMessage : MessageBase;
public record InstanceOrDutyStartMessage : MessageBase; public record InstanceOrDutyStartMessage : MessageBase;
public record InstanceOrDutyEndMessage : MessageBase; public record InstanceOrDutyEndMessage : MessageBase;
public record EventMessage(Event Event) : MessageBase; public record EventMessage(Event Event) : MessageBase;

View File

@@ -8,7 +8,6 @@ using LightlessSync.API.Data.Extensions;
using LightlessSync.API.Dto.Group; using LightlessSync.API.Dto.Group;
using LightlessSync.Interop.Ipc; using LightlessSync.Interop.Ipc;
using LightlessSync.LightlessConfiguration; using LightlessSync.LightlessConfiguration;
using LightlessSync.LightlessConfiguration.Configurations;
using LightlessSync.PlayerData.Handlers; using LightlessSync.PlayerData.Handlers;
using LightlessSync.PlayerData.Pairs; using LightlessSync.PlayerData.Pairs;
using LightlessSync.Services; using LightlessSync.Services;
@@ -35,6 +34,7 @@ public class CompactUi : WindowMediatorSubscriberBase
private readonly CharacterAnalyzer _characterAnalyzer; private readonly CharacterAnalyzer _characterAnalyzer;
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly LightlessConfigService _configService; private readonly LightlessConfigService _configService;
private readonly LightlessMediator _lightlessMediator;
private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads = new(); private readonly ConcurrentDictionary<GameObjectHandler, Dictionary<string, FileDownloadStatus>> _currentDownloads = new();
private readonly DrawEntityFactory _drawEntityFactory; private readonly DrawEntityFactory _drawEntityFactory;
private readonly FileUploadManager _fileTransferManager; private readonly FileUploadManager _fileTransferManager;
@@ -57,7 +57,6 @@ public class CompactUi : WindowMediatorSubscriberBase
private string _lastAddedUserComment = string.Empty; private string _lastAddedUserComment = string.Empty;
private Vector2 _lastPosition = Vector2.One; private Vector2 _lastPosition = Vector2.One;
private Vector2 _lastSize = Vector2.One; private Vector2 _lastSize = Vector2.One;
private int _secretKeyIdx = -1;
private bool _showModalForUserAddition; private bool _showModalForUserAddition;
private float _transferPartHeight; private float _transferPartHeight;
private bool _wasOpen; private bool _wasOpen;
@@ -68,7 +67,7 @@ public class CompactUi : WindowMediatorSubscriberBase
TagHandler tagHandler, DrawEntityFactory drawEntityFactory, TagHandler tagHandler, DrawEntityFactory drawEntityFactory,
SelectTagForPairUi selectTagForPairUi, SelectPairForTagUi selectPairForTagUi, RenamePairTagUi renameTagUi, SelectTagForPairUi selectTagForPairUi, SelectPairForTagUi selectPairForTagUi, RenamePairTagUi renameTagUi,
SelectTagForSyncshellUi selectTagForSyncshellUi, SelectSyncshellForTagUi selectSyncshellForTagUi, RenameSyncshellTagUi renameSyncshellTagUi, SelectTagForSyncshellUi selectTagForSyncshellUi, SelectSyncshellForTagUi selectSyncshellForTagUi, RenameSyncshellTagUi renameSyncshellTagUi,
PerformanceCollectorService performanceCollectorService, IpcManager ipcManager, CharacterAnalyzer characterAnalyzer, PlayerPerformanceConfigService playerPerformanceConfig) PerformanceCollectorService performanceCollectorService, IpcManager ipcManager, CharacterAnalyzer characterAnalyzer, PlayerPerformanceConfigService playerPerformanceConfig, LightlessMediator lightlessMediator)
: base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService) : base(logger, mediator, "###LightlessSyncMainUI", performanceCollectorService)
{ {
_uiSharedService = uiShared; _uiSharedService = uiShared;
@@ -124,7 +123,7 @@ public class CompactUi : WindowMediatorSubscriberBase
} }
}; };
_drawFolders = GetDrawFolders().ToList(); _drawFolders = [.. GetDrawFolders()];
#if DEBUG #if DEBUG
string dev = "Dev Build"; string dev = "Dev Build";
@@ -152,6 +151,7 @@ public class CompactUi : WindowMediatorSubscriberBase
}; };
_characterAnalyzer = characterAnalyzer; _characterAnalyzer = characterAnalyzer;
_playerPerformanceConfig = playerPerformanceConfig; _playerPerformanceConfig = playerPerformanceConfig;
_lightlessMediator = lightlessMediator;
} }
protected override void DrawInternal() protected override void DrawInternal()
@@ -430,7 +430,7 @@ public class CompactUi : WindowMediatorSubscriberBase
ImGui.SetClipboardText(uidText); ImGui.SetClipboardText(uidText);
} }
if (_cachedAnalysis != null) if (_cachedAnalysis != null && _apiController.ServerState is ServerState.Connected)
{ {
var firstEntry = _cachedAnalysis.FirstOrDefault(); var firstEntry = _cachedAnalysis.FirstOrDefault();
var valueDict = firstEntry.Value; var valueDict = firstEntry.Value;
@@ -460,6 +460,7 @@ public class CompactUi : WindowMediatorSubscriberBase
{ {
ImGui.SameLine(); ImGui.SameLine();
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow")); _uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow"));
string warningMessage = ""; string warningMessage = "";
if (isOverTriHold) if (isOverTriHold)
{ {
@@ -474,6 +475,10 @@ public class CompactUi : WindowMediatorSubscriberBase
$"{UiSharedService.ByteToString(actualVramUsage - (_playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024))}."; $"{UiSharedService.ByteToString(actualVramUsage - (_playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024))}.";
} }
UiSharedService.AttachToolTip(warningMessage); UiSharedService.AttachToolTip(warningMessage);
if (ImGui.IsItemClicked())
{
_lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
}
} }
} }
} }
@@ -494,7 +499,7 @@ public class CompactUi : WindowMediatorSubscriberBase
UiSharedService.AttachToolTip("Click to copy"); UiSharedService.AttachToolTip("Click to copy");
if (ImGui.IsItemClicked()) if (ImGui.IsItemClicked())
{ {
ImGui.SetClipboardText(_apiController.UID); _lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
} }
} }
} }
@@ -543,6 +548,8 @@ public class CompactUi : WindowMediatorSubscriberBase
=> u.Value.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal)); => u.Value.Exists(g => string.Equals(g.GID, group.GID, StringComparison.Ordinal));
bool FilterNotTaggedUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u) bool FilterNotTaggedUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
=> u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && !_tagHandler.HasAnyPairTag(u.Key.UserData.UID); => u.Key.IsDirectlyPaired && !u.Key.IsOneSidedPair && !_tagHandler.HasAnyPairTag(u.Key.UserData.UID);
bool FilterNotTaggedSyncshells(GroupFullInfoDto group)
=> (!_tagHandler.HasAnySyncshellTag(group.GID) && !_configService.Current.ShowGroupedSyncshellsInAll) || true;
bool FilterOfflineUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u) bool FilterOfflineUsers(KeyValuePair<Pair, List<GroupFullInfoDto>> u)
=> ((u.Key.IsDirectlyPaired && _configService.Current.ShowSyncshellOfflineUsersSeparately) => ((u.Key.IsDirectlyPaired && _configService.Current.ShowSyncshellOfflineUsersSeparately)
|| !_configService.Current.ShowSyncshellOfflineUsersSeparately) || !_configService.Current.ShowSyncshellOfflineUsersSeparately)
@@ -566,8 +573,11 @@ public class CompactUi : WindowMediatorSubscriberBase
foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase)) foreach (var group in _pairManager.GroupPairs.Select(g => g.Key).OrderBy(g => g.GroupAliasOrGID, StringComparer.OrdinalIgnoreCase))
{ {
GetGroups(allPairs, filteredPairs, group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs); GetGroups(allPairs, filteredPairs, group, out ImmutableList<Pair> allGroupPairs, out Dictionary<Pair, List<GroupFullInfoDto>> filteredGroupPairs);
if (FilterNotTaggedSyncshells(group))
{
groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs)); groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs));
} }
}
if (_configService.Current.GroupUpSyncshells) if (_configService.Current.GroupUpSyncshells)
drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, "")); drawFolders.Add(new DrawGroupedGroupFolder(groupFolders, _tagHandler, _uiSharedService, _selectSyncshellForTagUi, _renameSyncshellTagUi, ""));

View File

@@ -633,25 +633,6 @@ public class SettingsUi : WindowMediatorSubscriberBase
{ {
_lastTab = "FileCache"; _lastTab = "FileCache";
_uiShared.UnderlinedBigText("Export MCDF", UIColors.Get("LightlessBlue"));
ImGuiHelpers.ScaledDummy(10);
UiSharedService.ColorTextWrapped("Exporting MCDF has moved.", UIColors.Get("LightlessYellow"));
ImGuiHelpers.ScaledDummy(5);
UiSharedService.TextWrapped("It is now found in the Main UI under \"Your User Menu\" (");
ImGui.SameLine();
_uiShared.IconText(FontAwesomeIcon.UserCog);
ImGui.SameLine();
UiSharedService.TextWrapped(") -> \"Character Data Hub\".");
if (_uiShared.IconTextButton(FontAwesomeIcon.Running, "Open Lightless Character Data Hub"))
{
Mediator.Publish(new UiToggleMessage(typeof(CharaDataHubUi)));
}
UiSharedService.TextWrapped("Note: this entry will be removed in the near future. Please use the Main UI to open the Character Data Hub.");
ImGuiHelpers.ScaledDummy(5);
ImGui.Separator();
_uiShared.UnderlinedBigText("Storage", UIColors.Get("LightlessBlue")); _uiShared.UnderlinedBigText("Storage", UIColors.Get("LightlessBlue"));
ImGuiHelpers.ScaledDummy(5); ImGuiHelpers.ScaledDummy(5);
@@ -831,9 +812,16 @@ public class SettingsUi : WindowMediatorSubscriberBase
_ = Task.Run(() => _ = Task.Run(() =>
{ {
foreach (var file in Directory.GetFiles(_configService.Current.CacheFolder)) foreach (var file in Directory.GetFiles(_configService.Current.CacheFolder))
{
try
{ {
File.Delete(file); File.Delete(file);
} }
catch (IOException ex)
{
_logger.LogWarning(ex, $"Could not delete file {file} because it is in use.");
}
}
}); });
} }
UiSharedService.AttachToolTip("You normally do not need to do this. THIS IS NOT SOMETHING YOU SHOULD BE DOING TO TRY TO FIX SYNC ISSUES." + Environment.NewLine UiSharedService.AttachToolTip("You normally do not need to do this. THIS IS NOT SOMETHING YOU SHOULD BE DOING TO TRY TO FIX SYNC ISSUES." + Environment.NewLine
@@ -939,6 +927,7 @@ public class SettingsUi : WindowMediatorSubscriberBase
var preferNotesInsteadOfName = _configService.Current.PreferNotesOverNamesForVisible; var preferNotesInsteadOfName = _configService.Current.PreferNotesOverNamesForVisible;
var useFocusTarget = _configService.Current.UseFocusTarget; var useFocusTarget = _configService.Current.UseFocusTarget;
var groupUpSyncshells = _configService.Current.GroupUpSyncshells; var groupUpSyncshells = _configService.Current.GroupUpSyncshells;
var groupedSyncshells = _configService.Current.ShowGroupedSyncshellsInAll;
var groupInVisible = _configService.Current.ShowSyncshellUsersInVisible; var groupInVisible = _configService.Current.ShowSyncshellUsersInVisible;
var syncshellOfflineSeparate = _configService.Current.ShowSyncshellOfflineUsersSeparately; var syncshellOfflineSeparate = _configService.Current.ShowSyncshellOfflineUsersSeparately;
@@ -1156,6 +1145,14 @@ public class SettingsUi : WindowMediatorSubscriberBase
} }
_uiShared.DrawHelpText("This will group up all Syncshells in a special 'All Syncshells' folder in the main UI."); _uiShared.DrawHelpText("This will group up all Syncshells in a special 'All Syncshells' folder in the main UI.");
//if (ImGui.Checkbox("Show grouped syncshells in main screen/all syncshells", ref groupedSyncshells))
//{
// _configService.Current.ShowGroupedSyncshellsInAll = groupedSyncshells;
// _configService.Save();
// Mediator.Publish(new RefreshUiMessage());
//}
_uiShared.DrawHelpText("This will show grouped syncshells in main screen or group 'All Syncshells'.");
if (ImGui.Checkbox("Show player name for visible players", ref showNameInsteadOfNotes)) if (ImGui.Checkbox("Show player name for visible players", ref showNameInsteadOfNotes))
{ {
_configService.Current.ShowCharacterNameInsteadOfNotesForVisible = showNameInsteadOfNotes; _configService.Current.ShowCharacterNameInsteadOfNotesForVisible = showNameInsteadOfNotes;
@@ -1380,16 +1377,32 @@ public class SettingsUi : WindowMediatorSubscriberBase
bool autoPause = _playerPerformanceConfigService.Current.AutoPausePlayersExceedingThresholds; bool autoPause = _playerPerformanceConfigService.Current.AutoPausePlayersExceedingThresholds;
bool autoPauseEveryone = _playerPerformanceConfigService.Current.AutoPausePlayersWithPreferredPermissionsExceedingThresholds; bool autoPauseEveryone = _playerPerformanceConfigService.Current.AutoPausePlayersWithPreferredPermissionsExceedingThresholds;
bool autoPauseInDuty = _playerPerformanceConfigService.Current.PauseInInstanceDuty; bool autoPauseInDuty = _playerPerformanceConfigService.Current.PauseInInstanceDuty;
bool autoPauseInCombat = _playerPerformanceConfigService.Current.PauseInCombat;
bool autoPauseWhilePerforming = _playerPerformanceConfigService.Current.PauseWhilePerforming;
if (_uiShared.MediumTreeNode("Auto Pause", UIColors.Get("LightlessPurple"))) if (_uiShared.MediumTreeNode("Auto Pause", UIColors.Get("LightlessPurple")))
{ {
if (ImGui.Checkbox("Auto pause sync while combat", ref autoPauseInCombat))
{
_playerPerformanceConfigService.Current.PauseInCombat = autoPauseInCombat;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText("AUTO-ENABLED: Your risk of crashing during a fight increases when this is disabled. For example: VFX mods Loading mid fight can cause a crash." + Environment.NewLine
+ UiSharedService.TooltipSeparator + "WARNING: DISABLE AT YOUR OWN RISK.");
if (ImGui.Checkbox("Auto pause sync while in Perfomance as Bard", ref autoPauseWhilePerforming))
{
_playerPerformanceConfigService.Current.PauseWhilePerforming = autoPauseWhilePerforming;
_playerPerformanceConfigService.Save();
}
_uiShared.DrawHelpText("AUTO-ENABLED: Your risk of crashing during a performance increases when this is disabled. For example: Some mods can crash you mid performance" + Environment.NewLine
+ UiSharedService.TooltipSeparator + "WARNING: DISABLE AT YOUR OWN RISK.");
if (ImGui.Checkbox("Auto pause sync while in instances and duties", ref autoPauseInDuty)) if (ImGui.Checkbox("Auto pause sync while in instances and duties", ref autoPauseInDuty))
{ {
_playerPerformanceConfigService.Current.PauseInInstanceDuty = autoPauseInDuty; _playerPerformanceConfigService.Current.PauseInInstanceDuty = autoPauseInDuty;
_playerPerformanceConfigService.Save(); _playerPerformanceConfigService.Save();
} }
_uiShared.DrawHelpText("When enabled, it will automatically pause all players while you are in an instance, such as a dungeon or raid." + Environment.NewLine _uiShared.DrawHelpText("When enabled, it will automatically pause all players while you are in an instance, such as a dungeon or raid." + Environment.NewLine
+ UiSharedService.TooltipSeparator + "Warning: You many have to leave the dungeon to resync with people again"); + UiSharedService.TooltipSeparator + "Warning: You may have to leave the dungeon to resync with people again");
if (ImGui.Checkbox("Automatically pause players exceeding thresholds", ref autoPause)) if (ImGui.Checkbox("Automatically pause players exceeding thresholds", ref autoPause))
{ {

View File

@@ -195,9 +195,9 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
using var table = ImRaii.Table("userList#" + GroupFullInfo.Group.GID, 3, tableFlags); using var table = ImRaii.Table("userList#" + GroupFullInfo.Group.GID, 3, tableFlags);
if (table) if (table)
{ {
ImGui.TableSetupColumn("Alias/UID/Note", ImGuiTableColumnFlags.None, 5); ImGui.TableSetupColumn("Alias/UID/Note", ImGuiTableColumnFlags.None, 4);
ImGui.TableSetupColumn("Flags", ImGuiTableColumnFlags.None, 1); ImGui.TableSetupColumn("Flags", ImGuiTableColumnFlags.None, 1);
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 2); ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 3);
ImGui.TableHeadersRow(); ImGui.TableHeadersRow();
var groupedPairs = new Dictionary<Pair, GroupPairUserInfo?>(pairs.Select(p => new KeyValuePair<Pair, GroupPairUserInfo?>(p, var groupedPairs = new Dictionary<Pair, GroupPairUserInfo?>(pairs.Select(p => new KeyValuePair<Pair, GroupPairUserInfo?>(p,
@@ -253,6 +253,24 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
ImGui.TableNextColumn(); // actions ImGui.TableNextColumn(); // actions
if (_isOwner) if (_isOwner)
{
using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow")))
{
using (ImRaii.Disabled(!UiSharedService.ShiftPressed()))
{
if (_uiSharedService.IconButton(FontAwesomeIcon.Crown))
{
_ = _apiController.GroupChangeOwnership(new(GroupFullInfo.Group, pair.Key.UserData));
IsOpen = false;
}
}
}
UiSharedService.AttachToolTip("Hold SHIFT and click to transfer ownership of this Syncshell to "
+ (pair.Key.UserData.AliasOrUID) + Environment.NewLine + "WARNING: This action is irreversible and will close screen.");
ImGui.SameLine();
using (ImRaii.PushColor(ImGuiCol.Text, pair.Value != null && pair.Value.Value.IsModerator() ? UIColors.Get("DimRed") : UIColors.Get("PairBlue")))
{ {
if (_uiSharedService.IconButton(FontAwesomeIcon.UserShield)) if (_uiSharedService.IconButton(FontAwesomeIcon.UserShield))
{ {
@@ -262,6 +280,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
_ = _apiController.GroupSetUserInfo(new GroupPairUserInfoDto(GroupFullInfo.Group, pair.Key.UserData, userInfo)); _ = _apiController.GroupSetUserInfo(new GroupPairUserInfoDto(GroupFullInfo.Group, pair.Key.UserData, userInfo));
} }
}
UiSharedService.AttachToolTip(pair.Value != null && pair.Value.Value.IsModerator() ? "Demod user" : "Mod user"); UiSharedService.AttachToolTip(pair.Value != null && pair.Value.Value.IsModerator() ? "Demod user" : "Mod user");
ImGui.SameLine(); ImGui.SameLine();
} }

View File

@@ -144,28 +144,55 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
_downloadStatus[downloadGroup].DownloadStatus = DownloadStatus.Downloading; _downloadStatus[downloadGroup].DownloadStatus = DownloadStatus.Downloading;
const int maxRetries = 3;
int retryCount = 0;
TimeSpan retryDelay = TimeSpan.FromSeconds(2);
HttpResponseMessage response = null!; HttpResponseMessage response = null!;
var requestUrl = LightlessFiles.CacheGetFullPath(fileTransfer[0].DownloadUri, requestId); var requestUrl = LightlessFiles.CacheGetFullPath(fileTransfer[0].DownloadUri, requestId);
Logger.LogDebug("Downloading {requestUrl} for request {id}", requestUrl, requestId); while (true)
{
try try
{ {
Logger.LogDebug("Attempt {attempt} - Downloading {requestUrl} for request {id}", retryCount + 1, requestUrl, requestId);
response = await _orchestrator.SendRequestAsync(HttpMethod.Get, requestUrl, ct, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); response = await _orchestrator.SendRequestAsync(HttpMethod.Get, requestUrl, ct, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false);
response.EnsureSuccessStatusCode(); response.EnsureSuccessStatusCode();
break;
}
catch (HttpRequestException ex) when (ex.InnerException is TimeoutException || ex.StatusCode == null)
{
retryCount++;
Logger.LogWarning(ex, "Timeout during download of {requestUrl}. Attempt {attempt} of {maxRetries}", requestUrl, retryCount, maxRetries);
if (retryCount >= maxRetries || ct.IsCancellationRequested)
{
Logger.LogError($"Max retries reached or cancelled. Failing download for {requestUrl}");
throw;
}
await Task.Delay(retryDelay, ct).ConfigureAwait(false); // Wait before retrying
} }
catch (HttpRequestException ex) catch (HttpRequestException ex)
{ {
Logger.LogWarning(ex, "Error during download of {requestUrl}, HttpStatusCode: {code}", requestUrl, ex.StatusCode); Logger.LogWarning(ex, "Error during download of {requestUrl}, HttpStatusCode: {code}", requestUrl, ex.StatusCode);
if (ex.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Unauthorized) if (ex.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Unauthorized)
{ {
throw new InvalidDataException($"Http error {ex.StatusCode} (cancelled: {ct.IsCancellationRequested}): {requestUrl}", ex); throw new InvalidDataException($"Http error {ex.StatusCode} (cancelled: {ct.IsCancellationRequested}): {requestUrl}", ex);
} }
}
throw;
}
}
ThrottledStream? stream = null; ThrottledStream? stream = null;
FileStream? fileStream = null;
try try
{ {
var fileStream = File.Create(tempPath); fileStream = File.Create(tempPath);
await using (fileStream.ConfigureAwait(false)) await using (fileStream.ConfigureAwait(false))
{ {
var bufferSize = response.Content.Headers.ContentLength > 1024 * 1024 ? 65536 : 8196; var bufferSize = response.Content.Headers.ContentLength > 1024 * 1024 ? 65536 : 8196;
@@ -174,8 +201,11 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
var bytesRead = 0; var bytesRead = 0;
var limit = _orchestrator.DownloadLimitPerSlot(); var limit = _orchestrator.DownloadLimitPerSlot();
Logger.LogTrace("Starting Download of {id} with a speed limit of {limit} to {tempPath}", requestId, limit, tempPath); Logger.LogTrace("Starting Download of {id} with a speed limit of {limit} to {tempPath}", requestId, limit, tempPath);
stream = new ThrottledStream(await response.Content.ReadAsStreamAsync(ct).ConfigureAwait(false), limit);
stream = new(await response.Content.ReadAsStreamAsync(ct).ConfigureAwait(false), limit);
_activeDownloadStreams.Add(stream); _activeDownloadStreams.Add(stream);
while ((bytesRead = await stream.ReadAsync(buffer, ct).ConfigureAwait(false)) > 0) while ((bytesRead = await stream.ReadAsync(buffer, ct).ConfigureAwait(false)) > 0)
{ {
ct.ThrowIfCancellationRequested(); ct.ThrowIfCancellationRequested();
@@ -194,16 +224,20 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
{ {
throw; throw;
} }
catch (Exception ex) catch (Exception)
{ {
try try
{ {
if (!tempPath.IsNullOrEmpty()) fileStream?.Close();
if (!string.IsNullOrEmpty(tempPath) && File.Exists(tempPath))
{
File.Delete(tempPath); File.Delete(tempPath);
} }
}
catch catch
{ {
// ignore if file deletion fails // Ignore errors during cleanup
} }
throw; throw;
} }

View File

@@ -69,7 +69,7 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
Logger.LogDebug("Trying to upload files"); Logger.LogDebug("Trying to upload files");
var filesPresentLocally = hashesToUpload.Where(h => _fileDbManager.GetFileCacheByHash(h) != null).ToHashSet(StringComparer.Ordinal); var filesPresentLocally = hashesToUpload.Where(h => _fileDbManager.GetFileCacheByHash(h) != null).ToHashSet(StringComparer.Ordinal);
var locallyMissingFiles = hashesToUpload.Except(filesPresentLocally, StringComparer.Ordinal).ToList(); var locallyMissingFiles = hashesToUpload.Except(filesPresentLocally, StringComparer.Ordinal).ToList();
if (locallyMissingFiles.Any()) if (locallyMissingFiles.Count != 0)
{ {
return locallyMissingFiles; return locallyMissingFiles;
} }
@@ -92,7 +92,7 @@ public sealed class FileUploadManager : DisposableMediatorSubscriberBase
var data = await _fileDbManager.GetCompressedFileData(file.Hash, ct ?? CancellationToken.None).ConfigureAwait(false); var data = await _fileDbManager.GetCompressedFileData(file.Hash, ct ?? CancellationToken.None).ConfigureAwait(false);
Logger.LogDebug("[{hash}] Starting upload for {filePath}", data.Item1, _fileDbManager.GetFileCacheByHash(data.Item1)!.ResolvedFilepath); Logger.LogDebug("[{hash}] Starting upload for {filePath}", data.Item1, _fileDbManager.GetFileCacheByHash(data.Item1)!.ResolvedFilepath);
await uploadTask.ConfigureAwait(false); await uploadTask.ConfigureAwait(false);
uploadTask = UploadFile(data.Item2, file.Hash, false, ct ?? CancellationToken.None); uploadTask = UploadFile(data.Item2, file.Hash, postProgress: false, ct ?? CancellationToken.None);
(ct ?? CancellationToken.None).ThrowIfCancellationRequested(); (ct ?? CancellationToken.None).ThrowIfCancellationRequested();
} }