Merge remote-tracking branch 'origin/2.0.3' into i18n
This commit is contained in:
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors></Authors>
|
||||
<Company></Company>
|
||||
<Version>2.0.2</Version>
|
||||
<Version>2.0.3</Version>
|
||||
<Description></Description>
|
||||
<Copyright></Copyright>
|
||||
<PackageProjectUrl>https://github.com/Light-Public-Syncshells/LightlessClient</PackageProjectUrl>
|
||||
|
||||
@@ -1423,7 +1423,7 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
private Task _visibilityGraceTask;
|
||||
|
||||
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);
|
||||
try
|
||||
@@ -1577,24 +1577,37 @@ internal sealed class PairHandlerAdapter : DisposableMediatorSubscriberBase, IPa
|
||||
RecordFailure("Handler not available for application", "HandlerUnavailable");
|
||||
return;
|
||||
}
|
||||
_applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource();
|
||||
|
||||
var appToken = _applicationCancellationTokenSource?.Token;
|
||||
while ((!_applicationTask?.IsCompleted ?? false)
|
||||
&& !downloadToken.IsCancellationRequested
|
||||
&& (!appToken?.IsCancellationRequested ?? false))
|
||||
if (_applicationTask != null && !_applicationTask.IsCompleted)
|
||||
{
|
||||
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);
|
||||
Logger.LogDebug("[BASE-{appBase}] Cancelling current data application (Id: {id}) for player ({handler})", applicationBase, _applicationId, PlayerName);
|
||||
|
||||
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 || (appToken?.IsCancellationRequested ?? false))
|
||||
if (downloadToken.IsCancellationRequested)
|
||||
{
|
||||
_forceFullReapply = true;
|
||||
RecordFailure("Application cancelled", "Cancellation");
|
||||
return;
|
||||
}
|
||||
|
||||
_applicationCancellationTokenSource = _applicationCancellationTokenSource.CancelRecreate() ?? new CancellationTokenSource();
|
||||
var token = _applicationCancellationTokenSource.Token;
|
||||
|
||||
_applicationTask = ApplyCharacterDataAsync(applicationBase, handlerForApply, charaData, updatedData, updateModdedPaths, updateManip, moddedPaths, wantsModApply, pendingModReapply, token);
|
||||
|
||||
@@ -10,7 +10,6 @@ using LightlessSync.UI;
|
||||
using LightlessSync.UI.Services;
|
||||
using LightlessSync.Utils;
|
||||
using LightlessSync.WebAPI;
|
||||
using Lumina.Excel.Sheets;
|
||||
using Microsoft.Extensions.Hosting;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
@@ -172,9 +171,8 @@ internal class ContextMenuService : IHostedService
|
||||
_logger.LogTrace("Cannot send pair request to {TargetName}@{World} while in PvP or GPose.", target.TargetName, target.TargetHomeWorld.RowId);
|
||||
return;
|
||||
}
|
||||
|
||||
var world = GetWorld(target.TargetHomeWorld.RowId);
|
||||
if (!IsWorldValid(world))
|
||||
|
||||
if (!IsWorldValid(target.TargetHomeWorld.RowId))
|
||||
{
|
||||
_logger.LogTrace("Target player {TargetName}@{World} is on an invalid world.", target.TargetName, target.TargetHomeWorld.RowId);
|
||||
return;
|
||||
@@ -226,9 +224,8 @@ internal class ContextMenuService : IHostedService
|
||||
{
|
||||
if (args.Target is not MenuTargetDefault target)
|
||||
return;
|
||||
|
||||
var world = GetWorld(target.TargetHomeWorld.RowId);
|
||||
if (!IsWorldValid(world))
|
||||
|
||||
if (!target.TargetHomeWorld.IsValid || !IsWorldValid(target.TargetHomeWorld.RowId))
|
||||
return;
|
||||
|
||||
try
|
||||
@@ -237,7 +234,7 @@ internal class ContextMenuService : IHostedService
|
||||
|
||||
if (targetData == null || targetData.Address == nint.Zero)
|
||||
{
|
||||
_logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, world.Name);
|
||||
_logger.LogWarning("Target player {TargetName}@{World} not found in object table.", target.TargetName, target.TargetHomeWorld.Value.Name);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -252,7 +249,7 @@ internal class ContextMenuService : IHostedService
|
||||
}
|
||||
|
||||
// Notify in chat when NotificationService is disabled
|
||||
NotifyInChat($"Pair request sent to {target.TargetName}@{world.Name}.", NotificationType.Info);
|
||||
NotifyInChat($"Pair request sent to {target.TargetName}@{target.TargetHomeWorld.Value.Name}.", NotificationType.Info);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
@@ -312,37 +309,8 @@ internal class ContextMenuService : IHostedService
|
||||
p.HomeWorld.RowId == target.TargetHomeWorld.RowId);
|
||||
}
|
||||
|
||||
private World GetWorld(uint worldId)
|
||||
private bool IsWorldValid(uint worldId)
|
||||
{
|
||||
var sheet = _gameData.GetExcelSheet<World>()!;
|
||||
var luminaWorlds = sheet.Where(x =>
|
||||
{
|
||||
var dc = x.DataCenter.ValueNullable;
|
||||
var name = x.Name.ExtractText();
|
||||
var internalName = x.InternalName.ExtractText();
|
||||
|
||||
if (dc == null || dc.Value.Region == 0 || string.IsNullOrWhiteSpace(dc.Value.Name.ExtractText()))
|
||||
return false;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(name) || string.IsNullOrWhiteSpace(internalName))
|
||||
return false;
|
||||
|
||||
if (name.Contains('-', StringComparison.Ordinal) || name.Contains('_', StringComparison.Ordinal))
|
||||
return false;
|
||||
|
||||
return x.DataCenter.Value.Region != 5 || x.RowId > 3001 && x.RowId != 1200 && IsChineseJapaneseKoreanString(name);
|
||||
});
|
||||
|
||||
return luminaWorlds.FirstOrDefault(x => x.RowId == worldId);
|
||||
}
|
||||
|
||||
private static bool IsChineseJapaneseKoreanString(string text) => text.All(IsChineseJapaneseKoreanCharacter);
|
||||
|
||||
private static bool IsChineseJapaneseKoreanCharacter(char c) => c >= 0x4E00 && c <= 0x9FFF;
|
||||
|
||||
public static bool IsWorldValid(World world)
|
||||
{
|
||||
var name = world.Name.ToString();
|
||||
return !string.IsNullOrWhiteSpace(name) && char.IsUpper(name[0]);
|
||||
return _dalamudUtil.WorldData.Value.ContainsKey((ushort)worldId);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -60,6 +60,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
private string _lastGlobalBlockReason = string.Empty;
|
||||
private ushort _lastZone = 0;
|
||||
private ushort _lastWorldId = 0;
|
||||
private uint _lastMapId = 0;
|
||||
private bool _sentBetweenAreas = false;
|
||||
private Lazy<ulong> _cid;
|
||||
|
||||
@@ -90,7 +91,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
{
|
||||
return gameData.GetExcelSheet<Lumina.Excel.Sheets.World>(clientLanguage)!
|
||||
.Where(w => !w.Name.IsEmpty && w.DataCenter.RowId != 0 && (w.IsPublic || char.IsUpper(w.Name.ToString()[0])
|
||||
|| w is { RowId: > 1000, Region: 101 }))
|
||||
|| w is { RowId: > 1000, Region: 101 or 201 }))
|
||||
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
|
||||
});
|
||||
JobData = new(() =>
|
||||
@@ -689,7 +690,7 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
var outside = houseMan->OutdoorTerritory;
|
||||
var house = outside->HouseId;
|
||||
location.WardId = house.WardIndex + 1u;
|
||||
location.HouseId = (uint)houseMan->GetCurrentPlot() + 1;
|
||||
//location.HouseId = (uint)houseMan->GetCurrentPlot() + 1;
|
||||
location.DivisionId = houseMan->GetCurrentDivision();
|
||||
}
|
||||
//_logger.LogWarning(LocationToString(location));
|
||||
@@ -1139,6 +1140,18 @@ public class DalamudUtilService : IHostedService, IMediatorSubscriber
|
||||
Mediator.Publish(new ZoneSwitchEndMessage());
|
||||
Mediator.Publish(new ResumeScanMessage(nameof(ConditionFlag.BetweenAreas)));
|
||||
}
|
||||
|
||||
//Map
|
||||
if (!_sentBetweenAreas)
|
||||
{
|
||||
var mapid = _clientState.MapId;
|
||||
if (mapid != _lastMapId)
|
||||
{
|
||||
_lastMapId = mapid;
|
||||
Mediator.Publish(new MapChangedMessage(mapid));
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var localPlayer = _objectTable.LocalPlayer;
|
||||
if (localPlayer != null)
|
||||
|
||||
@@ -17,8 +17,6 @@ namespace LightlessSync.Services
|
||||
private IMemoryCache _sharingStatus = new MemoryCache(new MemoryCacheOptions());
|
||||
private CancellationTokenSource _resetToken = new CancellationTokenSource();
|
||||
|
||||
|
||||
|
||||
public LocationShareService(ILogger<LocationShareService> logger, LightlessMediator mediator, DalamudUtilService dalamudUtilService, ApiController apiController) : base(logger, mediator)
|
||||
{
|
||||
_dalamudUtilService = dalamudUtilService;
|
||||
@@ -37,7 +35,7 @@ namespace LightlessSync.Services
|
||||
_ = RequestAllLocation();
|
||||
} );
|
||||
Mediator.Subscribe<LocationSharingMessage>(this, UpdateLocationList);
|
||||
Mediator.Subscribe<ZoneSwitchEndMessage>(this,
|
||||
Mediator.Subscribe<MapChangedMessage>(this,
|
||||
msg => _ = _apiController.UpdateLocation(new LocationDto(new UserData(_apiController.UID, _apiController.DisplayName), _dalamudUtilService.GetMapData())));
|
||||
}
|
||||
|
||||
@@ -134,5 +132,6 @@ namespace LightlessSync.Services
|
||||
AddStatus(user, expireAt);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
@@ -136,5 +136,6 @@ public record ChatChannelMessageAdded(string ChannelKey, ChatMessageEntry Messag
|
||||
public record GroupCollectionChangedMessage : MessageBase;
|
||||
public record OpenUserProfileMessage(UserData User) : MessageBase;
|
||||
public record LocationSharingMessage(UserData User, LocationInfo LocationInfo, DateTimeOffset ExpireAt) : MessageBase;
|
||||
public record MapChangedMessage(uint MapId) : MessageBase;
|
||||
#pragma warning restore S2094
|
||||
#pragma warning restore MA0048 // File name must match type name
|
||||
@@ -642,9 +642,10 @@ public class DrawUserPair
|
||||
if (ImGui.IsItemHovered())
|
||||
{
|
||||
ImGui.BeginTooltip();
|
||||
if (shareLocation)
|
||||
|
||||
if (_pair.IsOnline)
|
||||
{
|
||||
if (_pair.IsOnline)
|
||||
if (shareLocation)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(location))
|
||||
{
|
||||
@@ -659,18 +660,18 @@ public class DrawUserPair
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted("User not onlineㄟ( ▔, ▔ )ㄏ");
|
||||
ImGui.TextUnformatted("NOT Sharing location with you. o(TヘTo)");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted("NOT Sharing location with you.(⊙x⊙;)");
|
||||
ImGui.TextUnformatted("User not online. (´・ω・`)?");
|
||||
}
|
||||
ImGui.Separator();
|
||||
|
||||
if (shareLocationToOther)
|
||||
{
|
||||
ImGui.TextUnformatted("Sharing your location.ヾ(•ω•`)o");
|
||||
ImGui.TextUnformatted("Sharing your location. ヾ(•ω•`)o");
|
||||
if (expireAt != DateTimeOffset.MaxValue)
|
||||
{
|
||||
ImGui.TextUnformatted("Expires at " + expireAt.ToLocalTime().ToString("g"));
|
||||
@@ -678,7 +679,7 @@ public class DrawUserPair
|
||||
}
|
||||
else
|
||||
{
|
||||
ImGui.TextUnformatted("NOT sharing your location.(´。_。`)");
|
||||
ImGui.TextUnformatted("NOT sharing your location.  ̄へ ̄");
|
||||
}
|
||||
ImGui.EndTooltip();
|
||||
}
|
||||
|
||||
@@ -2183,7 +2183,7 @@ public class DataAnalysisUi : WindowMediatorSubscriberBase
|
||||
bool toggleClicked = false;
|
||||
if (showToggle)
|
||||
{
|
||||
var icon = isCollapsed ? FontAwesomeIcon.ChevronRight : FontAwesomeIcon.ChevronLeft;
|
||||
var icon = !isCollapsed ? FontAwesomeIcon.ChevronRight : FontAwesomeIcon.ChevronLeft;
|
||||
Vector2 iconSize;
|
||||
using (_uiSharedService.IconFont.Push())
|
||||
{
|
||||
|
||||
@@ -116,7 +116,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
var drawList = ImGui.GetWindowDrawList();
|
||||
|
||||
var purple = UIColors.Get("LightlessPurple");
|
||||
var gradLeft = purple.WithAlpha(0.0f);
|
||||
var gradLeft = purple.WithAlpha(0.0f);
|
||||
var gradRight = purple.WithAlpha(0.85f);
|
||||
|
||||
uint colTopLeft = ImGui.ColorConvertFloat4ToU32(gradLeft);
|
||||
@@ -162,7 +162,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
|
||||
var subtitlePos = new Vector2(
|
||||
pMin.X + 12f * scale,
|
||||
titlePos.Y + titleHeight - 2f * scale);
|
||||
titlePos.Y + titleHeight - 2f * scale);
|
||||
|
||||
ImGui.SetCursorScreenPos(subtitlePos);
|
||||
ImGui.PushStyleColor(ImGuiCol.Text, ImGuiColors.DalamudGrey);
|
||||
@@ -392,25 +392,27 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
}
|
||||
UiSharedService.AttachToolTip("When enabled, inactive non-pinned, non-moderator users will be pruned automatically on the server.");
|
||||
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth(150);
|
||||
|
||||
using (ImRaii.Disabled(!_autoPruneEnabled))
|
||||
{
|
||||
_uiSharedService.DrawCombo(
|
||||
"Day(s) of inactivity",
|
||||
[1, 3, 7, 14, 30, 90],
|
||||
days => $"{days} day(s)",
|
||||
selected =>
|
||||
{
|
||||
_autoPruneDays = selected;
|
||||
SavePruneSettings();
|
||||
},
|
||||
_autoPruneDays);
|
||||
}
|
||||
|
||||
if (!_autoPruneEnabled)
|
||||
{
|
||||
ImGui.BeginDisabled();
|
||||
}
|
||||
ImGui.SameLine();
|
||||
ImGui.SetNextItemWidth(150);
|
||||
_uiSharedService.DrawCombo(
|
||||
"Day(s) of inactivity (gets checked hourly)",
|
||||
[0, 1, 3, 7, 14, 30, 90],
|
||||
(count) => count == 0 ? "2 hours(s)" : count + " day(s)",
|
||||
selected =>
|
||||
{
|
||||
_autoPruneDays = selected;
|
||||
SavePruneSettings();
|
||||
},
|
||||
_autoPruneDays);
|
||||
|
||||
if (!_autoPruneEnabled)
|
||||
{
|
||||
ImGui.EndDisabled();
|
||||
UiSharedService.ColorTextWrapped(
|
||||
"Automatic prune is currently disabled. Enable it and choose an inactivity threshold to let the server clean up inactive users automatically.",
|
||||
ImGuiColors.DalamudGrey);
|
||||
@@ -593,7 +595,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
_uiSharedService.DrawCombo(
|
||||
"Day(s) of inactivity",
|
||||
[0, 1, 3, 7, 14, 30, 90],
|
||||
(count) => count == 0 ? "15 minute(s)" : count + " day(s)",
|
||||
(count) => count == 0 ? "2 hours(s)" : count + " day(s)",
|
||||
(selected) =>
|
||||
{
|
||||
_pruneDays = selected;
|
||||
@@ -663,8 +665,8 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
var style = ImGui.GetStyle();
|
||||
float fullW = ImGui.GetContentRegionAvail().X;
|
||||
|
||||
float colIdentity = fullW * 0.45f;
|
||||
float colMeta = fullW * 0.35f;
|
||||
float colIdentity = fullW * 0.45f;
|
||||
float colMeta = fullW * 0.35f;
|
||||
float colActions = fullW - colIdentity - colMeta - style.ItemSpacing.X * 2.0f;
|
||||
|
||||
// Header
|
||||
@@ -873,7 +875,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
|
||||
var boolcolor = UiSharedService.GetBoolColor(pair.IsOnline);
|
||||
UiSharedService.ColorText(text, boolcolor);
|
||||
|
||||
|
||||
if (ImGui.IsItemClicked())
|
||||
ImGui.SetClipboardText(pair.UserData.AliasOrUID);
|
||||
|
||||
@@ -1093,6 +1095,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
|
||||
ImGui.Dummy(new Vector2(0, 4 * ImGuiHelpers.GlobalScale));
|
||||
}
|
||||
|
||||
private void SavePruneSettings()
|
||||
{
|
||||
if (_autoPruneDays <= 0)
|
||||
@@ -1100,8 +1103,7 @@ public class SyncshellAdminUI : WindowMediatorSubscriberBase
|
||||
_autoPruneEnabled = false;
|
||||
}
|
||||
|
||||
var enabled = _autoPruneEnabled && _autoPruneDays > 0;
|
||||
var dto = new GroupPruneSettingsDto(Group: GroupFullInfo.Group, AutoPruneEnabled: enabled, AutoPruneDays: enabled ? _autoPruneDays : 0);
|
||||
var dto = new GroupPruneSettingsDto(Group: GroupFullInfo.Group, AutoPruneEnabled: _autoPruneEnabled, AutoPruneDays: _autoPruneDays);
|
||||
|
||||
try
|
||||
{
|
||||
|
||||
@@ -502,6 +502,14 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
||||
}
|
||||
}
|
||||
|
||||
private void RemoveStatus(string key)
|
||||
{
|
||||
lock (_downloadStatusLock)
|
||||
{
|
||||
_downloadStatus.Remove(key);
|
||||
}
|
||||
}
|
||||
|
||||
private async Task DecompressBlockFileAsync(
|
||||
string downloadStatusKey,
|
||||
string blockFilePath,
|
||||
@@ -595,6 +603,10 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
||||
{
|
||||
Logger.LogError(ex, "{dlName}: Error during block file read", downloadLabel);
|
||||
}
|
||||
finally
|
||||
{
|
||||
RemoveStatus(downloadStatusKey);
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<List<DownloadFileTransfer>> InitiateDownloadList(
|
||||
@@ -866,6 +878,8 @@ public partial class FileDownloadManager : DisposableMediatorSubscriberBase
|
||||
|
||||
MarkTransferredFiles(directDownload.DirectDownloadUrl!, 1);
|
||||
Logger.LogDebug("Finished direct download of {hash}.", directDownload.Hash);
|
||||
|
||||
RemoveStatus(directDownload.DirectDownloadUrl!);
|
||||
}
|
||||
catch (OperationCanceledException ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user