Compare commits

..

7 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
14ec282f21 Version Bump
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 33s
2025-09-13 17:16:46 +02:00
c7316e4f55 1.11.9 - Caching logging and dupe checks
Some checks failed
Tag and Release Lightless / tag-and-release (push) Has been cancelled
Co-authored-by: CakeAndBanana <admin@cakeandbanana.nl>
Co-authored-by: defnotken <itsdefnotken@gmail.com>
Reviewed-on: #28
2025-09-13 17:16:04 +02:00
8c308ab488 1.11.8 Hottofixo
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 38s
Co-authored-by: defnotken <itsdefnotken@gmail.com>
Co-authored-by: cake <cake@noreply.git.lightless-sync.org>
Reviewed-on: #26
2025-09-12 07:10:37 +02:00
a8512e2a86 1.11.7 hotfix
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 36s
Co-authored-by: defnotken <itsdefnotken@gmail.com>
Reviewed-on: #25
2025-09-12 01:14:23 +02:00
abe28e931c 1.11.6 (#4)
All checks were successful
Tag and Release Lightless / tag-and-release (push) Successful in 36s
1.11.6 Changelog (In Progress)
---
* Update submodule reference
* Update dalamud sdk
* Reworked the Syncshell Admin Page
   - Fixed that owners are visible in the list, Removed Pin/Remove/Ban buttons on Owners.
   - Styling is done similiar as settings page.
   - Added 1 or 3 day(s) option for inactive check.
+ Added new functions on the Server Top Bar button
   - Right click on the button will disconnect you from Lightless
   - Shift+Left click will open the settings page
+ Added colors section in the settings to change accent colors.
   - The nameplate coloring has been moved to this section
+ Added pin option from Dalamud in the UI.
+ Added ability to pause syncing while going in Instance/Duty
+ Added functionality to make syncshell folders
+ Fixed nameplate bug in PVP
+ added self-threshold warning

Co-authored-by: defnotken <itsdefnotken@gmail.com>
Co-authored-by: CakeAndBanana <admin@cakeandbanana.nl>
Co-authored-by: thijmenh <thijmenhogenkamp@gmail.com>
Co-authored-by: choco <choco@noreply.git.lightless-sync.org>
Co-authored-by: cake <cake@noreply.git.lightless-sync.org>
Co-authored-by: choco <thijmenhogenkamp@gmail.com>
Reviewed-on: #4
2025-09-11 23:43:11 +02:00
17 changed files with 317 additions and 137 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;
@@ -583,7 +583,14 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.LogWarning(ex, "Failed validating {path}", workload.ResolvedFilepath); if (workload != null)
{
Logger.LogWarning(ex, "Failed validating {path}", workload.ResolvedFilepath);
}
else
{
Logger.LogWarning(ex, "Failed validating unknown workload");
}
} }
Interlocked.Increment(ref _currentFileProgress); Interlocked.Increment(ref _currentFileProgress);
} }
@@ -612,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)
{ {
@@ -647,6 +654,12 @@ public sealed class CacheMonitor : DisposableMediatorSubscriberBase
CancellationToken = ct CancellationToken = ct
}, (cachePath) => }, (cachePath) =>
{ {
if (_fileDbManager == null || _ipcManager?.Penumbra == null || cachePath == null)
{
Logger.LogTrace("Potential null in db: {isDbNull} penumbra: {isPenumbraNull} cachepath: {isPathNull}", _fileDbManager == null, _ipcManager?.Penumbra == null, cachePath == null);
return;
}
if (ct.IsCancellationRequested) return; if (ct.IsCancellationRequested) return;
if (!_ipcManager.Penumbra.APIAvailable) if (!_ipcManager.Penumbra.APIAvailable)

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,37 +182,66 @@ public sealed class FileCacheManager : IHostedService
try try
{ {
var cleanedPaths = paths.Distinct(StringComparer.OrdinalIgnoreCase).ToDictionary(p => p, var allEntities = _fileCaches.SelectMany(f => f.Value).ToArray();
p => p.Replace("/", "\\", StringComparison.OrdinalIgnoreCase)
.Replace(_ipcManager.Penumbra.ModDirectory!, _ipcManager.Penumbra.ModDirectory!.EndsWith('\\') ? PenumbraPrefix + '\\' : PenumbraPrefix, StringComparison.OrdinalIgnoreCase) var cacheDict = new ConcurrentDictionary<string, FileCacheEntity>(
.Replace(_configService.Current.CacheFolder, _configService.Current.CacheFolder.EndsWith('\\') ? CachePrefix + '\\' : CachePrefix, StringComparison.OrdinalIgnoreCase)
.Replace("\\\\", "\\", StringComparison.Ordinal),
StringComparer.OrdinalIgnoreCase); StringComparer.OrdinalIgnoreCase);
Dictionary<string, FileCacheEntity?> result = new(StringComparer.OrdinalIgnoreCase); Parallel.ForEach(allEntities, entity =>
var dict = _fileCaches.SelectMany(f => f.Value)
.ToDictionary(d => d.PrefixedFilePath, d => d, StringComparer.OrdinalIgnoreCase);
foreach (var entry in cleanedPaths)
{ {
//_logger.LogDebug("Checking {path}", entry.Value); cacheDict[entity.PrefixedFilePath] = entity;
});
if (dict.TryGetValue(entry.Value, out var 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)
.Replace(
_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);
if (seenCleaned.TryAdd(cleaned, 0))
{
_logger.LogDebug("Adding to cleanedPaths: {cleaned}", cleaned);
cleanedPaths[p] = cleaned;
}
else
{
_logger.LogWarning("Duplicate found: {cleaned}", cleaned);
}
});
var result = new ConcurrentDictionary<string, FileCacheEntity?>(StringComparer.OrdinalIgnoreCase);
Parallel.ForEach(cleanedPaths, entry =>
{
_logger.LogDebug("Checking if in cache: {path}", entry.Value);
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
{ {
@@ -439,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.6</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

@@ -299,7 +299,7 @@ public class ServerConfigurationManager
internal void AddPairTag(string tag) internal void AddPairTag(string tag)
{ {
if (tag.Length > _maxCharactersFolder) if (tag.Length <= _maxCharactersFolder)
{ {
CurrentPairTagStorage().ServerAvailablePairTags.Add(tag); CurrentPairTagStorage().ServerAvailablePairTags.Add(tag);
_pairTagConfig.Save(); _pairTagConfig.Save();
@@ -313,7 +313,7 @@ public class ServerConfigurationManager
internal void AddSyncshellTag(string tag) internal void AddSyncshellTag(string tag)
{ {
if (tag.Length > _maxCharactersFolder) if (tag.Length <= _maxCharactersFolder)
{ {
CurrentSyncshellTagStorage().ServerAvailableSyncshellTags.Add(tag); CurrentSyncshellTagStorage().ServerAvailableSyncshellTags.Add(tag);
_syncshellTagConfig.Save(); _syncshellTagConfig.Save();

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()
@@ -416,10 +416,6 @@ public class CompactUi : WindowMediatorSubscriberBase
//Getting information of character and triangles threshold to show overlimit status in UID bar. //Getting information of character and triangles threshold to show overlimit status in UID bar.
_cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone(); _cachedAnalysis = _characterAnalyzer.LastAnalysis.DeepClone();
var groupedfiles = _cachedAnalysis.First().Value.Select(v => v.Value).GroupBy(f => f.FileType, StringComparer.Ordinal)
.OrderBy(k => k.Key, StringComparer.Ordinal).ToList();
var actualTriCount = _cachedAnalysis.First().Value.Sum(f => f.Value.Triangles);
var isOverTriHold = actualTriCount > (_playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000);
using (_uiSharedService.UidFont.Push()) using (_uiSharedService.UidFont.Push())
{ {
@@ -428,30 +424,63 @@ public class CompactUi : WindowMediatorSubscriberBase
ImGui.TextColored(GetUidColor(), uidText); ImGui.TextColored(GetUidColor(), uidText);
} }
if (groupedfiles != null) UiSharedService.AttachToolTip("Click to copy");
if (ImGui.IsItemClicked())
{ {
//Checking of VRAM threshhold ImGui.SetClipboardText(uidText);
var actualVramUsage = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal)).Sum(f => f.OriginalSize); }
var isOverVRAMUsage = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < actualVramUsage;
if (isOverTriHold || isOverVRAMUsage) if (_cachedAnalysis != null && _apiController.ServerState is ServerState.Connected)
{
var firstEntry = _cachedAnalysis.FirstOrDefault();
var valueDict = firstEntry.Value;
if (valueDict != null && valueDict.Count > 0)
{ {
ImGui.SameLine(); var groupedfiles = valueDict
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow")); .Select(v => v.Value)
string warningMessage = ""; .Where(v => v != null)
if (isOverTriHold) .GroupBy(f => f.FileType, StringComparer.Ordinal)
{ .OrderBy(k => k.Key, StringComparer.Ordinal)
warningMessage += $"You exceed your own triangles threshold by " + .ToList();
$"{actualTriCount - _playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000} triangles.";
warningMessage += Environment.NewLine;
} var actualTriCount = valueDict
if (isOverVRAMUsage) .Select(v => v.Value)
.Where(v => v != null)
.Sum(f => f.Triangles);
if (groupedfiles != null)
{ {
warningMessage += $"You exceed your own VRAM threshold by " + //Checking of VRAM threshhold
$"{UiSharedService.ByteToString(actualVramUsage - (_playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024))}."; var texGroup = groupedfiles.SingleOrDefault(v => string.Equals(v.Key, "tex", StringComparison.Ordinal));
var actualVramUsage = texGroup != null ? texGroup.Sum(f => f.OriginalSize) : 0L;
var isOverVRAMUsage = _playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024 < actualVramUsage;
var isOverTriHold = actualTriCount > (_playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000);
if ((isOverTriHold || isOverVRAMUsage) && _playerPerformanceConfig.Current.WarnOnExceedingThresholds)
{
ImGui.SameLine();
_uiSharedService.IconText(FontAwesomeIcon.ExclamationTriangle, UIColors.Get("LightlessYellow"));
string warningMessage = "";
if (isOverTriHold)
{
warningMessage += $"You exceed your own triangles threshold by " +
$"{actualTriCount - _playerPerformanceConfig.Current.TrisWarningThresholdThousands * 1000} triangles.";
warningMessage += Environment.NewLine;
}
if (isOverVRAMUsage)
{
warningMessage += $"You exceed your own VRAM threshold by " +
$"{UiSharedService.ByteToString(actualVramUsage - (_playerPerformanceConfig.Current.VRAMSizeWarningThresholdMiB * 1024 * 1024))}.";
}
UiSharedService.AttachToolTip(warningMessage);
if (ImGui.IsItemClicked())
{
_lightlessMediator.Publish(new UiToggleMessage(typeof(DataAnalysisUi)));
}
}
} }
UiSharedService.AttachToolTip(warningMessage);
} }
} }
@@ -470,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)));
} }
} }
} }
@@ -519,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)
@@ -542,7 +573,10 @@ 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);
groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs)); if (FilterNotTaggedSyncshells(group))
{
groupFolders.Add(_drawEntityFactory.CreateDrawGroupFolder(group, filteredGroupPairs, allGroupPairs));
}
} }
if (_configService.Current.GroupUpSyncshells) if (_configService.Current.GroupUpSyncshells)

