Compare commits

..

1 Commits

Author SHA1 Message Date
defnotken
7659edc447 Add Serious Warning about nameplates 2025-12-28 00:35:41 -06:00
5 changed files with 51 additions and 126 deletions

View File

@@ -1423,7 +1423,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
private Task _visibilityGraceTask; private Task _visibilityGraceTask;
private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData, private async Task DownloadAndApplyCharacterAsync(Guid applicationBase, CharacterData charaData, Dictionary<ObjectKind, HashSet<PlayerChanges>> updatedData,
bool updateModdedPaths, bool updateManip, Dictionary<(string GamePath, string? Hash), string>? cachedModdedPaths, CancellationToken downloadToken) bool updateModdedPaths, bool updateManip, Dictionary<(string GamePath, string? Hash), string>? cachedModdedPaths, CancellationToken downloadToken)
{ {
var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false); var concurrencyLease = await _pairProcessingLimiter.AcquireAsync(downloadToken).ConfigureAwait(false);
try try
@@ -1577,37 +1577,24 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
RecordFailure("Handler not available for application", "HandlerUnavailable"); RecordFailure("Handler not available for application", "HandlerUnavailable");
return; return;
} }
_applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource();
if (_applicationTask != null && !_applicationTask.IsCompleted) var appToken = _applicationCancellationTokenSource?.Token;
while ((!_applicationTask?.IsCompleted ?? false)
&& !downloadToken.IsCancellationRequested
&& (!appToken?.IsCancellationRequested ?? false))
{ {
Logger.LogDebug("[BASE-{appBase}] Cancelling current data application (Id: {id}) for player ({handler})", applicationBase, _applicationId, PlayerName); Logger.LogDebug("[BASE-{appBase}] Waiting for current data application (Id: {id}) for player ({handler}) to finish", applicationBase, _applicationId, PlayerName);
await Task.Delay(250).ConfigureAwait(false);
var timeoutCts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var combinedCts = CancellationTokenSource.CreateLinkedTokenSource(downloadToken, timeoutCts.Token);
try
{
await _applicationTask.WaitAsync(combinedCts.Token).ConfigureAwait(false);
}
catch (OperationCanceledException)
{
Logger.LogWarning("[BASE-{appBase}] Timeout waiting for application task {id} to complete, proceeding anyway", applicationBase, _applicationId);
}
finally
{
timeoutCts.Dispose();
combinedCts.Dispose();
}
} }
if (downloadToken.IsCancellationRequested) if (downloadToken.IsCancellationRequested || (appToken?.IsCancellationRequested ?? false))
{ {
_forceFullReapply = true; _forceFullReapply = true;
RecordFailure("Application cancelled", "Cancellation"); RecordFailure("Application cancelled", "Cancellation");
return; return;
} }
_applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource();
var token = _applicationCancellationTokenSource.Token; var token = _applicationCancellationTokenSource.Token;
_applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, wantsModApply, pendingModReapply, token); _applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, wantsModApply, pendingModReapply, token);

View File

@@ -105,7 +105,6 @@ public class UiFactory
groupData: groupData, groupData: groupData,
isLightfinderContext: isLightfinderContext, isLightfinderContext: isLightfinderContext,
lightfinderCid: lightfinderCid, lightfinderCid: lightfinderCid,
performanceCollector: _performanceCollectorService, performanceCollector: _performanceCollectorService);
_apiController);
} }
} }

View File