View File

@@ -169,7 +169,7 @@ public class DownloadUi : WindowMediatorSubscriberBase
var dlProgressPercent = transferredBytes / (double)totalBytes; var dlProgressPercent = transferredBytes / (double)totalBytes;
drawList.AddRectFilled(dlBarStart, drawList.AddRectFilled(dlBarStart,
dlBarEnd with { X = dlBarStart.X + (float)(dlProgressPercent * dlBarWidth) }, dlBarEnd with { X = dlBarStart.X + (float)(dlProgressPercent * dlBarWidth) },
UiSharedService.Color(173, 138, 245, transparency), 1); UiSharedService.Color(UIColors.Get("LightlessPurple")));
if (_configService.Current.TransferBarsShowText) if (_configService.Current.TransferBarsShowText)
{ {

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);
@@ -832,7 +813,14 @@ public class SettingsUi : WindowMediatorSubscriberBase
{ {
foreach (var file in Directory.GetFiles(_configService.Current.CacheFolder)) foreach (var file in Directory.GetFiles(_configService.Current.CacheFolder))
{ {
File.Delete(file); try
{
File.Delete(file);
}
catch (IOException ex)
{
_logger.LogWarning(ex, $"Could not delete file {file} because it is in use.");
}
} }
}); });
} }
@@ -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,
@@ -254,13 +254,32 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
ImGui.TableNextColumn(); // actions ImGui.TableNextColumn(); // actions
if (_isOwner) if (_isOwner)
{ {
if (_uiSharedService.IconButton(FontAwesomeIcon.UserShield)) using (ImRaii.PushColor(ImGuiCol.Text, UIColors.Get("LightlessYellow")))
{ {
GroupPairUserInfo userInfo = pair.Value ?? GroupPairUserInfo.None; using (ImRaii.Disabled(!UiSharedService.ShiftPressed()))
{
if (_uiSharedService.IconButton(FontAwesomeIcon.Crown))
{
_ = _apiController.GroupChangeOwnership(new(GroupFullInfo.Group, pair.Key.UserData));
IsOpen = false;
}
}
userInfo.SetModerator(!userInfo.IsModerator()); }
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();
_ = _apiController.GroupSetUserInfo(new GroupPairUserInfoDto(GroupFullInfo.Group, pair.Key.UserData, userInfo)); using (ImRaii.PushColor(ImGuiCol.Text, pair.Value != null && pair.Value.Value.IsModerator() ? UIColors.Get("DimRed") : UIColors.Get("PairBlue")))
{
if (_uiSharedService.IconButton(FontAwesomeIcon.UserShield))
{
GroupPairUserInfo userInfo = pair.Value ?? GroupPairUserInfo.None;
userInfo.SetModerator(!userInfo.IsModerator());
_ = _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
{ {
response = await _orchestrator.SendRequestAsync(HttpMethod.Get, requestUrl, ct, HttpCompletionOption.ResponseHeadersRead).ConfigureAwait(false); try
response.EnsureSuccessStatusCode();
}
catch (HttpRequestException ex)
{
Logger.LogWarning(ex, "Error during download of {requestUrl}, HttpStatusCode: {code}", requestUrl, ex.StatusCode);
if (ex.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Unauthorized)
{ {
throw new InvalidDataException($"Http error {ex.StatusCode} (cancelled: {ct.IsCancellationRequested}): {requestUrl}", ex); 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.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)
{
Logger.LogWarning(ex, "Error during download of {requestUrl}, HttpStatusCode: {code}", requestUrl, ex.StatusCode);
if (ex.StatusCode is HttpStatusCode.NotFound or HttpStatusCode.Unauthorized)
{
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();
} }