@@ -11,7 +11,6 @@ using LightlessSync.Services.ServerConfiguration;
using LightlessSync.UI.Services; using LightlessSync.UI.Services;
using LightlessSync.UI.Tags; using LightlessSync.UI.Tags;
using LightlessSync.Utils; using LightlessSync.Utils;
using LightlessSync.WebAPI;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using System.Numerics; using System.Numerics;
@@ -23,7 +22,6 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
private readonly PairUiService _pairUiService; private readonly PairUiService _pairUiService;
private readonly ServerConfigurationManager _serverManager; private readonly ServerConfigurationManager _serverManager;
private readonly ProfileTagService _profileTagService; private readonly ProfileTagService _profileTagService;
private readonly ApiController _apiController;
private readonly UiSharedService _uiSharedService; private readonly UiSharedService _uiSharedService;
private readonly UserData? _userData; private readonly UserData? _userData;
private readonly GroupData? _groupData; private readonly GroupData? _groupData;
@@ -62,8 +60,7 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
GroupData? groupData, GroupData? groupData,
bool isLightfinderContext, bool isLightfinderContext,
string? lightfinderCid, string? lightfinderCid,
PerformanceCollectorService performanceCollector, PerformanceCollectorService performanceCollector)
ApiController apiController)
: base(logger, mediator, BuildWindowTitle( : base(logger, mediator, BuildWindowTitle(
userData, userData,
groupData, groupData,
@@ -97,7 +94,6 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
.Apply(); .Apply();
IsOpen = true; IsOpen = true;
_apiController = apiController;
} }
public Pair? Pair { get; } public Pair? Pair { get; }
@@ -252,33 +248,19 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
ResetBannerTexture(); ResetBannerTexture();
_lastBannerPicture = bannerBytes; _lastBannerPicture = bannerBytes;
} }
string? noteText = null; string? noteText = null;
string statusLabel = _isLightfinderContext ? "Exploring" : "Offline";
var isSelfProfile = !_isLightfinderContext
&& _userData is not null
&& !string.IsNullOrEmpty(_apiController.UID)
&& string.Equals(_userData.UID, _apiController.UID, StringComparison.Ordinal);
string statusLabel = _isLightfinderContext
? "Exploring"
: isSelfProfile ? "Online" : "Offline";
string? visiblePlayerName = null; string? visiblePlayerName = null;
bool directPair = false; bool directPair = false;
bool youPaused = false; bool youPaused = false;
bool theyPaused = false; bool theyPaused = false;
List<string> syncshellLines = []; List<string> syncshellLines = [];
if (!_isLightfinderContext)
{
noteText = _serverManager.GetNoteForUid(_userData!.UID);
}
if (!_isLightfinderContext && Pair != null) if (!_isLightfinderContext && Pair != null)
{ {
var snapshot = _pairUiService.GetSnapshot(); var snapshot = _pairUiService.GetSnapshot();
noteText = _serverManager.GetNoteForUid(Pair.UserData.UID); noteText = _serverManager.GetNoteForUid(Pair.UserData.UID);
statusLabel = Pair.IsVisible ? "Visible" : (Pair.IsOnline ? "Online" : "Offline"); statusLabel = Pair.IsVisible ? "Visible" : (Pair.IsOnline ? "Online" : "Offline");
visiblePlayerName = Pair.IsVisible ? Pair.PlayerName : null; visiblePlayerName = Pair.IsVisible ? Pair.PlayerName : null;
@@ -300,15 +282,11 @@ public class StandaloneProfileUi : WindowMediatorSubscriberBase
var groupLabel = snapshot.GroupsByGid.TryGetValue(gid, out var groupInfo) var groupLabel = snapshot.GroupsByGid.TryGetValue(gid, out var groupInfo)
? groupInfo.GroupAliasOrGID ? groupInfo.GroupAliasOrGID
: gid; : gid;
var groupNote = _serverManager.GetNoteForGid(gid); var groupNote = _serverManager.GetNoteForGid(gid);
syncshellLines.Add(string.IsNullOrEmpty(groupNote) ? groupLabel : $"{groupNote} ({groupLabel})"); syncshellLines.Add(string.IsNullOrEmpty(groupNote) ? groupLabel : $"{groupNote} ({groupLabel})");
} }
} }
} }
if (isSelfProfile)
statusLabel = "Online";
} }
var presenceTokens = new List<PresenceToken> var presenceTokens = new List<PresenceToken>

View File

@@ -116,7 +116,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
var drawList = ImGui.GetWindowDrawList(); var drawList = ImGui.GetWindowDrawList();
var purple = UIColors.Get("LightlessPurple"); var purple = UIColors.Get("LightlessPurple");
var gradLeft = purple.WithAlpha(0.0f); var gradLeft = purple.WithAlpha(0.0f);
var gradRight = purple.WithAlpha(0.85f); var gradRight = purple.WithAlpha(0.85f);
uint colTopLeft = ImGui.ColorConvertFloat4ToU32(gradLeft); uint colTopLeft = ImGui.ColorConvertFloat4ToU32(gradLeft);
@@ -162,7 +162,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
var subtitlePos = new Vector2( var subtitlePos = new Vector2(
pMin.X + 12f * scale, pMin.X + 12f * scale,
titlePos.Y + titleHeight - 2f * scale); titlePos.Y + titleHeight - 2f * scale);
ImGui.SetCursorScreenPos(subtitlePos); ImGui.SetCursorScreenPos(subtitlePos);
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey); ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
@@ -392,27 +392,25 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
} }
UiSharedService.AttachToolTip("When enabled, inactive non-pinned, non-moderator users will be pruned automatically on the server."); UiSharedService.AttachToolTip("When enabled, inactive non-pinned, non-moderator users will be pruned automatically on the server.");
if (!_autoPruneEnabled)
{
ImGui.BeginDisabled();
}
ImGui.SameLine(); ImGui.SameLine();
ImGui.SetNextItemWidth(150); ImGui.SetNextItemWidth(150);
_uiSharedService.DrawCombo(
"Day(s) of inactivity (gets checked hourly)", using (ImRaii.Disabled(!_autoPruneEnabled))
[0, 1, 3, 7, 14, 30, 90], {
(count) => count == 0 ? "2 hours(s)" : count + " day(s)", _uiSharedService.DrawCombo(
selected => "Day(s) of inactivity",
{ [1, 3, 7, 14, 30, 90],
_autoPruneDays = selected; days => $"{days} day(s)",
SavePruneSettings(); selected =>
}, {
_autoPruneDays); _autoPruneDays = selected;
SavePruneSettings();
},
_autoPruneDays);
}
if (!_autoPruneEnabled) if (!_autoPruneEnabled)
{ {
ImGui.EndDisabled();
UiSharedService.ColorTextWrapped( UiSharedService.ColorTextWrapped(
"Automatic prune is currently disabled. Enable it and choose an inactivity threshold to let the server clean up inactive users automatically.", "Automatic prune is currently disabled. Enable it and choose an inactivity threshold to let the server clean up inactive users automatically.",
ImGuiColors.DalamudGrey); ImGuiColors.DalamudGrey);
@@ -595,7 +593,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
_uiSharedService.DrawCombo( _uiSharedService.DrawCombo(
"Day(s) of inactivity", "Day(s) of inactivity",
[0, 1, 3, 7, 14, 30, 90], [0, 1, 3, 7, 14, 30, 90],
(count) => count == 0 ? "2 hours(s)" : count + " day(s)", (count) => count == 0 ? "15 minute(s)" : count + " day(s)",
(selected) => (selected) =>
{ {
_pruneDays = selected; _pruneDays = selected;
@@ -665,8 +663,8 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
var style = ImGui.GetStyle(); var style = ImGui.GetStyle();
float fullW = ImGui.GetContentRegionAvail().X; float fullW = ImGui.GetContentRegionAvail().X;
float colIdentity = fullW * 0.45f; float colIdentity = fullW * 0.45f;
float colMeta = fullW * 0.35f; float colMeta = fullW * 0.35f;
float colActions = fullW - colIdentity - colMeta - style.ItemSpacing.X * 2.0f; float colActions = fullW - colIdentity - colMeta - style.ItemSpacing.X * 2.0f;
// Header // Header
@@ -875,7 +873,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
var boolcolor = UiSharedService.GetBoolColor(pair.IsOnline); var boolcolor = UiSharedService.GetBoolColor(pair.IsOnline);
UiSharedService.ColorText(text, boolcolor); UiSharedService.ColorText(text, boolcolor);
if (ImGui.IsItemClicked()) if (ImGui.IsItemClicked())
ImGui.SetClipboardText(pair.UserData.AliasOrUID); ImGui.SetClipboardText(pair.UserData.AliasOrUID);
@@ -1095,7 +1093,6 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale)); ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale));
} }
private void SavePruneSettings() private void SavePruneSettings()
{ {
if (_autoPruneDays <= 0) if (_autoPruneDays <= 0)
@@ -1103,7 +1100,8 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
_autoPruneEnabled = false; _autoPruneEnabled = false;
} }
var dto = new GroupPruneSettingsDto(Group: GroupFullInfo.Group, AutoPruneEnabled: _autoPruneEnabled, AutoPruneDays: _autoPruneDays); var enabled = _autoPruneEnabled && _autoPruneDays > 0;
var dto = new GroupPruneSettingsDto(Group: GroupFullInfo.Group, AutoPruneEnabled: enabled, AutoPruneDays: enabled ? _autoPruneDays : 0);
try try
{ {

View File

@@ -28,8 +28,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
private readonly TextureMetadataHelper _textureMetadataHelper; private readonly TextureMetadataHelper _textureMetadataHelper;
private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams; private readonly ConcurrentDictionary<ThrottledStream, byte> _activeDownloadStreams;
private readonly SemaphoreSlim _decompressGate =
new(Math.Max(1, Environment.ProcessorCount / 2), Math.Max(1, Environment.ProcessorCount / 2));
private volatile bool _disableDirectDownloads; private volatile bool _disableDirectDownloads;
private int _consecutiveDirectDownloadFailures; private int _consecutiveDirectDownloadFailures;
@@ -502,14 +500,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
} }
} }
private void RemoveStatus(string key)
{
lock (_downloadStatusLock)
{
_downloadStatus.Remove(key);
}
}
private async Task DecompressBlockFileAsync( private async Task DecompressBlockFileAsync(
string downloadStatusKey, string downloadStatusKey,
string blockFilePath, string blockFilePath,
@@ -532,57 +522,32 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
try try
{ {
// sanity check length
if (fileLengthBytes < 0 || fileLengthBytes > int.MaxValue) if (fileLengthBytes < 0 || fileLengthBytes > int.MaxValue)
throw new InvalidDataException($"Invalid block entry length: {fileLengthBytes}"); throw new InvalidDataException($"Invalid block entry length: {fileLengthBytes}");
// safe cast after check
var len = checked((int)fileLengthBytes);
if (!replacementLookup.TryGetValue(fileHash, out var repl)) if (!replacementLookup.TryGetValue(fileHash, out var repl))
{ {
Logger.LogWarning("{dlName}: No replacement mapping for {fileHash}", downloadLabel, fileHash); Logger.LogWarning("{dlName}: No replacement mapping for {fileHash}", downloadLabel, fileHash);
fileBlockStream.Seek(len, SeekOrigin.Current); // still need to skip bytes:
var skip = checked((int)fileLengthBytes);
fileBlockStream.Position += skip;
continue; continue;
} }
// decompress
var filePath = _fileDbManager.GetCacheFilePath(fileHash, repl.Extension); var filePath = _fileDbManager.GetCacheFilePath(fileHash, repl.Extension);
Logger.LogTrace("{dlName}: Decompressing {file}:{len} => {dest}", downloadLabel, fileHash, fileLengthBytes, filePath);
// read compressed data Logger.LogDebug("{dlName}: Decompressing {file}:{len} => {dest}", downloadLabel, fileHash, fileLengthBytes, filePath);
var len = checked((int)fileLengthBytes);
var compressed = new byte[len]; var compressed = new byte[len];
await ReadExactlyAsync(fileBlockStream, compressed.AsMemory(0, len), ct).ConfigureAwait(false); await ReadExactlyAsync(fileBlockStream, compressed.AsMemory(0, len), ct).ConfigureAwait(false);
if (len == 0)
{
await _fileCompactor.WriteAllBytesAsync(filePath, Array.Empty<byte>(), ct).ConfigureAwait(false);
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
continue;
}
MungeBuffer(compressed); MungeBuffer(compressed);
var decompressed = LZ4Wrapper.Unwrap(compressed);
// limit concurrent decompressions await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false);
await _decompressGate.WaitAsync(ct).ConfigureAwait(false); PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
try
{
var sw = System.Diagnostics.Stopwatch.StartNew();
// decompress
var decompressed = LZ4Wrapper.Unwrap(compressed);
Logger.LogTrace("{dlName}: Unwrap {fileHash} took {ms}ms (compressed {c} bytes, decompressed {d} bytes)",
downloadLabel, fileHash, sw.ElapsedMilliseconds, compressed.Length, decompressed?.Length ?? -1);
// write to file
await _fileCompactor.WriteAllBytesAsync(filePath, decompressed, ct).ConfigureAwait(false);
PersistFileToStorage(fileHash, filePath, repl.GamePath, skipDownscale);
}
finally
{
_decompressGate.Release();
}
} }
catch (EndOfStreamException) catch (EndOfStreamException)
{ {
@@ -603,10 +568,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
{ {
Logger.LogError(ex, "{dlName}: Error during block file read", downloadLabel); Logger.LogError(ex, "{dlName}: Error during block file read", downloadLabel);
} }
finally
{
RemoveStatus(downloadStatusKey);
}
} }
public async Task<List<DownloadFileTransfer>> InitiateDownloadList( public async Task<List<DownloadFileTransfer>> InitiateDownloadList(
@@ -644,16 +605,20 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
.. await FilesGetSizes(hashes, ct).ConfigureAwait(false), .. await FilesGetSizes(hashes, ct).ConfigureAwait(false),
]; ];
Logger.LogDebug("Files with size 0 or less: {files}",
string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden)) foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden))
{ {
if (!_orchestrator.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal))) if (!_orchestrator.ForbiddenTransfers.Exists(f => string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal)))
_orchestrator.ForbiddenTransfers.Add(new DownloadFileTransfer(dto)); _orchestrator.ForbiddenTransfers.Add(new DownloadFileTransfer(dto));
} }
CurrentDownloads = [.. downloadFileInfoFromService CurrentDownloads = downloadFileInfoFromService
.Distinct() .Distinct()
.Select(d => new DownloadFileTransfer(d)) .Select(d => new DownloadFileTransfer(d))
.Where(d => d.CanBeTransferred)]; .Where(d => d.CanBeTransferred)
.ToList();
return CurrentDownloads; return CurrentDownloads;
} }
@@ -878,8 +843,6 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
MarkTransferredFiles(directDownload.DirectDownloadUrl!, 1); MarkTransferredFiles(directDownload.DirectDownloadUrl!, 1);
Logger.LogDebug("Finished direct download of {hash}.", directDownload.Hash); Logger.LogDebug("Finished direct download of {hash}.", directDownload.Hash);
RemoveStatus(directDownload.DirectDownloadUrl!);
} }
catch (OperationCanceledException ex) catch (OperationCanceledException ex)
{ {