From b10a02f2286307cd9f967f4a1d34f4e492b639c8 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Thu, 29 Sep 2022 15:31:41 +0200 Subject: [PATCH 01/17] initial fixes for api changes for groups --- .../WebAPI/ApiController.Functions.Callbacks.cs | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs index beebc73..e565e71 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs @@ -13,13 +13,12 @@ namespace MareSynchronos.WebAPI _ = CreateConnections(); } - private void UpdateLocalClientPairsCallback(ClientPairDto dto, string characterIdentifier) + private void UpdateLocalClientPairsCallback(ClientPairDto dto) { var entry = PairedClients.SingleOrDefault(e => e.OtherUID == dto.OtherUID); if (dto.IsRemoved) { PairedClients.RemoveAll(p => p.OtherUID == dto.OtherUID); - UnpairedFromOther?.Invoke(characterIdentifier); return; } if (entry == null) @@ -28,20 +27,9 @@ namespace MareSynchronos.WebAPI return; } - if ((entry.IsPausedFromOthers != dto.IsPausedFromOthers || entry.IsSynced != dto.IsSynced || entry.IsPaused != dto.IsPaused) - && !dto.IsPaused && dto.IsSynced && !dto.IsPausedFromOthers) - { - PairedWithOther?.Invoke(characterIdentifier); - } - entry.IsPaused = dto.IsPaused; entry.IsPausedFromOthers = dto.IsPausedFromOthers; entry.IsSynced = dto.IsSynced; - - if (dto.IsPaused || dto.IsPausedFromOthers || !dto.IsSynced) - { - UnpairedFromOther?.Invoke(characterIdentifier); - } } private Task ReceiveCharacterDataCallback(CharacterCacheDto character, string characterHash) From ac6c46390c6fb44b61445ae7912f36f1b16f2d78 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Thu, 29 Sep 2022 15:52:33 +0200 Subject: [PATCH 02/17] add api to mare, change all to file scoped namespace --- MareAPI | 2 +- MareSynchronos/Configuration.cs | 303 +++-- MareSynchronos/Interop/Weapon.cs | 59 +- MareSynchronos/Localization/Strings.cs | 91 +- MareSynchronos/Managers/IpcManager.cs | 747 ++++++------ MareSynchronos/Managers/PlayerManager.cs | 469 ++++---- .../Managers/TransientResourceManager.cs | 363 +++--- MareSynchronos/Models/CharacterData.cs | 139 ++- MareSynchronos/Models/FileReplacement.cs | 87 +- MareSynchronos/Models/PlayerRelatedObject.cs | 241 ++-- MareSynchronos/Plugin.cs | 373 +++--- MareSynchronos/UI/CompactUI.cs | 1019 ++++++++-------- MareSynchronos/UI/IntroUI.cs | 485 ++++---- MareSynchronos/UI/SettingsUi.cs | 1069 ++++++++--------- MareSynchronos/UI/UIShared.cs | 1035 ++++++++-------- MareSynchronos/Utils/Crypto.cs | 43 +- MareSynchronos/Utils/DalamudUtil.cs | 391 +++--- MareSynchronos/Utils/Logger.cs | 193 ++- MareSynchronos/Utils/Various.cs | 31 +- .../WebAPI/ApIController.Functions.Files.cs | 591 +++++---- .../WebAPI/ApIController.Functions.Users.cs | 67 +- .../WebAPI/ApiController.Connectivity.cs | 372 ------ .../WebAPI/ApiController.Functions.Admin.cs | 61 +- .../ApiController.Functions.Callbacks.cs | 131 +- .../WebAPI/ApiController.Functions.Groups.cs | 64 + MareSynchronos/WebAPI/ApiController.cs | 372 ++++++ MareSynchronos/WebAPI/ServerState.cs | 11 + 27 files changed, 4436 insertions(+), 4373 deletions(-) delete mode 100644 MareSynchronos/WebAPI/ApiController.Connectivity.cs create mode 100644 MareSynchronos/WebAPI/ApiController.Functions.Groups.cs create mode 100644 MareSynchronos/WebAPI/ApiController.cs create mode 100644 MareSynchronos/WebAPI/ServerState.cs diff --git a/MareAPI b/MareAPI index 9dc1e90..57a7ab8 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit 9dc1e901aa453e65bb432b2b288c1ff00021700a +Subproject commit 57a7ab82625ca350bce0442c60f457e5e5e172c3 diff --git a/MareSynchronos/Configuration.cs b/MareSynchronos/Configuration.cs index 017ef81..d5b51ad 100644 --- a/MareSynchronos/Configuration.cs +++ b/MareSynchronos/Configuration.cs @@ -7,195 +7,194 @@ using System.Linq; using MareSynchronos.Utils; using MareSynchronos.WebAPI; -namespace MareSynchronos +namespace MareSynchronos; + +public static class ConfigurationExtensions { - public static class ConfigurationExtensions + public static bool HasValidSetup(this Configuration configuration) { - public static bool HasValidSetup(this Configuration configuration) - { - return configuration.AcceptedAgreement && configuration.InitialScanComplete - && !string.IsNullOrEmpty(configuration.CacheFolder) - && Directory.Exists(configuration.CacheFolder) - && configuration.ClientSecret.ContainsKey(configuration.ApiUri); - } - - public static Dictionary GetCurrentServerUidComments(this Configuration configuration) - { - return configuration.UidServerComments.ContainsKey(configuration.ApiUri) - ? configuration.UidServerComments[configuration.ApiUri] - : new Dictionary(); - } - - public static void SetCurrentServerUidComment(this Configuration configuration, string uid, string comment) - { - if (!configuration.UidServerComments.ContainsKey(configuration.ApiUri)) - { - configuration.UidServerComments[configuration.ApiUri] = new Dictionary(); - } - - configuration.UidServerComments[configuration.ApiUri][uid] = comment; - } + return configuration.AcceptedAgreement && configuration.InitialScanComplete + && !string.IsNullOrEmpty(configuration.CacheFolder) + && Directory.Exists(configuration.CacheFolder) + && configuration.ClientSecret.ContainsKey(configuration.ApiUri); } - [Serializable] - public class Configuration : IPluginConfiguration + public static Dictionary GetCurrentServerUidComments(this Configuration configuration) { - private string _apiUri = string.Empty; - private int _maxParallelScan = 10; - [NonSerialized] - private DalamudPluginInterface? _pluginInterface; + return configuration.UidServerComments.ContainsKey(configuration.ApiUri) + ? configuration.UidServerComments[configuration.ApiUri] + : new Dictionary(); + } - public bool DarkSoulsAgreement { get; set; } = false; - public bool AcceptedAgreement { get; set; } = false; - public string ApiUri + public static void SetCurrentServerUidComment(this Configuration configuration, string uid, string comment) + { + if (!configuration.UidServerComments.ContainsKey(configuration.ApiUri)) { - get => string.IsNullOrEmpty(_apiUri) ? ApiController.MainServiceUri : _apiUri; - set => _apiUri = value; + configuration.UidServerComments[configuration.ApiUri] = new Dictionary(); } - public string CacheFolder { get; set; } = string.Empty; - public Dictionary ClientSecret { get; set; } = new(); - public Dictionary CustomServerList { get; set; } = new(); - public int MaxLocalCacheInGiB { get; set; } = 20; - public bool ReverseUserSort { get; set; } = true; + configuration.UidServerComments[configuration.ApiUri][uid] = comment; + } +} - public int TimeSpanBetweenScansInSeconds { get; set; } = 30; - public bool FileScanPaused { get; set; } = false; +[Serializable] +public class Configuration : IPluginConfiguration +{ + private string _apiUri = string.Empty; + private int _maxParallelScan = 10; + [NonSerialized] + private DalamudPluginInterface? _pluginInterface; - public bool InitialScanComplete { get; set; } = false; + public bool DarkSoulsAgreement { get; set; } = false; + public bool AcceptedAgreement { get; set; } = false; + public string ApiUri + { + get => string.IsNullOrEmpty(_apiUri) ? ApiController.MainServiceUri : _apiUri; + set => _apiUri = value; + } - public bool FullPause { get; set; } = false; - public Dictionary> UidServerComments { get; set; } = new(); + public string CacheFolder { get; set; } = string.Empty; + public Dictionary ClientSecret { get; set; } = new(); + public Dictionary CustomServerList { get; set; } = new(); + public int MaxLocalCacheInGiB { get; set; } = 20; + public bool ReverseUserSort { get; set; } = true; - public Dictionary UidComments { get; set; } = new(); - public int Version { get; set; } = 5; + public int TimeSpanBetweenScansInSeconds { get; set; } = 30; + public bool FileScanPaused { get; set; } = false; - public bool ShowTransferWindow { get; set; } = true; + public bool InitialScanComplete { get; set; } = false; - // the below exist just to make saving less cumbersome - public void Initialize(DalamudPluginInterface pluginInterface) + public bool FullPause { get; set; } = false; + public Dictionary> UidServerComments { get; set; } = new(); + + public Dictionary UidComments { get; set; } = new(); + public int Version { get; set; } = 5; + + public bool ShowTransferWindow { get; set; } = true; + + // the below exist just to make saving less cumbersome + public void Initialize(DalamudPluginInterface pluginInterface) + { + _pluginInterface = pluginInterface; + + if (!Directory.Exists(CacheFolder)) { - _pluginInterface = pluginInterface; + InitialScanComplete = false; + } - if (!Directory.Exists(CacheFolder)) + Save(); + } + + public void Save() + { + _pluginInterface!.SavePluginConfig(this); + } + + public void Migrate() + { + if (Version == 0) + { + Logger.Debug("Migrating Configuration from V0 to V1"); + Version = 1; + ApiUri = ApiUri.Replace("https", "wss"); + foreach (var kvp in ClientSecret.ToList()) { - InitialScanComplete = false; + var newKey = kvp.Key.Replace("https", "wss"); + ClientSecret.Remove(kvp.Key); + if (ClientSecret.ContainsKey(newKey)) + { + ClientSecret[newKey] = kvp.Value; + } + else + { + ClientSecret.Add(newKey, kvp.Value); + } } - + UidServerComments.Add(ApiUri, UidComments.ToDictionary(k => k.Key, k => k.Value)); + UidComments.Clear(); Save(); } - public void Save() + if (Version == 1) { - _pluginInterface!.SavePluginConfig(this); + Logger.Debug("Migrating Configuration from V1 to V2"); + ApiUri = ApiUri.Replace("5001", "5000"); + foreach (var kvp in ClientSecret.ToList()) + { + var newKey = kvp.Key.Replace("5001", "5000"); + ClientSecret.Remove(kvp.Key); + if (ClientSecret.ContainsKey(newKey)) + { + ClientSecret[newKey] = kvp.Value; + } + else + { + ClientSecret.Add(newKey, kvp.Value); + } + } + + foreach (var kvp in UidServerComments.ToList()) + { + var newKey = kvp.Key.Replace("5001", "5000"); + UidServerComments.Remove(kvp.Key); + UidServerComments.Add(newKey, kvp.Value); + } + + Version = 2; + Save(); } - public void Migrate() + if (Version == 2) { - if (Version == 0) + Logger.Debug("Migrating Configuration from V2 to V3"); + ApiUri = "wss://v2202207178628194299.powersrv.de:6871"; + ClientSecret.Clear(); + UidServerComments.Clear(); + + Version = 3; + Save(); + } + + if (Version == 3) + { + Logger.Debug("Migrating Configuration from V3 to V4"); + + ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872"); + foreach (var kvp in ClientSecret.ToList()) { - Logger.Debug("Migrating Configuration from V0 to V1"); - Version = 1; - ApiUri = ApiUri.Replace("https", "wss"); - foreach (var kvp in ClientSecret.ToList()) + var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872"); + ClientSecret.Remove(kvp.Key); + if (ClientSecret.ContainsKey(newKey)) { - var newKey = kvp.Key.Replace("https", "wss"); - ClientSecret.Remove(kvp.Key); - if (ClientSecret.ContainsKey(newKey)) - { - ClientSecret[newKey] = kvp.Value; - } - else - { - ClientSecret.Add(newKey, kvp.Value); - } + ClientSecret[newKey] = kvp.Value; + } + else + { + ClientSecret.Add(newKey, kvp.Value); } - UidServerComments.Add(ApiUri, UidComments.ToDictionary(k => k.Key, k => k.Value)); - UidComments.Clear(); - Save(); } - if (Version == 1) + foreach (var kvp in UidServerComments.ToList()) { - Logger.Debug("Migrating Configuration from V1 to V2"); - ApiUri = ApiUri.Replace("5001", "5000"); - foreach (var kvp in ClientSecret.ToList()) - { - var newKey = kvp.Key.Replace("5001", "5000"); - ClientSecret.Remove(kvp.Key); - if (ClientSecret.ContainsKey(newKey)) - { - ClientSecret[newKey] = kvp.Value; - } - else - { - ClientSecret.Add(newKey, kvp.Value); - } - } - - foreach (var kvp in UidServerComments.ToList()) - { - var newKey = kvp.Key.Replace("5001", "5000"); - UidServerComments.Remove(kvp.Key); - UidServerComments.Add(newKey, kvp.Value); - } - - Version = 2; - Save(); + var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872"); + UidServerComments.Remove(kvp.Key); + UidServerComments.Add(newKey, kvp.Value); } - if (Version == 2) - { - Logger.Debug("Migrating Configuration from V2 to V3"); - ApiUri = "wss://v2202207178628194299.powersrv.de:6871"; - ClientSecret.Clear(); - UidServerComments.Clear(); + Version = 4; + Save(); + } - Version = 3; - Save(); - } + if (Version == 4) + { + Logger.Debug("Migrating Configuration from V4 to V5"); - if (Version == 3) - { - Logger.Debug("Migrating Configuration from V3 to V4"); + ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6872", "wss://maresynchronos.com"); + ClientSecret.Remove("wss://v2202207178628194299.powersrv.de:6872"); + UidServerComments.Remove("wss://v2202207178628194299.powersrv.de:6872"); - ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872"); - foreach (var kvp in ClientSecret.ToList()) - { - var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872"); - ClientSecret.Remove(kvp.Key); - if (ClientSecret.ContainsKey(newKey)) - { - ClientSecret[newKey] = kvp.Value; - } - else - { - ClientSecret.Add(newKey, kvp.Value); - } - } - - foreach (var kvp in UidServerComments.ToList()) - { - var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872"); - UidServerComments.Remove(kvp.Key); - UidServerComments.Add(newKey, kvp.Value); - } - - Version = 4; - Save(); - } - - if (Version == 4) - { - Logger.Debug("Migrating Configuration from V4 to V5"); - - ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6872", "wss://maresynchronos.com"); - ClientSecret.Remove("wss://v2202207178628194299.powersrv.de:6872"); - UidServerComments.Remove("wss://v2202207178628194299.powersrv.de:6872"); - - Version = 5; - Save(); - } + Version = 5; + Save(); } } } diff --git a/MareSynchronos/Interop/Weapon.cs b/MareSynchronos/Interop/Weapon.cs index 5670471..b02cbf2 100644 --- a/MareSynchronos/Interop/Weapon.cs +++ b/MareSynchronos/Interop/Weapon.cs @@ -4,35 +4,34 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using Penumbra.Interop.Structs; -namespace MareSynchronos.Interop +namespace MareSynchronos.Interop; + +[StructLayout(LayoutKind.Explicit)] +public unsafe struct Weapon { - [StructLayout(LayoutKind.Explicit)] - public unsafe struct Weapon - { - [FieldOffset(0x18)] public IntPtr Parent; - [FieldOffset(0x20)] public IntPtr NextSibling; - [FieldOffset(0x28)] public IntPtr PreviousSibling; - [FieldOffset(0xA8)] public WeaponDrawObject* WeaponRenderModel; - } - - [StructLayout(LayoutKind.Explicit)] - public unsafe struct WeaponDrawObject - { - [FieldOffset(0x00)] public RenderModel* RenderModel; - } - - [StructLayout(LayoutKind.Explicit)] - public unsafe struct HumanExt - { - [FieldOffset(0x0)] public Human Human; - [FieldOffset(0x9E8)] public ResourceHandle* Decal; - [FieldOffset(0x9F0)] public ResourceHandle* LegacyBodyDecal; - } - - [StructLayout(LayoutKind.Explicit)] - public unsafe struct CharaExt - { - [FieldOffset(0x0)] public Character Character; - [FieldOffset(0x650)] public Character* Mount; - } + [FieldOffset(0x18)] public IntPtr Parent; + [FieldOffset(0x20)] public IntPtr NextSibling; + [FieldOffset(0x28)] public IntPtr PreviousSibling; + [FieldOffset(0xA8)] public WeaponDrawObject* WeaponRenderModel; +} + +[StructLayout(LayoutKind.Explicit)] +public unsafe struct WeaponDrawObject +{ + [FieldOffset(0x00)] public RenderModel* RenderModel; +} + +[StructLayout(LayoutKind.Explicit)] +public unsafe struct HumanExt +{ + [FieldOffset(0x0)] public Human Human; + [FieldOffset(0x9E8)] public ResourceHandle* Decal; + [FieldOffset(0x9F0)] public ResourceHandle* LegacyBodyDecal; +} + +[StructLayout(LayoutKind.Explicit)] +public unsafe struct CharaExt +{ + [FieldOffset(0x0)] public Character Character; + [FieldOffset(0x650)] public Character* Mount; } diff --git a/MareSynchronos/Localization/Strings.cs b/MareSynchronos/Localization/Strings.cs index e55f8e8..4f1da9c 100644 --- a/MareSynchronos/Localization/Strings.cs +++ b/MareSynchronos/Localization/Strings.cs @@ -1,68 +1,67 @@ using CheapLoc; -namespace MareSynchronos.Localization +namespace MareSynchronos.Localization; + +public static class Strings { - public static class Strings + public class ToSStrings { - public class ToSStrings - { - public readonly string LanguageLabel = Loc.Localize("LanguageLabel", "Language"); - public readonly string AgreementLabel = Loc.Localize("AgreementLabel", "Agreement of Usage of Service"); - public readonly string ReadLabel = Loc.Localize("ReadLabel", "READ THIS CAREFULLY"); + public readonly string LanguageLabel = Loc.Localize("LanguageLabel", "Language"); + public readonly string AgreementLabel = Loc.Localize("AgreementLabel", "Agreement of Usage of Service"); + public readonly string ReadLabel = Loc.Localize("ReadLabel", "READ THIS CAREFULLY"); - public readonly string Paragraph1 = Loc.Localize("Paragraph1", - "All of the mod files currently active on your character as well as your current character state will be uploaded to the service you registered yourself at automatically. " + - "The plugin will exclusively upload the necessary mod files and not the whole mod."); + public readonly string Paragraph1 = Loc.Localize("Paragraph1", + "All of the mod files currently active on your character as well as your current character state will be uploaded to the service you registered yourself at automatically. " + + "The plugin will exclusively upload the necessary mod files and not the whole mod."); - public readonly string Paragraph2 = Loc.Localize("Paragraph2", - "If you are on a data capped internet connection, higher fees due to data usage depending on the amount of downloaded and uploaded mod files might occur. " + - "Mod files will be compressed on up- and download to save on bandwidth usage. Due to varying up- and download speeds, changes in characters might not be visible immediately. " + - "Files present on the service that already represent your active mod files will not be uploaded again."); + public readonly string Paragraph2 = Loc.Localize("Paragraph2", + "If you are on a data capped internet connection, higher fees due to data usage depending on the amount of downloaded and uploaded mod files might occur. " + + "Mod files will be compressed on up- and download to save on bandwidth usage. Due to varying up- and download speeds, changes in characters might not be visible immediately. " + + "Files present on the service that already represent your active mod files will not be uploaded again."); - public readonly string Paragraph3 = Loc.Localize("Paragraph3", - "The mod files you are uploading are confidential and will not be distributed to parties other than the ones who are requesting the exact same mod files. " + - "Please think about who you are going to pair since it is unavoidable that they will receive and locally cache the necessary mod files that you have currently in use. " + - "Locally cached mod files will have arbitrary file names to discourage attempts at replicating the original mod."); + public readonly string Paragraph3 = Loc.Localize("Paragraph3", + "The mod files you are uploading are confidential and will not be distributed to parties other than the ones who are requesting the exact same mod files. " + + "Please think about who you are going to pair since it is unavoidable that they will receive and locally cache the necessary mod files that you have currently in use. " + + "Locally cached mod files will have arbitrary file names to discourage attempts at replicating the original mod."); - public readonly string Paragraph4 = Loc.Localize("Paragraph4", - "The plugin creator tried their best to keep you secure. However, there is no guarantee for 100% security. Do not blindly pair your client with everyone."); + public readonly string Paragraph4 = Loc.Localize("Paragraph4", + "The plugin creator tried their best to keep you secure. However, there is no guarantee for 100% security. Do not blindly pair your client with everyone."); - public readonly string Paragraph5 = Loc.Localize("Paragraph5", - "Mod files that are saved on the service will remain on the service as long as there are requests for the files from clients. " + - "After a period of not being used, the mod files will be automatically deleted. " + - "You will also be able to wipe all the files you have personally uploaded on request. " + - "The service holds no information about which mod files belong to which mod."); + public readonly string Paragraph5 = Loc.Localize("Paragraph5", + "Mod files that are saved on the service will remain on the service as long as there are requests for the files from clients. " + + "After a period of not being used, the mod files will be automatically deleted. " + + "You will also be able to wipe all the files you have personally uploaded on request. " + + "The service holds no information about which mod files belong to which mod."); - public readonly string Paragraph6 = Loc.Localize("Paragraph6", - "This service is provided as-is. In case of abuse, contact darkarchon#4313 on Discord or join the Mare Synchronos Discord. " + - "To accept those conditions hold CTRL while clicking 'I agree'"); + public readonly string Paragraph6 = Loc.Localize("Paragraph6", + "This service is provided as-is. In case of abuse, contact darkarchon#4313 on Discord or join the Mare Synchronos Discord. " + + "To accept those conditions hold CTRL while clicking 'I agree'"); - public readonly string AgreeLabel = Loc.Localize("AgreeLabel", "I agree"); + public readonly string AgreeLabel = Loc.Localize("AgreeLabel", "I agree"); - public readonly string RemainingLabel = Loc.Localize("RemainingLabel", "remaining"); + public readonly string RemainingLabel = Loc.Localize("RemainingLabel", "remaining"); - public readonly string FailedLabel = Loc.Localize("FailedLabel", - "Congratulations. You have failed to read the agreements."); + public readonly string FailedLabel = Loc.Localize("FailedLabel", + "Congratulations. You have failed to read the agreements."); - public readonly string TimeoutLabel = Loc.Localize("TimeoutLabel", - "I'm going to give you 1 minute to read the agreements carefully again. If you fail once more you will have to solve an annoying puzzle."); + public readonly string TimeoutLabel = Loc.Localize("TimeoutLabel", + "I'm going to give you 1 minute to read the agreements carefully again. If you fail once more you will have to solve an annoying puzzle."); - public readonly string FailedAgainLabel = Loc.Localize("FailedAgainLabel", - "Congratulations. You have failed to read the agreements. Again."); + public readonly string FailedAgainLabel = Loc.Localize("FailedAgainLabel", + "Congratulations. You have failed to read the agreements. Again."); - public readonly string PuzzleLabel = Loc.Localize("PuzzleLabel", - "I did warn you. Here's your annoying puzzle:"); + public readonly string PuzzleLabel = Loc.Localize("PuzzleLabel", + "I did warn you. Here's your annoying puzzle:"); - public readonly string PuzzleDescLabel = Loc.Localize("PuzzleDescLabel", - "Enter the following 3 words from the agreement exactly as described without punctuation to make the \"I agree\" button visible again."); + public readonly string PuzzleDescLabel = Loc.Localize("PuzzleDescLabel", + "Enter the following 3 words from the agreement exactly as described without punctuation to make the \"I agree\" button visible again."); - public readonly string ParagraphLabel = Loc.Localize("ParagraphLabel", "Paragraph"); + public readonly string ParagraphLabel = Loc.Localize("ParagraphLabel", "Paragraph"); - public readonly string SentenceLabel = Loc.Localize("SentenceLabel", "Sentence"); + public readonly string SentenceLabel = Loc.Localize("SentenceLabel", "Sentence"); - public readonly string WordLabel = Loc.Localize("WordLabel", "Word"); - } - - public static ToSStrings ToS { get; set; } = new(); + public readonly string WordLabel = Loc.Localize("WordLabel", "Word"); } + + public static ToSStrings ToS { get; set; } = new(); } \ No newline at end of file diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index 47186af..e7c5882 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -8,394 +8,393 @@ using MareSynchronos.WebAPI; using Action = System.Action; using System.Collections.Concurrent; -namespace MareSynchronos.Managers +namespace MareSynchronos.Managers; + +public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx); +public delegate void HeelsOffsetChange(float change); +public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath); +public class IpcManager : IDisposable { - public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx); - public delegate void HeelsOffsetChange(float change); - public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath); - public class IpcManager : IDisposable + private readonly ICallGateSubscriber _glamourerApiVersion; + private readonly ICallGateSubscriber? _glamourerApplyAll; + private readonly ICallGateSubscriber? _glamourerGetAllCustomization; + private readonly ICallGateSubscriber _glamourerRevertCustomization; + private readonly ICallGateSubscriber? _glamourerApplyOnlyEquipment; + private readonly ICallGateSubscriber? _glamourerApplyOnlyCustomization; + private readonly ICallGateSubscriber<(int, int)> _penumbraApiVersion; + private readonly ICallGateSubscriber _penumbraCreateTemporaryCollection; + private readonly ICallGateSubscriber _penumbraGetMetaManipulations; + private readonly ICallGateSubscriber _penumbraInit; + private readonly ICallGateSubscriber _penumbraDispose; + private readonly ICallGateSubscriber _penumbraObjectIsRedrawn; + private readonly ICallGateSubscriber? _penumbraRedraw; + private readonly ICallGateSubscriber? _penumbraRedrawObject; + private readonly ICallGateSubscriber _penumbraRemoveTemporaryCollection; + private readonly ICallGateSubscriber? _penumbraResolveModDir; + private readonly ICallGateSubscriber? _penumbraResolvePlayer; + private readonly ICallGateSubscriber? _reverseResolvePlayer; + private readonly ICallGateSubscriber, string, int, int> + _penumbraSetTemporaryMod; + private readonly ICallGateSubscriber _penumbraGameObjectResourcePathResolved; + + private readonly ICallGateSubscriber _heelsGetApiVersion; + private readonly ICallGateSubscriber _heelsGetOffset; + private readonly ICallGateSubscriber _heelsOffsetUpdate; + private readonly ICallGateSubscriber _heelsRegisterPlayer; + private readonly ICallGateSubscriber _heelsUnregisterPlayer; + + private readonly DalamudUtil _dalamudUtil; + private readonly ConcurrentQueue actionQueue = new(); + + public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil) { - private readonly ICallGateSubscriber _glamourerApiVersion; - private readonly ICallGateSubscriber? _glamourerApplyAll; - private readonly ICallGateSubscriber? _glamourerGetAllCustomization; - private readonly ICallGateSubscriber _glamourerRevertCustomization; - private readonly ICallGateSubscriber? _glamourerApplyOnlyEquipment; - private readonly ICallGateSubscriber? _glamourerApplyOnlyCustomization; - private readonly ICallGateSubscriber<(int, int)> _penumbraApiVersion; - private readonly ICallGateSubscriber _penumbraCreateTemporaryCollection; - private readonly ICallGateSubscriber _penumbraGetMetaManipulations; - private readonly ICallGateSubscriber _penumbraInit; - private readonly ICallGateSubscriber _penumbraDispose; - private readonly ICallGateSubscriber _penumbraObjectIsRedrawn; - private readonly ICallGateSubscriber? _penumbraRedraw; - private readonly ICallGateSubscriber? _penumbraRedrawObject; - private readonly ICallGateSubscriber _penumbraRemoveTemporaryCollection; - private readonly ICallGateSubscriber? _penumbraResolveModDir; - private readonly ICallGateSubscriber? _penumbraResolvePlayer; - private readonly ICallGateSubscriber? _reverseResolvePlayer; - private readonly ICallGateSubscriber, string, int, int> - _penumbraSetTemporaryMod; - private readonly ICallGateSubscriber _penumbraGameObjectResourcePathResolved; + Logger.Verbose("Creating " + nameof(IpcManager)); - private readonly ICallGateSubscriber _heelsGetApiVersion; - private readonly ICallGateSubscriber _heelsGetOffset; - private readonly ICallGateSubscriber _heelsOffsetUpdate; - private readonly ICallGateSubscriber _heelsRegisterPlayer; - private readonly ICallGateSubscriber _heelsUnregisterPlayer; + _penumbraInit = pi.GetIpcSubscriber("Penumbra.Initialized"); + _penumbraDispose = pi.GetIpcSubscriber("Penumbra.Disposed"); + _penumbraResolvePlayer = pi.GetIpcSubscriber("Penumbra.ResolvePlayerPath"); + _penumbraResolveModDir = pi.GetIpcSubscriber("Penumbra.GetModDirectory"); + _penumbraRedraw = pi.GetIpcSubscriber("Penumbra.RedrawObjectByName"); + _penumbraRedrawObject = pi.GetIpcSubscriber("Penumbra.RedrawObject"); + _reverseResolvePlayer = pi.GetIpcSubscriber("Penumbra.ReverseResolvePlayerPath"); + _penumbraApiVersion = pi.GetIpcSubscriber<(int, int)>("Penumbra.ApiVersions"); + _penumbraObjectIsRedrawn = pi.GetIpcSubscriber("Penumbra.GameObjectRedrawn"); + _penumbraGetMetaManipulations = + pi.GetIpcSubscriber("Penumbra.GetPlayerMetaManipulations"); + _penumbraSetTemporaryMod = pi.GetIpcSubscriber, string, int, + int>("Penumbra.AddTemporaryMod"); + _penumbraCreateTemporaryCollection = + pi.GetIpcSubscriber("Penumbra.CreateTemporaryCollection"); + _penumbraRemoveTemporaryCollection = + pi.GetIpcSubscriber("Penumbra.RemoveTemporaryCollection"); + _penumbraGameObjectResourcePathResolved = pi.GetIpcSubscriber("Penumbra.GameObjectResourcePathResolved"); - private readonly DalamudUtil _dalamudUtil; - private readonly ConcurrentQueue actionQueue = new(); + _penumbraGameObjectResourcePathResolved.Subscribe(ResourceLoaded); + _penumbraObjectIsRedrawn.Subscribe(RedrawEvent); + _penumbraInit.Subscribe(PenumbraInit); + _penumbraDispose.Subscribe(PenumbraDispose); - public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil) - { - Logger.Verbose("Creating " + nameof(IpcManager)); + _glamourerApiVersion = pi.GetIpcSubscriber("Glamourer.ApiVersion"); + _glamourerGetAllCustomization = pi.GetIpcSubscriber("Glamourer.GetAllCustomizationFromCharacter"); + _glamourerApplyAll = pi.GetIpcSubscriber("Glamourer.ApplyAllToCharacter"); + _glamourerApplyOnlyCustomization = pi.GetIpcSubscriber("Glamourer.ApplyOnlyCustomizationToCharacter"); + _glamourerApplyOnlyEquipment = pi.GetIpcSubscriber("Glamourer.ApplyOnlyEquipmentToCharacter"); + _glamourerRevertCustomization = pi.GetIpcSubscriber("Glamourer.RevertCharacter"); - _penumbraInit = pi.GetIpcSubscriber("Penumbra.Initialized"); - _penumbraDispose = pi.GetIpcSubscriber("Penumbra.Disposed"); - _penumbraResolvePlayer = pi.GetIpcSubscriber("Penumbra.ResolvePlayerPath"); - _penumbraResolveModDir = pi.GetIpcSubscriber("Penumbra.GetModDirectory"); - _penumbraRedraw = pi.GetIpcSubscriber("Penumbra.RedrawObjectByName"); - _penumbraRedrawObject = pi.GetIpcSubscriber("Penumbra.RedrawObject"); - _reverseResolvePlayer = pi.GetIpcSubscriber("Penumbra.ReverseResolvePlayerPath"); - _penumbraApiVersion = pi.GetIpcSubscriber<(int, int)>("Penumbra.ApiVersions"); - _penumbraObjectIsRedrawn = pi.GetIpcSubscriber("Penumbra.GameObjectRedrawn"); - _penumbraGetMetaManipulations = - pi.GetIpcSubscriber("Penumbra.GetPlayerMetaManipulations"); - _penumbraSetTemporaryMod = pi.GetIpcSubscriber, string, int, - int>("Penumbra.AddTemporaryMod"); - _penumbraCreateTemporaryCollection = - pi.GetIpcSubscriber("Penumbra.CreateTemporaryCollection"); - _penumbraRemoveTemporaryCollection = - pi.GetIpcSubscriber("Penumbra.RemoveTemporaryCollection"); - _penumbraGameObjectResourcePathResolved = pi.GetIpcSubscriber("Penumbra.GameObjectResourcePathResolved"); + _heelsGetApiVersion = pi.GetIpcSubscriber("HeelsPlugin.ApiVersion"); + _heelsGetOffset = pi.GetIpcSubscriber("HeelsPlugin.GetOffset"); + _heelsRegisterPlayer = pi.GetIpcSubscriber("HeelsPlugin.RegisterPlayer"); + _heelsUnregisterPlayer = pi.GetIpcSubscriber("HeelsPlugin.UnregisterPlayer"); + _heelsOffsetUpdate = pi.GetIpcSubscriber("HeelsPlugin.OffsetChanged"); - _penumbraGameObjectResourcePathResolved.Subscribe(ResourceLoaded); - _penumbraObjectIsRedrawn.Subscribe(RedrawEvent); - _penumbraInit.Subscribe(PenumbraInit); - _penumbraDispose.Subscribe(PenumbraDispose); + _heelsOffsetUpdate.Subscribe(HeelsOffsetChange); - _glamourerApiVersion = pi.GetIpcSubscriber("Glamourer.ApiVersion"); - _glamourerGetAllCustomization = pi.GetIpcSubscriber("Glamourer.GetAllCustomizationFromCharacter"); - _glamourerApplyAll = pi.GetIpcSubscriber("Glamourer.ApplyAllToCharacter"); - _glamourerApplyOnlyCustomization = pi.GetIpcSubscriber("Glamourer.ApplyOnlyCustomizationToCharacter"); - _glamourerApplyOnlyEquipment = pi.GetIpcSubscriber("Glamourer.ApplyOnlyEquipmentToCharacter"); - _glamourerRevertCustomization = pi.GetIpcSubscriber("Glamourer.RevertCharacter"); - - _heelsGetApiVersion = pi.GetIpcSubscriber("HeelsPlugin.ApiVersion"); - _heelsGetOffset = pi.GetIpcSubscriber("HeelsPlugin.GetOffset"); - _heelsRegisterPlayer = pi.GetIpcSubscriber("HeelsPlugin.RegisterPlayer"); - _heelsUnregisterPlayer = pi.GetIpcSubscriber("HeelsPlugin.UnregisterPlayer"); - _heelsOffsetUpdate = pi.GetIpcSubscriber("HeelsPlugin.OffsetChanged"); - - _heelsOffsetUpdate.Subscribe(HeelsOffsetChange); - - if (Initialized) - { - PenumbraInitialized?.Invoke(); - } - - _dalamudUtil = dalamudUtil; - _dalamudUtil.FrameworkUpdate += HandleActionQueue; - _dalamudUtil.ZoneSwitchEnd += ClearActionQueue; - } - - private void ClearActionQueue() - { - actionQueue.Clear(); - } - - private void ResourceLoaded(IntPtr ptr, string arg1, string arg2) - { - if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, true, System.Globalization.CultureInfo.InvariantCulture) != 0) - { - PenumbraResourceLoadEvent?.Invoke(ptr, arg1, arg2); - //Logger.Debug($"Resolved {ptr:X}: {arg1} => {arg2}"); - } - } - - private void HandleActionQueue() - { - if (actionQueue.TryDequeue(out var action)) - { - if (action == null) return; - Logger.Debug("Execution action in queue: " + action.Method); - action(); - } - } - - public event VoidDelegate? PenumbraInitialized; - public event VoidDelegate? PenumbraDisposed; - public event PenumbraRedrawEvent? PenumbraRedrawEvent; - public event HeelsOffsetChange? HeelsOffsetChangeEvent; - public event PenumbraResourceLoadEvent? PenumbraResourceLoadEvent; - - public bool Initialized => CheckPenumbraApi(); - public bool CheckGlamourerApi() - { - try - { - return _glamourerApiVersion.InvokeFunc() >= 0; - } - catch - { - return false; - } - } - - public bool CheckPenumbraApi() - { - try - { - return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >= 13 }; - } - catch - { - return false; - } - } - - public bool CheckHeelsApi() - { - try - { - return _heelsGetApiVersion.InvokeFunc() == "1.0.1"; - } - catch - { - return false; - } - } - - public void Dispose() - { - Logger.Verbose("Disposing " + nameof(IpcManager)); - - int totalSleepTime = 0; - while (actionQueue.Count > 0 && totalSleepTime < 2000) - { - Logger.Verbose("Waiting for actionqueue to clear..."); - HandleActionQueue(); - System.Threading.Thread.Sleep(16); - totalSleepTime += 16; - } - - if (totalSleepTime >= 2000) - { - Logger.Verbose("Action queue clear or not, disposing"); - } - - _dalamudUtil.FrameworkUpdate -= HandleActionQueue; - _dalamudUtil.ZoneSwitchEnd -= ClearActionQueue; - actionQueue.Clear(); - - _penumbraDispose.Unsubscribe(PenumbraDispose); - _penumbraInit.Unsubscribe(PenumbraInit); - _penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent); - _penumbraGameObjectResourcePathResolved.Unsubscribe(ResourceLoaded); - _heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange); - } - - public float GetHeelsOffset() - { - if (!CheckHeelsApi()) return 0.0f; - return _heelsGetOffset.InvokeFunc(); - } - - public void HeelsSetOffsetForPlayer(float offset, IntPtr character) - { - if (!CheckHeelsApi()) return; - actionQueue.Enqueue(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj != null) - { - Logger.Verbose("Applying Heels data to " + character.ToString("X")); - _heelsRegisterPlayer.InvokeAction(gameObj, offset); - } - }); - } - - public void HeelsRestoreOffsetForPlayer(IntPtr character) - { - if (!CheckHeelsApi()) return; - actionQueue.Enqueue(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj != null) - { - Logger.Verbose("Restoring Heels data to " + character.ToString("X")); - _heelsUnregisterPlayer.InvokeAction(gameObj); - } - }); - } - - public void GlamourerApplyAll(string? customization, IntPtr obj) - { - if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; - actionQueue.Enqueue(() => - { - var gameObj = _dalamudUtil.CreateGameObject(obj); - if (gameObj is Character c) - { - Logger.Verbose("Glamourer applying for " + c.Address.ToString("X")); - _glamourerApplyAll!.InvokeAction(customization, c); - } - }); - } - - public void GlamourerApplyOnlyEquipment(string customization, IntPtr character) - { - if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; - actionQueue.Enqueue(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj is Character c) - { - Logger.Verbose("Glamourer apply only equipment to " + c.Address.ToString("X")); - _glamourerApplyOnlyEquipment!.InvokeAction(customization, c); - } - }); - } - - public void GlamourerApplyOnlyCustomization(string customization, IntPtr character) - { - if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; - actionQueue.Enqueue(() => - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj is Character c) - { - Logger.Verbose("Glamourer apply only customization to " + c.Address.ToString("X")); - _glamourerApplyOnlyCustomization!.InvokeAction(customization, c); - } - }); - } - - public string GlamourerGetCharacterCustomization(IntPtr character) - { - if (!CheckGlamourerApi()) return string.Empty; - try - { - var gameObj = _dalamudUtil.CreateGameObject(character); - if (gameObj is Character c) - { - var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(c); - byte[] bytes = Convert.FromBase64String(glamourerString); - // ignore transparency - bytes[88] = 128; - bytes[89] = 63; - return Convert.ToBase64String(bytes); - } - return string.Empty; - } - catch - { - return string.Empty; - } - } - - public void GlamourerRevertCharacterCustomization(GameObject character) - { - if (!CheckGlamourerApi()) return; - actionQueue.Enqueue(() => _glamourerRevertCustomization!.InvokeAction(character)); - } - - public string PenumbraGetMetaManipulations() - { - if (!CheckPenumbraApi()) return string.Empty; - return _penumbraGetMetaManipulations.InvokeFunc(); - } - - public string? PenumbraModDirectory() - { - if (!CheckPenumbraApi()) return null; - return _penumbraResolveModDir!.InvokeFunc().ToLowerInvariant(); - } - - public void PenumbraRedraw(IntPtr obj) - { - if (!CheckPenumbraApi()) return; - actionQueue.Enqueue(() => - { - var gameObj = _dalamudUtil.CreateGameObject(obj); - if (gameObj != null) - { - Logger.Verbose("Redrawing " + gameObj); - _penumbraRedrawObject!.InvokeAction(gameObj, 0); - } - }); - } - - public void PenumbraRedraw(string actorName) - { - if (!CheckPenumbraApi()) return; - actionQueue.Enqueue(() => _penumbraRedraw!.InvokeAction(actorName, 0)); - } - - public void PenumbraRemoveTemporaryCollection(string characterName) - { - if (!CheckPenumbraApi()) return; - actionQueue.Enqueue(() => - { - Logger.Verbose("Removing temp collection for " + characterName); - _penumbraRemoveTemporaryCollection.InvokeFunc(characterName); - }); - } - - public string PenumbraResolvePath(string path) - { - if (!CheckPenumbraApi()) return path; - var resolvedPath = _penumbraResolvePlayer!.InvokeFunc(path); - return resolvedPath ?? path; - } - - public string[] PenumbraReverseResolvePlayer(string path) - { - if (!CheckPenumbraApi()) return new[] { path }; - var resolvedPaths = _reverseResolvePlayer!.InvokeFunc(path); - if (resolvedPaths.Length == 0) - { - resolvedPaths = new[] { path }; - } - return resolvedPaths; - } - - public void PenumbraSetTemporaryMods(string characterName, Dictionary modPaths, string manipulationData) - { - if (!CheckPenumbraApi()) return; - - actionQueue.Enqueue(() => - { - var ret = _penumbraCreateTemporaryCollection.InvokeFunc("MareSynchronos", characterName, true); - Logger.Verbose("Assigning temp mods for " + ret.Item2); - foreach (var mod in modPaths) - { - Logger.Verbose(mod.Key + " => " + mod.Value); - } - _penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", ret.Item2, modPaths, manipulationData, 0); - }); - } - - private void RedrawEvent(IntPtr objectAddress, int objectTableIndex) - { - PenumbraRedrawEvent?.Invoke(objectAddress, objectTableIndex); - } - - private void PenumbraInit() + if (Initialized) { PenumbraInitialized?.Invoke(); - _penumbraRedraw!.InvokeAction("self", 0); } - private void HeelsOffsetChange(float offset) - { - HeelsOffsetChangeEvent?.Invoke(offset); - } + _dalamudUtil = dalamudUtil; + _dalamudUtil.FrameworkUpdate += HandleActionQueue; + _dalamudUtil.ZoneSwitchEnd += ClearActionQueue; + } - private void PenumbraDispose() + private void ClearActionQueue() + { + actionQueue.Clear(); + } + + private void ResourceLoaded(IntPtr ptr, string arg1, string arg2) + { + if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, true, System.Globalization.CultureInfo.InvariantCulture) != 0) { - PenumbraDisposed?.Invoke(); - actionQueue.Clear(); + PenumbraResourceLoadEvent?.Invoke(ptr, arg1, arg2); + //Logger.Debug($"Resolved {ptr:X}: {arg1} => {arg2}"); } } + + private void HandleActionQueue() + { + if (actionQueue.TryDequeue(out var action)) + { + if (action == null) return; + Logger.Debug("Execution action in queue: " + action.Method); + action(); + } + } + + public event VoidDelegate? PenumbraInitialized; + public event VoidDelegate? PenumbraDisposed; + public event PenumbraRedrawEvent? PenumbraRedrawEvent; + public event HeelsOffsetChange? HeelsOffsetChangeEvent; + public event PenumbraResourceLoadEvent? PenumbraResourceLoadEvent; + + public bool Initialized => CheckPenumbraApi(); + public bool CheckGlamourerApi() + { + try + { + return _glamourerApiVersion.InvokeFunc() >= 0; + } + catch + { + return false; + } + } + + public bool CheckPenumbraApi() + { + try + { + return _penumbraApiVersion.InvokeFunc() is { Item1: 4, Item2: >= 13 }; + } + catch + { + return false; + } + } + + public bool CheckHeelsApi() + { + try + { + return _heelsGetApiVersion.InvokeFunc() == "1.0.1"; + } + catch + { + return false; + } + } + + public void Dispose() + { + Logger.Verbose("Disposing " + nameof(IpcManager)); + + int totalSleepTime = 0; + while (actionQueue.Count > 0 && totalSleepTime < 2000) + { + Logger.Verbose("Waiting for actionqueue to clear..."); + HandleActionQueue(); + System.Threading.Thread.Sleep(16); + totalSleepTime += 16; + } + + if (totalSleepTime >= 2000) + { + Logger.Verbose("Action queue clear or not, disposing"); + } + + _dalamudUtil.FrameworkUpdate -= HandleActionQueue; + _dalamudUtil.ZoneSwitchEnd -= ClearActionQueue; + actionQueue.Clear(); + + _penumbraDispose.Unsubscribe(PenumbraDispose); + _penumbraInit.Unsubscribe(PenumbraInit); + _penumbraObjectIsRedrawn.Unsubscribe(RedrawEvent); + _penumbraGameObjectResourcePathResolved.Unsubscribe(ResourceLoaded); + _heelsOffsetUpdate.Unsubscribe(HeelsOffsetChange); + } + + public float GetHeelsOffset() + { + if (!CheckHeelsApi()) return 0.0f; + return _heelsGetOffset.InvokeFunc(); + } + + public void HeelsSetOffsetForPlayer(float offset, IntPtr character) + { + if (!CheckHeelsApi()) return; + actionQueue.Enqueue(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj != null) + { + Logger.Verbose("Applying Heels data to " + character.ToString("X")); + _heelsRegisterPlayer.InvokeAction(gameObj, offset); + } + }); + } + + public void HeelsRestoreOffsetForPlayer(IntPtr character) + { + if (!CheckHeelsApi()) return; + actionQueue.Enqueue(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj != null) + { + Logger.Verbose("Restoring Heels data to " + character.ToString("X")); + _heelsUnregisterPlayer.InvokeAction(gameObj); + } + }); + } + + public void GlamourerApplyAll(string? customization, IntPtr obj) + { + if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; + actionQueue.Enqueue(() => + { + var gameObj = _dalamudUtil.CreateGameObject(obj); + if (gameObj is Character c) + { + Logger.Verbose("Glamourer applying for " + c.Address.ToString("X")); + _glamourerApplyAll!.InvokeAction(customization, c); + } + }); + } + + public void GlamourerApplyOnlyEquipment(string customization, IntPtr character) + { + if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; + actionQueue.Enqueue(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + Logger.Verbose("Glamourer apply only equipment to " + c.Address.ToString("X")); + _glamourerApplyOnlyEquipment!.InvokeAction(customization, c); + } + }); + } + + public void GlamourerApplyOnlyCustomization(string customization, IntPtr character) + { + if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return; + actionQueue.Enqueue(() => + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + Logger.Verbose("Glamourer apply only customization to " + c.Address.ToString("X")); + _glamourerApplyOnlyCustomization!.InvokeAction(customization, c); + } + }); + } + + public string GlamourerGetCharacterCustomization(IntPtr character) + { + if (!CheckGlamourerApi()) return string.Empty; + try + { + var gameObj = _dalamudUtil.CreateGameObject(character); + if (gameObj is Character c) + { + var glamourerString = _glamourerGetAllCustomization!.InvokeFunc(c); + byte[] bytes = Convert.FromBase64String(glamourerString); + // ignore transparency + bytes[88] = 128; + bytes[89] = 63; + return Convert.ToBase64String(bytes); + } + return string.Empty; + } + catch + { + return string.Empty; + } + } + + public void GlamourerRevertCharacterCustomization(GameObject character) + { + if (!CheckGlamourerApi()) return; + actionQueue.Enqueue(() => _glamourerRevertCustomization!.InvokeAction(character)); + } + + public string PenumbraGetMetaManipulations() + { + if (!CheckPenumbraApi()) return string.Empty; + return _penumbraGetMetaManipulations.InvokeFunc(); + } + + public string? PenumbraModDirectory() + { + if (!CheckPenumbraApi()) return null; + return _penumbraResolveModDir!.InvokeFunc().ToLowerInvariant(); + } + + public void PenumbraRedraw(IntPtr obj) + { + if (!CheckPenumbraApi()) return; + actionQueue.Enqueue(() => + { + var gameObj = _dalamudUtil.CreateGameObject(obj); + if (gameObj != null) + { + Logger.Verbose("Redrawing " + gameObj); + _penumbraRedrawObject!.InvokeAction(gameObj, 0); + } + }); + } + + public void PenumbraRedraw(string actorName) + { + if (!CheckPenumbraApi()) return; + actionQueue.Enqueue(() => _penumbraRedraw!.InvokeAction(actorName, 0)); + } + + public void PenumbraRemoveTemporaryCollection(string characterName) + { + if (!CheckPenumbraApi()) return; + actionQueue.Enqueue(() => + { + Logger.Verbose("Removing temp collection for " + characterName); + _penumbraRemoveTemporaryCollection.InvokeFunc(characterName); + }); + } + + public string PenumbraResolvePath(string path) + { + if (!CheckPenumbraApi()) return path; + var resolvedPath = _penumbraResolvePlayer!.InvokeFunc(path); + return resolvedPath ?? path; + } + + public string[] PenumbraReverseResolvePlayer(string path) + { + if (!CheckPenumbraApi()) return new[] { path }; + var resolvedPaths = _reverseResolvePlayer!.InvokeFunc(path); + if (resolvedPaths.Length == 0) + { + resolvedPaths = new[] { path }; + } + return resolvedPaths; + } + + public void PenumbraSetTemporaryMods(string characterName, Dictionary modPaths, string manipulationData) + { + if (!CheckPenumbraApi()) return; + + actionQueue.Enqueue(() => + { + var ret = _penumbraCreateTemporaryCollection.InvokeFunc("MareSynchronos", characterName, true); + Logger.Verbose("Assigning temp mods for " + ret.Item2); + foreach (var mod in modPaths) + { + Logger.Verbose(mod.Key + " => " + mod.Value); + } + _penumbraSetTemporaryMod.InvokeFunc("MareSynchronos", ret.Item2, modPaths, manipulationData, 0); + }); + } + + private void RedrawEvent(IntPtr objectAddress, int objectTableIndex) + { + PenumbraRedrawEvent?.Invoke(objectAddress, objectTableIndex); + } + + private void PenumbraInit() + { + PenumbraInitialized?.Invoke(); + _penumbraRedraw!.InvokeAction("self", 0); + } + + private void HeelsOffsetChange(float offset) + { + HeelsOffsetChangeEvent?.Invoke(offset); + } + + private void PenumbraDispose() + { + PenumbraDisposed?.Invoke(); + actionQueue.Clear(); + } } diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index 95eed2f..913f35e 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -14,258 +14,257 @@ using MareSynchronos.FileCache; using Newtonsoft.Json; #endif -namespace MareSynchronos.Managers +namespace MareSynchronos.Managers; + +public delegate void PlayerHasChanged(CharacterCacheDto characterCache); + +public class PlayerManager : IDisposable { - public delegate void PlayerHasChanged(CharacterCacheDto characterCache); + private readonly ApiController _apiController; + private readonly CharacterDataFactory _characterDataFactory; + private readonly DalamudUtil _dalamudUtil; + private readonly TransientResourceManager _transientResourceManager; + private readonly PeriodicFileScanner _periodicFileScanner; + private readonly IpcManager _ipcManager; + public event PlayerHasChanged? PlayerHasChanged; + public CharacterCacheDto? LastCreatedCharacterData { get; private set; } + public CharacterData PermanentDataCache { get; private set; } = new(); + private readonly Dictionary> objectKindsToUpdate = new(); - public class PlayerManager : IDisposable + private CancellationTokenSource? _playerChangedCts = new(); + private CancellationTokenSource _transientUpdateCts = new(); + + private List playerRelatedObjects = new List(); + + public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager, + CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager, + PeriodicFileScanner periodicFileScanner) { - private readonly ApiController _apiController; - private readonly CharacterDataFactory _characterDataFactory; - private readonly DalamudUtil _dalamudUtil; - private readonly TransientResourceManager _transientResourceManager; - private readonly PeriodicFileScanner _periodicFileScanner; - private readonly IpcManager _ipcManager; - public event PlayerHasChanged? PlayerHasChanged; - public CharacterCacheDto? LastCreatedCharacterData { get; private set; } - public CharacterData PermanentDataCache { get; private set; } = new(); - private readonly Dictionary> objectKindsToUpdate = new(); + Logger.Verbose("Creating " + nameof(PlayerManager)); - private CancellationTokenSource? _playerChangedCts = new(); - private CancellationTokenSource _transientUpdateCts = new(); + _apiController = apiController; + _ipcManager = ipcManager; + _characterDataFactory = characterDataFactory; + _dalamudUtil = dalamudUtil; + _transientResourceManager = transientResourceManager; + _periodicFileScanner = periodicFileScanner; + _apiController.Connected += ApiControllerOnConnected; + _apiController.Disconnected += ApiController_Disconnected; + _transientResourceManager.TransientResourceLoaded += HandleTransientResourceLoad; + _dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate; + _ipcManager.HeelsOffsetChangeEvent += HeelsOffsetChanged; + _dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate; - private List playerRelatedObjects = new List(); - public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager, - CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager, - PeriodicFileScanner periodicFileScanner) + Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected); + if (_apiController.IsConnected) { - Logger.Verbose("Creating " + nameof(PlayerManager)); - - _apiController = apiController; - _ipcManager = ipcManager; - _characterDataFactory = characterDataFactory; - _dalamudUtil = dalamudUtil; - _transientResourceManager = transientResourceManager; - _periodicFileScanner = periodicFileScanner; - _apiController.Connected += ApiControllerOnConnected; - _apiController.Disconnected += ApiController_Disconnected; - _transientResourceManager.TransientResourceLoaded += HandleTransientResourceLoad; - _dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate; - _ipcManager.HeelsOffsetChangeEvent += HeelsOffsetChanged; - _dalamudUtil.FrameworkUpdate += DalamudUtilOnFrameworkUpdate; - - - Logger.Debug("Watching Player, ApiController is Connected: " + _apiController.IsConnected); - if (_apiController.IsConnected) - { - ApiControllerOnConnected(); - } - - playerRelatedObjects = new List() - { - new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.PlayerPointer), - new PlayerRelatedObject(ObjectKind.MinionOrMount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject), - new PlayerRelatedObject(ObjectKind.Pet, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetPet()), - new PlayerRelatedObject(ObjectKind.Companion, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetCompanion()), - }; + ApiControllerOnConnected(); } - private void DalamudUtilOnFrameworkUpdate() + playerRelatedObjects = new List() { - _transientResourceManager.PlayerRelatedPointers = playerRelatedObjects.Select(f => f.CurrentAddress).ToArray(); - } + new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.PlayerPointer), + new PlayerRelatedObject(ObjectKind.MinionOrMount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject), + new PlayerRelatedObject(ObjectKind.Pet, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetPet()), + new PlayerRelatedObject(ObjectKind.Companion, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.GetCompanion()), + }; + } - public void HandleTransientResourceLoad(IntPtr gameObj) + private void DalamudUtilOnFrameworkUpdate() + { + _transientResourceManager.PlayerRelatedPointers = playerRelatedObjects.Select(f => f.CurrentAddress).ToArray(); + } + + public void HandleTransientResourceLoad(IntPtr gameObj) + { + foreach (var obj in playerRelatedObjects) { - foreach (var obj in playerRelatedObjects) + if (obj.Address == gameObj && !obj.HasUnprocessedUpdate) { - if (obj.Address == gameObj && !obj.HasUnprocessedUpdate) + _transientUpdateCts.Cancel(); + _transientUpdateCts = new CancellationTokenSource(); + var token = _transientUpdateCts.Token; + Task.Run(async () => { - _transientUpdateCts.Cancel(); - _transientUpdateCts = new CancellationTokenSource(); - var token = _transientUpdateCts.Token; - Task.Run(async () => - { - Logger.Debug("Delaying transient resource load update"); - await Task.Delay(750, token); - if (obj.HasUnprocessedUpdate || token.IsCancellationRequested) return; - Logger.Debug("Firing transient resource load update"); - obj.HasTransientsUpdate = true; - }, token); + Logger.Debug("Delaying transient resource load update"); + await Task.Delay(750, token); + if (obj.HasUnprocessedUpdate || token.IsCancellationRequested) return; + Logger.Debug("Firing transient resource load update"); + obj.HasTransientsUpdate = true; + }, token); - return; - } - } - } - - private void HeelsOffsetChanged(float change) - { - var player = playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player); - if (LastCreatedCharacterData != null && LastCreatedCharacterData.HeelsOffset != change && !player.IsProcessing) - { - Logger.Debug("Heels offset changed to " + change); - playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player).HasTransientsUpdate = true; - } - } - - public void Dispose() - { - Logger.Verbose("Disposing " + nameof(PlayerManager)); - - _apiController.Connected -= ApiControllerOnConnected; - _apiController.Disconnected -= ApiController_Disconnected; - - _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; - _dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate; - _dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate; - - _transientResourceManager.TransientResourceLoaded -= HandleTransientResourceLoad; - - _playerChangedCts?.Cancel(); - _ipcManager.HeelsOffsetChangeEvent -= HeelsOffsetChanged; - } - - private unsafe void DalamudUtilOnDelayedFrameworkUpdate() - { - if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return; - - playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject()); - if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && !c.IsProcessing)) - { - OnPlayerOrAttachedObjectsChanged(); - } - } - - private void ApiControllerOnConnected() - { - Logger.Debug("ApiController Connected"); - - _ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent; - } - - private void ApiController_Disconnected() - { - Logger.Debug(nameof(ApiController_Disconnected)); - - _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; - } - - private async Task CreateFullCharacterCacheDto(CancellationToken token) - { - foreach (var unprocessedObject in playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList()) - { - Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind); - PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject, token); - if (!token.IsCancellationRequested) - { - unprocessedObject.HasUnprocessedUpdate = false; - unprocessedObject.IsProcessing = false; - unprocessedObject.HasTransientsUpdate = false; - } - token.ThrowIfCancellationRequested(); - } - - while (!PermanentDataCache.IsReady && !token.IsCancellationRequested) - { - Logger.Verbose("Waiting until cache is ready"); - await Task.Delay(50, token); - } - - if (token.IsCancellationRequested) return null; - - Logger.Verbose("Cache creation complete"); - - var cache = PermanentDataCache.ToCharacterCacheDto(); - //Logger.Verbose(JsonConvert.SerializeObject(cache, Formatting.Indented)); - return cache; - } - - private void IpcManager_PenumbraRedrawEvent(IntPtr address, int idx) - { - Logger.Verbose("RedrawEvent for addr " + address); - - foreach (var item in playerRelatedObjects) - { - if (address == item.Address) - { - Logger.Debug("Penumbra redraw Event for " + item.ObjectKind); - item.HasUnprocessedUpdate = true; - } - } - - if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && (!c.IsProcessing || (c.IsProcessing && c.DoNotSendUpdate)))) - { - OnPlayerOrAttachedObjectsChanged(); - } - } - - private void OnPlayerOrAttachedObjectsChanged() - { - var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList(); - foreach (var unprocessedObject in unprocessedObjects) - { - unprocessedObject.IsProcessing = true; - } - Logger.Debug("Object(s) changed: " + string.Join(", ", unprocessedObjects.Select(c => c.ObjectKind))); - bool doNotSendUpdate = unprocessedObjects.All(c => c.DoNotSendUpdate); - unprocessedObjects.ForEach(p => p.DoNotSendUpdate = false); - _playerChangedCts?.Cancel(); - _playerChangedCts = new CancellationTokenSource(); - var token = _playerChangedCts.Token; - - // fix for redraw from anamnesis - while ((!_dalamudUtil.IsPlayerPresent || _dalamudUtil.PlayerName == "--") && !token.IsCancellationRequested) - { - Logger.Debug("Waiting Until Player is Present"); - Thread.Sleep(100); - } - - if (token.IsCancellationRequested) - { - Logger.Debug("Cancelled"); return; } - - if (!_ipcManager.Initialized) - { - Logger.Warn("Penumbra not active, doing nothing."); - return; - } - - Task.Run(async () => - { - _periodicFileScanner.HaltScan("Character creation"); - foreach (var item in unprocessedObjects) - { - _dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, 10000, token); - } - - CharacterCacheDto? cacheDto = (await CreateFullCharacterCacheDto(token)); - _periodicFileScanner.ResumeScan("Character creation"); - if (cacheDto == null || token.IsCancellationRequested) return; - -#if DEBUG - //var json = JsonConvert.SerializeObject(cacheDto, Formatting.Indented); - //Logger.Verbose(json); -#endif - - if ((LastCreatedCharacterData?.GetHashCode() ?? 0) == cacheDto.GetHashCode()) - { - Logger.Debug("Not sending data, already sent"); - return; - } - else - { - LastCreatedCharacterData = cacheDto; - } - - if (_apiController.IsConnected && !token.IsCancellationRequested && !doNotSendUpdate) - { - Logger.Verbose("Invoking PlayerHasChanged"); - PlayerHasChanged?.Invoke(cacheDto); - } - }, token); } } + + private void HeelsOffsetChanged(float change) + { + var player = playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player); + if (LastCreatedCharacterData != null && LastCreatedCharacterData.HeelsOffset != change && !player.IsProcessing) + { + Logger.Debug("Heels offset changed to " + change); + playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player).HasTransientsUpdate = true; + } + } + + public void Dispose() + { + Logger.Verbose("Disposing " + nameof(PlayerManager)); + + _apiController.Connected -= ApiControllerOnConnected; + _apiController.Disconnected -= ApiController_Disconnected; + + _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; + _dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate; + _dalamudUtil.FrameworkUpdate -= DalamudUtilOnFrameworkUpdate; + + _transientResourceManager.TransientResourceLoaded -= HandleTransientResourceLoad; + + _playerChangedCts?.Cancel(); + _ipcManager.HeelsOffsetChangeEvent -= HeelsOffsetChanged; + } + + private unsafe void DalamudUtilOnDelayedFrameworkUpdate() + { + if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return; + + playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject()); + if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && !c.IsProcessing)) + { + OnPlayerOrAttachedObjectsChanged(); + } + } + + private void ApiControllerOnConnected() + { + Logger.Debug("ApiController Connected"); + + _ipcManager.PenumbraRedrawEvent += IpcManager_PenumbraRedrawEvent; + } + + private void ApiController_Disconnected() + { + Logger.Debug(nameof(ApiController_Disconnected)); + + _ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent; + } + + private async Task CreateFullCharacterCacheDto(CancellationToken token) + { + foreach (var unprocessedObject in playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList()) + { + Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind); + PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject, token); + if (!token.IsCancellationRequested) + { + unprocessedObject.HasUnprocessedUpdate = false; + unprocessedObject.IsProcessing = false; + unprocessedObject.HasTransientsUpdate = false; + } + token.ThrowIfCancellationRequested(); + } + + while (!PermanentDataCache.IsReady && !token.IsCancellationRequested) + { + Logger.Verbose("Waiting until cache is ready"); + await Task.Delay(50, token); + } + + if (token.IsCancellationRequested) return null; + + Logger.Verbose("Cache creation complete"); + + var cache = PermanentDataCache.ToCharacterCacheDto(); + //Logger.Verbose(JsonConvert.SerializeObject(cache, Formatting.Indented)); + return cache; + } + + private void IpcManager_PenumbraRedrawEvent(IntPtr address, int idx) + { + Logger.Verbose("RedrawEvent for addr " + address); + + foreach (var item in playerRelatedObjects) + { + if (address == item.Address) + { + Logger.Debug("Penumbra redraw Event for " + item.ObjectKind); + item.HasUnprocessedUpdate = true; + } + } + + if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && (!c.IsProcessing || (c.IsProcessing && c.DoNotSendUpdate)))) + { + OnPlayerOrAttachedObjectsChanged(); + } + } + + private void OnPlayerOrAttachedObjectsChanged() + { + var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList(); + foreach (var unprocessedObject in unprocessedObjects) + { + unprocessedObject.IsProcessing = true; + } + Logger.Debug("Object(s) changed: " + string.Join(", ", unprocessedObjects.Select(c => c.ObjectKind))); + bool doNotSendUpdate = unprocessedObjects.All(c => c.DoNotSendUpdate); + unprocessedObjects.ForEach(p => p.DoNotSendUpdate = false); + _playerChangedCts?.Cancel(); + _playerChangedCts = new CancellationTokenSource(); + var token = _playerChangedCts.Token; + + // fix for redraw from anamnesis + while ((!_dalamudUtil.IsPlayerPresent || _dalamudUtil.PlayerName == "--") && !token.IsCancellationRequested) + { + Logger.Debug("Waiting Until Player is Present"); + Thread.Sleep(100); + } + + if (token.IsCancellationRequested) + { + Logger.Debug("Cancelled"); + return; + } + + if (!_ipcManager.Initialized) + { + Logger.Warn("Penumbra not active, doing nothing."); + return; + } + + Task.Run(async () => + { + _periodicFileScanner.HaltScan("Character creation"); + foreach (var item in unprocessedObjects) + { + _dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, 10000, token); + } + + CharacterCacheDto? cacheDto = (await CreateFullCharacterCacheDto(token)); + _periodicFileScanner.ResumeScan("Character creation"); + if (cacheDto == null || token.IsCancellationRequested) return; + +#if DEBUG + //var json = JsonConvert.SerializeObject(cacheDto, Formatting.Indented); + //Logger.Verbose(json); +#endif + + if ((LastCreatedCharacterData?.GetHashCode() ?? 0) == cacheDto.GetHashCode()) + { + Logger.Debug("Not sending data, already sent"); + return; + } + else + { + LastCreatedCharacterData = cacheDto; + } + + if (_apiController.IsConnected && !token.IsCancellationRequested && !doNotSendUpdate) + { + Logger.Verbose("Invoking PlayerHasChanged"); + PlayerHasChanged?.Invoke(cacheDto); + } + }, token); + } } diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index 560355f..12668ab 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -8,198 +8,197 @@ using System.Linq; using System.Threading; using System.Threading.Tasks; -namespace MareSynchronos.Managers +namespace MareSynchronos.Managers; + +public delegate void TransientResourceLoadedEvent(IntPtr drawObject); + +public class TransientResourceManager : IDisposable { - public delegate void TransientResourceLoadedEvent(IntPtr drawObject); + private readonly IpcManager manager; + private readonly DalamudUtil dalamudUtil; - public class TransientResourceManager : IDisposable + public event TransientResourceLoadedEvent? TransientResourceLoaded; + public IntPtr[] PlayerRelatedPointers = Array.Empty(); + private readonly string[] FileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp" }; + + private ConcurrentDictionary> TransientResources { get; } = new(); + private ConcurrentDictionary> SemiTransientResources { get; } = new(); + public TransientResourceManager(IpcManager manager, DalamudUtil dalamudUtil) { - private readonly IpcManager manager; - private readonly DalamudUtil dalamudUtil; + manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent; + this.manager = manager; + this.dalamudUtil = dalamudUtil; + dalamudUtil.FrameworkUpdate += DalamudUtil_FrameworkUpdate; + dalamudUtil.ClassJobChanged += DalamudUtil_ClassJobChanged; + } - public event TransientResourceLoadedEvent? TransientResourceLoaded; - public IntPtr[] PlayerRelatedPointers = Array.Empty(); - private readonly string[] FileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp" }; - - private ConcurrentDictionary> TransientResources { get; } = new(); - private ConcurrentDictionary> SemiTransientResources { get; } = new(); - public TransientResourceManager(IpcManager manager, DalamudUtil dalamudUtil) + private void DalamudUtil_ClassJobChanged() + { + if (SemiTransientResources.ContainsKey(ObjectKind.Pet)) { - manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent; - this.manager = manager; - this.dalamudUtil = dalamudUtil; - dalamudUtil.FrameworkUpdate += DalamudUtil_FrameworkUpdate; - dalamudUtil.ClassJobChanged += DalamudUtil_ClassJobChanged; + SemiTransientResources[ObjectKind.Pet].Clear(); } + } - private void DalamudUtil_ClassJobChanged() + private void DalamudUtil_FrameworkUpdate() + { + foreach (var item in TransientResources.ToList()) { - if (SemiTransientResources.ContainsKey(ObjectKind.Pet)) + if (!dalamudUtil.IsGameObjectPresent(item.Key)) { - SemiTransientResources[ObjectKind.Pet].Clear(); - } - } - - private void DalamudUtil_FrameworkUpdate() - { - foreach (var item in TransientResources.ToList()) - { - if (!dalamudUtil.IsGameObjectPresent(item.Key)) - { - Logger.Debug("Object not present anymore: " + item.Key.ToString("X")); - TransientResources.TryRemove(item.Key, out _); - } - } - } - - public void CleanSemiTransientResources(ObjectKind objectKind) - { - if (SemiTransientResources.ContainsKey(objectKind)) - { - SemiTransientResources[objectKind].Clear(); - } - } - - public List GetTransientResources(IntPtr gameObject) - { - if (TransientResources.TryGetValue(gameObject, out var result)) - { - return result.ToList(); - } - - return new List(); - } - - public List GetSemiTransientResources(ObjectKind objectKind) - { - if (SemiTransientResources.TryGetValue(objectKind, out var result)) - { - return result.ToList(); - } - - return new List(); - } - - private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath) - { - if (!FileTypesToHandle.Any(type => gamePath.ToLowerInvariant().EndsWith(type))) - { - return; - } - if (!PlayerRelatedPointers.Contains(gameObject)) - { - return; - } - - if (!TransientResources.ContainsKey(gameObject)) - { - TransientResources[gameObject] = new(); - } - - if (filePath.StartsWith("|")) - { - filePath = filePath.Split("|")[2]; - } - - filePath = filePath.ToLowerInvariant().Replace("\\", "/"); - - var replacedGamePath = gamePath.ToLowerInvariant().Replace("\\", "/"); - - if (TransientResources[gameObject].Contains(replacedGamePath) || - SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath - && f.ResolvedPath.ToLowerInvariant() == filePath))) - { - Logger.Debug("Not adding " + replacedGamePath + ":" + filePath); - Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath - && f.ResolvedPath.ToLowerInvariant() == filePath)).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath)); - } - else - { - TransientResources[gameObject].Add(replacedGamePath); - Logger.Debug($"Adding {replacedGamePath} for {gameObject} ({filePath})"); - TransientResourceLoaded?.Invoke(gameObject); - } - } - - public void RemoveTransientResource(IntPtr gameObject, FileReplacement fileReplacement) - { - if (TransientResources.ContainsKey(gameObject)) - { - TransientResources[gameObject].RemoveWhere(f => fileReplacement.GamePaths.Any(g => g.ToLowerInvariant() == f.ToLowerInvariant())); - } - } - - public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind, Func createFileReplacement) - { - if (!SemiTransientResources.ContainsKey(objectKind)) - { - SemiTransientResources[objectKind] = new HashSet(); - } - - if (!TransientResources.TryGetValue(gameObject, out var resources)) - { - return; - } - - var transientResources = resources.ToList(); - Logger.Debug("Persisting " + transientResources.Count + " transient resources"); - foreach (var gamePath in transientResources) - { - var existingResource = SemiTransientResources[objectKind].Any(f => f.GamePaths.First().ToLowerInvariant() == gamePath.ToLowerInvariant()); - if (existingResource) - { - Logger.Debug("Semi Transient resource replaced: " + gamePath); - SemiTransientResources[objectKind].RemoveWhere(f => f.GamePaths.First().ToLowerInvariant() == gamePath.ToLowerInvariant()); - } - - try - { - var fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), true); - if (!fileReplacement.HasFileReplacement) - fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), false); - if (fileReplacement.HasFileReplacement) - { - Logger.Debug("Persisting " + gamePath.ToLowerInvariant()); - if (SemiTransientResources[objectKind].Add(fileReplacement)) - { - Logger.Debug("Added " + fileReplacement); - } - else - { - Logger.Debug("Not added " + fileReplacement); - } - } - } - catch (Exception ex) - { - Logger.Warn("Issue during transient file persistence"); - Logger.Warn(ex.Message); - Logger.Warn(ex.StackTrace.ToString()); - } - } - - TransientResources[gameObject].Clear(); - } - - public void Dispose() - { - dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate; - manager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent; - dalamudUtil.ClassJobChanged -= DalamudUtil_ClassJobChanged; - TransientResources.Clear(); - } - - internal void AddSemiTransientResource(ObjectKind objectKind, FileReplacement item) - { - if (!SemiTransientResources.ContainsKey(objectKind)) - { - SemiTransientResources[objectKind] = new HashSet(); - } - - if (!SemiTransientResources[objectKind].Any(f => f.ResolvedPath.ToLowerInvariant() == item.ResolvedPath.ToLowerInvariant())) - { - SemiTransientResources[objectKind].Add(item); + Logger.Debug("Object not present anymore: " + item.Key.ToString("X")); + TransientResources.TryRemove(item.Key, out _); } } } + + public void CleanSemiTransientResources(ObjectKind objectKind) + { + if (SemiTransientResources.ContainsKey(objectKind)) + { + SemiTransientResources[objectKind].Clear(); + } + } + + public List GetTransientResources(IntPtr gameObject) + { + if (TransientResources.TryGetValue(gameObject, out var result)) + { + return result.ToList(); + } + + return new List(); + } + + public List GetSemiTransientResources(ObjectKind objectKind) + { + if (SemiTransientResources.TryGetValue(objectKind, out var result)) + { + return result.ToList(); + } + + return new List(); + } + + private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath) + { + if (!FileTypesToHandle.Any(type => gamePath.ToLowerInvariant().EndsWith(type))) + { + return; + } + if (!PlayerRelatedPointers.Contains(gameObject)) + { + return; + } + + if (!TransientResources.ContainsKey(gameObject)) + { + TransientResources[gameObject] = new(); + } + + if (filePath.StartsWith("|")) + { + filePath = filePath.Split("|")[2]; + } + + filePath = filePath.ToLowerInvariant().Replace("\\", "/"); + + var replacedGamePath = gamePath.ToLowerInvariant().Replace("\\", "/"); + + if (TransientResources[gameObject].Contains(replacedGamePath) || + SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath + && f.ResolvedPath.ToLowerInvariant() == filePath))) + { + Logger.Debug("Not adding " + replacedGamePath + ":" + filePath); + Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath + && f.ResolvedPath.ToLowerInvariant() == filePath)).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath)); + } + else + { + TransientResources[gameObject].Add(replacedGamePath); + Logger.Debug($"Adding {replacedGamePath} for {gameObject} ({filePath})"); + TransientResourceLoaded?.Invoke(gameObject); + } + } + + public void RemoveTransientResource(IntPtr gameObject, FileReplacement fileReplacement) + { + if (TransientResources.ContainsKey(gameObject)) + { + TransientResources[gameObject].RemoveWhere(f => fileReplacement.GamePaths.Any(g => g.ToLowerInvariant() == f.ToLowerInvariant())); + } + } + + public void PersistTransientResources(IntPtr gameObject, ObjectKind objectKind, Func createFileReplacement) + { + if (!SemiTransientResources.ContainsKey(objectKind)) + { + SemiTransientResources[objectKind] = new HashSet(); + } + + if (!TransientResources.TryGetValue(gameObject, out var resources)) + { + return; + } + + var transientResources = resources.ToList(); + Logger.Debug("Persisting " + transientResources.Count + " transient resources"); + foreach (var gamePath in transientResources) + { + var existingResource = SemiTransientResources[objectKind].Any(f => f.GamePaths.First().ToLowerInvariant() == gamePath.ToLowerInvariant()); + if (existingResource) + { + Logger.Debug("Semi Transient resource replaced: " + gamePath); + SemiTransientResources[objectKind].RemoveWhere(f => f.GamePaths.First().ToLowerInvariant() == gamePath.ToLowerInvariant()); + } + + try + { + var fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), true); + if (!fileReplacement.HasFileReplacement) + fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), false); + if (fileReplacement.HasFileReplacement) + { + Logger.Debug("Persisting " + gamePath.ToLowerInvariant()); + if (SemiTransientResources[objectKind].Add(fileReplacement)) + { + Logger.Debug("Added " + fileReplacement); + } + else + { + Logger.Debug("Not added " + fileReplacement); + } + } + } + catch (Exception ex) + { + Logger.Warn("Issue during transient file persistence"); + Logger.Warn(ex.Message); + Logger.Warn(ex.StackTrace.ToString()); + } + } + + TransientResources[gameObject].Clear(); + } + + public void Dispose() + { + dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate; + manager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent; + dalamudUtil.ClassJobChanged -= DalamudUtil_ClassJobChanged; + TransientResources.Clear(); + } + + internal void AddSemiTransientResource(ObjectKind objectKind, FileReplacement item) + { + if (!SemiTransientResources.ContainsKey(objectKind)) + { + SemiTransientResources[objectKind] = new HashSet(); + } + + if (!SemiTransientResources[objectKind].Any(f => f.ResolvedPath.ToLowerInvariant() == item.ResolvedPath.ToLowerInvariant())) + { + SemiTransientResources[objectKind].Add(item); + } + } } diff --git a/MareSynchronos/Models/CharacterData.cs b/MareSynchronos/Models/CharacterData.cs index c0c349f..b0edfc4 100644 --- a/MareSynchronos/Models/CharacterData.cs +++ b/MareSynchronos/Models/CharacterData.cs @@ -6,83 +6,82 @@ using MareSynchronos.API; using MareSynchronos.Utils; using Lumina.Excel.GeneratedSheets; -namespace MareSynchronos.Models +namespace MareSynchronos.Models; + +[JsonObject(MemberSerialization.OptIn)] +public class CharacterData { - [JsonObject(MemberSerialization.OptIn)] - public class CharacterData + [JsonProperty] + public Dictionary> FileReplacements { get; set; } = new(); + + [JsonProperty] + public Dictionary GlamourerString { get; set; } = new(); + + public bool IsReady => FileReplacements.SelectMany(k => k.Value).All(f => f.Computed); + + [JsonProperty] + public string ManipulationString { get; set; } = string.Empty; + + [JsonProperty] + public float HeelsOffset { get; set; } = 0f; + + public void AddFileReplacement(ObjectKind objectKind, FileReplacement fileReplacement) { - [JsonProperty] - public Dictionary> FileReplacements { get; set; } = new(); + if (!fileReplacement.HasFileReplacement) return; - [JsonProperty] - public Dictionary GlamourerString { get; set; } = new(); + if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new List()); - public bool IsReady => FileReplacements.SelectMany(k => k.Value).All(f => f.Computed); - - [JsonProperty] - public string ManipulationString { get; set; } = string.Empty; - - [JsonProperty] - public float HeelsOffset { get; set; } = 0f; - - public void AddFileReplacement(ObjectKind objectKind, FileReplacement fileReplacement) + var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => f.ResolvedPath == fileReplacement.ResolvedPath); + if (existingReplacement != null) { - if (!fileReplacement.HasFileReplacement) return; - - if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new List()); - - var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => f.ResolvedPath == fileReplacement.ResolvedPath); - if (existingReplacement != null) - { - existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e))); - } - else - { - FileReplacements[objectKind].Add(fileReplacement); - } + existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e))); } - - public CharacterCacheDto ToCharacterCacheDto() + else { - var fileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement && !f.IsFileSwap).GroupBy(f => f.Hash).Select(g => - { - return new FileReplacementDto() - { - GamePaths = g.SelectMany(f => f.GamePaths).Distinct().ToArray(), - Hash = g.First().Hash, - }; - }).ToList()); - - Logger.Debug("Adding fileSwaps"); - foreach (var item in FileReplacements) - { - Logger.Debug("Checking fileSwaps for " + item.Key); - var fileSwapsToAdd = item.Value.Where(f => f.IsFileSwap).Select(f => f.ToFileReplacementDto()); - Logger.Debug("Adding " + fileSwapsToAdd.Count() + " file swaps"); - foreach (var swap in fileSwapsToAdd) - { - Logger.Debug("Adding: " + swap.GamePaths.First() + ":" + swap.FileSwapPath); - } - fileReplacements[item.Key].AddRange(fileSwapsToAdd); - } - - return new CharacterCacheDto() - { - FileReplacements = fileReplacements, - GlamourerData = GlamourerString.ToDictionary(d => d.Key, d => d.Value), - ManipulationData = ManipulationString, - HeelsOffset = HeelsOffset - }; - } - - public override string ToString() - { - StringBuilder stringBuilder = new(); - foreach (var fileReplacement in FileReplacements.SelectMany(k => k.Value).OrderBy(a => a.GamePaths[0])) - { - stringBuilder.AppendLine(fileReplacement.ToString()); - } - return stringBuilder.ToString(); + FileReplacements[objectKind].Add(fileReplacement); } } + + public CharacterCacheDto ToCharacterCacheDto() + { + var fileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement && !f.IsFileSwap).GroupBy(f => f.Hash).Select(g => + { + return new FileReplacementDto() + { + GamePaths = g.SelectMany(f => f.GamePaths).Distinct().ToArray(), + Hash = g.First().Hash, + }; + }).ToList()); + + Logger.Debug("Adding fileSwaps"); + foreach (var item in FileReplacements) + { + Logger.Debug("Checking fileSwaps for " + item.Key); + var fileSwapsToAdd = item.Value.Where(f => f.IsFileSwap).Select(f => f.ToFileReplacementDto()); + Logger.Debug("Adding " + fileSwapsToAdd.Count() + " file swaps"); + foreach (var swap in fileSwapsToAdd) + { + Logger.Debug("Adding: " + swap.GamePaths.First() + ":" + swap.FileSwapPath); + } + fileReplacements[item.Key].AddRange(fileSwapsToAdd); + } + + return new CharacterCacheDto() + { + FileReplacements = fileReplacements, + GlamourerData = GlamourerString.ToDictionary(d => d.Key, d => d.Value), + ManipulationData = ManipulationString, + HeelsOffset = HeelsOffset + }; + } + + public override string ToString() + { + StringBuilder stringBuilder = new(); + foreach (var fileReplacement in FileReplacements.SelectMany(k => k.Value).OrderBy(a => a.GamePaths[0])) + { + stringBuilder.AppendLine(fileReplacement.ToString()); + } + return stringBuilder.ToString(); + } } diff --git a/MareSynchronos/Models/FileReplacement.cs b/MareSynchronos/Models/FileReplacement.cs index d4b2406..a231afa 100644 --- a/MareSynchronos/Models/FileReplacement.cs +++ b/MareSynchronos/Models/FileReplacement.cs @@ -6,55 +6,54 @@ using MareSynchronos.API; using System.Text.RegularExpressions; using MareSynchronos.FileCache; -namespace MareSynchronos.Models +namespace MareSynchronos.Models; + +public class FileReplacement { - public class FileReplacement + private readonly FileCacheManager fileDbManager; + + public FileReplacement(FileCacheManager fileDbManager) { - private readonly FileCacheManager fileDbManager; + this.fileDbManager = fileDbManager; + } - public FileReplacement(FileCacheManager fileDbManager) + public bool Computed => IsFileSwap || !HasFileReplacement || !string.IsNullOrEmpty(Hash); + + public List GamePaths { get; set; } = new(); + + public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => p != ResolvedPath); + + public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && GamePaths.First() != ResolvedPath; + + public string Hash { get; set; } = string.Empty; + + public string ResolvedPath { get; set; } = string.Empty; + + public void SetResolvedPath(string path) + { + ResolvedPath = path.ToLowerInvariant().Replace('\\', '/'); + if (!HasFileReplacement || IsFileSwap) return; + + _ = Task.Run(() => { - this.fileDbManager = fileDbManager; - } + var cache = fileDbManager.GetFileCacheByPath(ResolvedPath); + Hash = cache.Hash; + }); + } - public bool Computed => IsFileSwap || !HasFileReplacement || !string.IsNullOrEmpty(Hash); - - public List GamePaths { get; set; } = new(); - - public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => p != ResolvedPath); - - public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && GamePaths.First() != ResolvedPath; - - public string Hash { get; set; } = string.Empty; - - public string ResolvedPath { get; set; } = string.Empty; - - public void SetResolvedPath(string path) + public FileReplacementDto ToFileReplacementDto() + { + return new FileReplacementDto { - ResolvedPath = path.ToLowerInvariant().Replace('\\', '/'); - if (!HasFileReplacement || IsFileSwap) return; - - _ = Task.Run(() => - { - var cache = fileDbManager.GetFileCacheByPath(ResolvedPath); - Hash = cache.Hash; - }); - } - - public FileReplacementDto ToFileReplacementDto() - { - return new FileReplacementDto - { - GamePaths = GamePaths.ToArray(), - Hash = Hash, - FileSwapPath = IsFileSwap ? ResolvedPath : string.Empty - }; - } - public override string ToString() - { - StringBuilder builder = new(); - builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}"); - return builder.ToString(); - } + GamePaths = GamePaths.ToArray(), + Hash = Hash, + FileSwapPath = IsFileSwap ? ResolvedPath : string.Empty + }; + } + public override string ToString() + { + StringBuilder builder = new(); + builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}"); + return builder.ToString(); } } diff --git a/MareSynchronos/Models/PlayerRelatedObject.cs b/MareSynchronos/Models/PlayerRelatedObject.cs index f91b221..d425891 100644 --- a/MareSynchronos/Models/PlayerRelatedObject.cs +++ b/MareSynchronos/Models/PlayerRelatedObject.cs @@ -5,132 +5,131 @@ using System.Runtime.InteropServices; using MareSynchronos.Utils; using Penumbra.GameData.ByteString; -namespace MareSynchronos.Models +namespace MareSynchronos.Models; + +public class PlayerRelatedObject { - public class PlayerRelatedObject + private readonly Func getAddress; + + public unsafe Character* Character => (Character*)Address; + + private string _name; + + public ObjectKind ObjectKind { get; } + public IntPtr Address { get; set; } + public IntPtr DrawObjectAddress { get; set; } + + public IntPtr CurrentAddress { - private readonly Func getAddress; - - public unsafe Character* Character => (Character*)Address; - - private string _name; - - public ObjectKind ObjectKind { get; } - public IntPtr Address { get; set; } - public IntPtr DrawObjectAddress { get; set; } - - public IntPtr CurrentAddress + get { - get + try { - try - { - return getAddress.Invoke(); - } - catch - { return IntPtr.Zero; } + return getAddress.Invoke(); } - } - - public PlayerRelatedObject(ObjectKind objectKind, IntPtr address, IntPtr drawObjectAddress, Func getAddress) - { - ObjectKind = objectKind; - Address = address; - DrawObjectAddress = drawObjectAddress; - this.getAddress = getAddress; - _name = string.Empty; - } - - public byte[] EquipSlotData { get; set; } = new byte[40]; - public byte[] CustomizeData { get; set; } = new byte[26]; - public byte? HatState { get; set; } - public byte? VisorWeaponState { get; set; } - - public bool HasTransientsUpdate { get; set; } = false; - public bool HasUnprocessedUpdate { get; set; } = false; - public bool DoNotSendUpdate { get; set; } = false; - public bool IsProcessing { get; set; } = false; - - public unsafe void CheckAndUpdateObject() - { - var curPtr = CurrentAddress; - if (curPtr != IntPtr.Zero) - { - var chara = (Character*)curPtr; - bool addr = Address == IntPtr.Zero || Address != curPtr; - bool equip = CompareAndUpdateByteData(chara->EquipSlotData, chara->CustomizeData); - bool drawObj = (IntPtr)chara->GameObject.DrawObject != DrawObjectAddress; - var name = new Utf8String(chara->GameObject.Name).ToString(); - bool nameChange = (name != _name); - if (addr || equip || drawObj || nameChange) - { - _name = name; - Logger.Verbose($"{ObjectKind} changed: {_name}, now: {curPtr:X}, {(IntPtr)chara->GameObject.DrawObject:X}"); - - Address = curPtr; - DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject; - HasUnprocessedUpdate = true; - } - } - else if (Address != IntPtr.Zero || DrawObjectAddress != IntPtr.Zero) - { - Address = IntPtr.Zero; - DrawObjectAddress = IntPtr.Zero; - Logger.Verbose(ObjectKind + " Changed: " + _name + ", now: " + Address + ", " + DrawObjectAddress); - } - } - - private unsafe bool CompareAndUpdateByteData(byte* equipSlotData, byte* customizeData) - { - bool hasChanges = false; - DoNotSendUpdate = false; - for (int i = 0; i < EquipSlotData.Length; i++) - { - var data = Marshal.ReadByte((IntPtr)equipSlotData, i); - if (EquipSlotData[i] != data) - { - EquipSlotData[i] = data; - hasChanges = true; - } - } - - for (int i = 0; i < CustomizeData.Length; i++) - { - var data = Marshal.ReadByte((IntPtr)customizeData, i); - if (CustomizeData[i] != data) - { - CustomizeData[i] = data; - hasChanges = true; - } - } - - var newHatState = Marshal.ReadByte((IntPtr)customizeData + 30, 0); - var newWeaponOrVisorState = Marshal.ReadByte((IntPtr)customizeData + 31, 0); - if (newHatState != HatState) - { - if (HatState != null && !hasChanges && !HasUnprocessedUpdate) - { - Logger.Debug("Not Sending Update, only Hat changed"); - DoNotSendUpdate = true; - } - HatState = newHatState; - hasChanges = true; - } - - newWeaponOrVisorState &= 0b1101; // ignore drawing weapon - - if (newWeaponOrVisorState != VisorWeaponState) - { - if (VisorWeaponState != null && !hasChanges && !HasUnprocessedUpdate) - { - Logger.Debug("Not Sending Update, only Visor/Weapon changed"); - DoNotSendUpdate = true; - } - VisorWeaponState = newWeaponOrVisorState; - hasChanges = true; - } - - return hasChanges; + catch + { return IntPtr.Zero; } } } + + public PlayerRelatedObject(ObjectKind objectKind, IntPtr address, IntPtr drawObjectAddress, Func getAddress) + { + ObjectKind = objectKind; + Address = address; + DrawObjectAddress = drawObjectAddress; + this.getAddress = getAddress; + _name = string.Empty; + } + + public byte[] EquipSlotData { get; set; } = new byte[40]; + public byte[] CustomizeData { get; set; } = new byte[26]; + public byte? HatState { get; set; } + public byte? VisorWeaponState { get; set; } + + public bool HasTransientsUpdate { get; set; } = false; + public bool HasUnprocessedUpdate { get; set; } = false; + public bool DoNotSendUpdate { get; set; } = false; + public bool IsProcessing { get; set; } = false; + + public unsafe void CheckAndUpdateObject() + { + var curPtr = CurrentAddress; + if (curPtr != IntPtr.Zero) + { + var chara = (Character*)curPtr; + bool addr = Address == IntPtr.Zero || Address != curPtr; + bool equip = CompareAndUpdateByteData(chara->EquipSlotData, chara->CustomizeData); + bool drawObj = (IntPtr)chara->GameObject.DrawObject != DrawObjectAddress; + var name = new Utf8String(chara->GameObject.Name).ToString(); + bool nameChange = (name != _name); + if (addr || equip || drawObj || nameChange) + { + _name = name; + Logger.Verbose($"{ObjectKind} changed: {_name}, now: {curPtr:X}, {(IntPtr)chara->GameObject.DrawObject:X}"); + + Address = curPtr; + DrawObjectAddress = (IntPtr)chara->GameObject.DrawObject; + HasUnprocessedUpdate = true; + } + } + else if (Address != IntPtr.Zero || DrawObjectAddress != IntPtr.Zero) + { + Address = IntPtr.Zero; + DrawObjectAddress = IntPtr.Zero; + Logger.Verbose(ObjectKind + " Changed: " + _name + ", now: " + Address + ", " + DrawObjectAddress); + } + } + + private unsafe bool CompareAndUpdateByteData(byte* equipSlotData, byte* customizeData) + { + bool hasChanges = false; + DoNotSendUpdate = false; + for (int i = 0; i < EquipSlotData.Length; i++) + { + var data = Marshal.ReadByte((IntPtr)equipSlotData, i); + if (EquipSlotData[i] != data) + { + EquipSlotData[i] = data; + hasChanges = true; + } + } + + for (int i = 0; i < CustomizeData.Length; i++) + { + var data = Marshal.ReadByte((IntPtr)customizeData, i); + if (CustomizeData[i] != data) + { + CustomizeData[i] = data; + hasChanges = true; + } + } + + var newHatState = Marshal.ReadByte((IntPtr)customizeData + 30, 0); + var newWeaponOrVisorState = Marshal.ReadByte((IntPtr)customizeData + 31, 0); + if (newHatState != HatState) + { + if (HatState != null && !hasChanges && !HasUnprocessedUpdate) + { + Logger.Debug("Not Sending Update, only Hat changed"); + DoNotSendUpdate = true; + } + HatState = newHatState; + hasChanges = true; + } + + newWeaponOrVisorState &= 0b1101; // ignore drawing weapon + + if (newWeaponOrVisorState != VisorWeaponState) + { + if (VisorWeaponState != null && !hasChanges && !HasUnprocessedUpdate) + { + Logger.Debug("Not Sending Update, only Visor/Weapon changed"); + DoNotSendUpdate = true; + } + VisorWeaponState = newWeaponOrVisorState; + hasChanges = true; + } + + return hasChanges; + } } diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 74d5bf8..fbee90d 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -15,204 +15,203 @@ using MareSynchronos.Utils; using Dalamud.Game.ClientState.Conditions; using MareSynchronos.FileCache; -namespace MareSynchronos +namespace MareSynchronos; + +public sealed class Plugin : IDalamudPlugin { - public sealed class Plugin : IDalamudPlugin + private const string CommandName = "/mare"; + private readonly ApiController _apiController; + private readonly CommandManager _commandManager; + private readonly Configuration _configuration; + private readonly PeriodicFileScanner _periodicFileScanner; + private readonly IntroUi _introUi; + private readonly IpcManager _ipcManager; + private readonly DalamudPluginInterface _pluginInterface; + private readonly SettingsUi _settingsUi; + private readonly WindowSystem _windowSystem; + private PlayerManager? _playerManager; + private TransientResourceManager? _transientResourceManager; + private readonly DalamudUtil _dalamudUtil; + private OnlinePlayerManager? _characterCacheManager; + private readonly DownloadUi _downloadUi; + private readonly FileDialogManager _fileDialogManager; + private readonly FileCacheManager _fileDbManager; + private readonly CompactUi _compactUi; + private readonly UiShared _uiSharedComponent; + private readonly Dalamud.Localization _localization; + + + public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager, + Framework framework, ObjectTable objectTable, ClientState clientState, Condition condition) { - private const string CommandName = "/mare"; - private readonly ApiController _apiController; - private readonly CommandManager _commandManager; - private readonly Configuration _configuration; - private readonly PeriodicFileScanner _periodicFileScanner; - private readonly IntroUi _introUi; - private readonly IpcManager _ipcManager; - private readonly DalamudPluginInterface _pluginInterface; - private readonly SettingsUi _settingsUi; - private readonly WindowSystem _windowSystem; - private PlayerManager? _playerManager; - private TransientResourceManager? _transientResourceManager; - private readonly DalamudUtil _dalamudUtil; - private OnlinePlayerManager? _characterCacheManager; - private readonly DownloadUi _downloadUi; - private readonly FileDialogManager _fileDialogManager; - private readonly FileCacheManager _fileDbManager; - private readonly CompactUi _compactUi; - private readonly UiShared _uiSharedComponent; - private readonly Dalamud.Localization _localization; + Logger.Debug("Launching " + Name); + _pluginInterface = pluginInterface; + _commandManager = commandManager; + _configuration = _pluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); + _configuration.Initialize(_pluginInterface); + _configuration.Migrate(); + _localization = new Dalamud.Localization("MareSynchronos.Localization.", "", true); + _localization.SetupWithLangCode("en"); - public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager, - Framework framework, ObjectTable objectTable, ClientState clientState, Condition condition) + _windowSystem = new WindowSystem("MareSynchronos"); + + // those can be initialized outside of game login + _dalamudUtil = new DalamudUtil(clientState, objectTable, framework, condition); + + _ipcManager = new IpcManager(_pluginInterface, _dalamudUtil); + _fileDialogManager = new FileDialogManager(); + _fileDbManager = new FileCacheManager(_ipcManager, _configuration, _pluginInterface.ConfigDirectory.FullName); + _apiController = new ApiController(_configuration, _dalamudUtil, _fileDbManager); + _periodicFileScanner = new PeriodicFileScanner(_ipcManager, _configuration, _fileDbManager, _apiController, _dalamudUtil); + + _uiSharedComponent = + new UiShared(_ipcManager, _apiController, _periodicFileScanner, _fileDialogManager, _configuration, _dalamudUtil, _pluginInterface, _localization); + _settingsUi = new SettingsUi(_windowSystem, _uiSharedComponent, _configuration, _apiController); + _compactUi = new CompactUi(_windowSystem, _uiSharedComponent, _configuration, _apiController); + + _introUi = new IntroUi(_windowSystem, _uiSharedComponent, _configuration, _periodicFileScanner); + _settingsUi.SwitchToIntroUi += () => { - Logger.Debug("Launching " + Name); - _pluginInterface = pluginInterface; - _commandManager = commandManager; - _configuration = _pluginInterface.GetPluginConfig() as Configuration ?? new Configuration(); - _configuration.Initialize(_pluginInterface); - _configuration.Migrate(); - - _localization = new Dalamud.Localization("MareSynchronos.Localization.", "", true); - _localization.SetupWithLangCode("en"); - - _windowSystem = new WindowSystem("MareSynchronos"); - - // those can be initialized outside of game login - _dalamudUtil = new DalamudUtil(clientState, objectTable, framework, condition); - - _ipcManager = new IpcManager(_pluginInterface, _dalamudUtil); - _fileDialogManager = new FileDialogManager(); - _fileDbManager = new FileCacheManager(_ipcManager, _configuration, _pluginInterface.ConfigDirectory.FullName); - _apiController = new ApiController(_configuration, _dalamudUtil, _fileDbManager); - _periodicFileScanner = new PeriodicFileScanner(_ipcManager, _configuration, _fileDbManager, _apiController, _dalamudUtil); - - _uiSharedComponent = - new UiShared(_ipcManager, _apiController, _periodicFileScanner, _fileDialogManager, _configuration, _dalamudUtil, _pluginInterface, _localization); - _settingsUi = new SettingsUi(_windowSystem, _uiSharedComponent, _configuration, _apiController); - _compactUi = new CompactUi(_windowSystem, _uiSharedComponent, _configuration, _apiController); - - _introUi = new IntroUi(_windowSystem, _uiSharedComponent, _configuration, _periodicFileScanner); - _settingsUi.SwitchToIntroUi += () => - { - _introUi.IsOpen = true; - _settingsUi.IsOpen = false; - _compactUi.IsOpen = false; - }; - _introUi.SwitchToMainUi += () => - { - _introUi.IsOpen = false; - _compactUi.IsOpen = true; - _periodicFileScanner.StartScan(); - ReLaunchCharacterManager(); - }; - _compactUi.OpenSettingsUi += () => - { - _settingsUi.Toggle(); - }; - _downloadUi = new DownloadUi(_windowSystem, _configuration, _apiController, _uiSharedComponent); - - - _dalamudUtil.LogIn += DalamudUtilOnLogIn; - _dalamudUtil.LogOut += DalamudUtilOnLogOut; - - if (_dalamudUtil.IsLoggedIn) - { - DalamudUtilOnLogIn(); - } - } - - public string Name => "Mare Synchronos"; - public void Dispose() + _introUi.IsOpen = true; + _settingsUi.IsOpen = false; + _compactUi.IsOpen = false; + }; + _introUi.SwitchToMainUi += () => { - Logger.Verbose("Disposing " + Name); - _apiController?.Dispose(); - - _commandManager.RemoveHandler(CommandName); - _dalamudUtil.LogIn -= DalamudUtilOnLogIn; - _dalamudUtil.LogOut -= DalamudUtilOnLogOut; - - _uiSharedComponent.Dispose(); - _settingsUi?.Dispose(); - _introUi?.Dispose(); - _downloadUi?.Dispose(); - _compactUi?.Dispose(); - - _periodicFileScanner?.Dispose(); - _fileDbManager?.Dispose(); - _playerManager?.Dispose(); - _characterCacheManager?.Dispose(); - _ipcManager?.Dispose(); - _transientResourceManager?.Dispose(); - _dalamudUtil.Dispose(); - Logger.Debug("Shut down"); - } - - - private void DalamudUtilOnLogIn() - { - Logger.Debug("Client login"); - - _pluginInterface.UiBuilder.Draw += Draw; - _pluginInterface.UiBuilder.OpenConfigUi += OpenUi; - _commandManager.AddHandler(CommandName, new CommandInfo(OnCommand) - { - HelpMessage = "Opens the Mare Synchronos UI" - }); - - if (!_configuration.HasValidSetup()) - { - _introUi.IsOpen = true; - _configuration.FullPause = false; - _configuration.Save(); - return; - } - + _introUi.IsOpen = false; + _compactUi.IsOpen = true; _periodicFileScanner.StartScan(); ReLaunchCharacterManager(); - } - - private void DalamudUtilOnLogOut() + }; + _compactUi.OpenSettingsUi += () => { - Logger.Debug("Client logout"); - _characterCacheManager?.Dispose(); - _playerManager?.Dispose(); - _transientResourceManager?.Dispose(); - _pluginInterface.UiBuilder.Draw -= Draw; - _pluginInterface.UiBuilder.OpenConfigUi -= OpenUi; - _commandManager.RemoveHandler(CommandName); - } + _settingsUi.Toggle(); + }; + _downloadUi = new DownloadUi(_windowSystem, _configuration, _apiController, _uiSharedComponent); - public void ReLaunchCharacterManager() + + _dalamudUtil.LogIn += DalamudUtilOnLogIn; + _dalamudUtil.LogOut += DalamudUtilOnLogOut; + + if (_dalamudUtil.IsLoggedIn) { - _characterCacheManager?.Dispose(); - _playerManager?.Dispose(); - _transientResourceManager?.Dispose(); - - Task.Run(WaitForPlayerAndLaunchCharacterManager); - } - - private async Task WaitForPlayerAndLaunchCharacterManager() - { - while (!_dalamudUtil.IsPlayerPresent) - { - await Task.Delay(100); - } - - try - { - _transientResourceManager = new TransientResourceManager(_ipcManager, _dalamudUtil); - var characterCacheFactory = - new CharacterDataFactory(_dalamudUtil, _ipcManager, _transientResourceManager, _fileDbManager); - _playerManager = new PlayerManager(_apiController, _ipcManager, - characterCacheFactory, _dalamudUtil, _transientResourceManager, _periodicFileScanner); - _characterCacheManager = new OnlinePlayerManager(_apiController, - _dalamudUtil, _ipcManager, _playerManager, _fileDbManager); - } - catch (Exception ex) - { - Logger.Debug(ex.Message); - } - } - - private void Draw() - { - _windowSystem.Draw(); - _fileDialogManager.Draw(); - } - - private void OnCommand(string command, string args) - { - if (string.IsNullOrEmpty(args)) - { - OpenUi(); - } - } - - private void OpenUi() - { - if (_configuration.HasValidSetup()) - _compactUi.Toggle(); - else - _introUi.Toggle(); + DalamudUtilOnLogIn(); } } + + public string Name => "Mare Synchronos"; + public void Dispose() + { + Logger.Verbose("Disposing " + Name); + _apiController?.Dispose(); + + _commandManager.RemoveHandler(CommandName); + _dalamudUtil.LogIn -= DalamudUtilOnLogIn; + _dalamudUtil.LogOut -= DalamudUtilOnLogOut; + + _uiSharedComponent.Dispose(); + _settingsUi?.Dispose(); + _introUi?.Dispose(); + _downloadUi?.Dispose(); + _compactUi?.Dispose(); + + _periodicFileScanner?.Dispose(); + _fileDbManager?.Dispose(); + _playerManager?.Dispose(); + _characterCacheManager?.Dispose(); + _ipcManager?.Dispose(); + _transientResourceManager?.Dispose(); + _dalamudUtil.Dispose(); + Logger.Debug("Shut down"); + } + + + private void DalamudUtilOnLogIn() + { + Logger.Debug("Client login"); + + _pluginInterface.UiBuilder.Draw += Draw; + _pluginInterface.UiBuilder.OpenConfigUi += OpenUi; + _commandManager.AddHandler(CommandName, new CommandInfo(OnCommand) + { + HelpMessage = "Opens the Mare Synchronos UI" + }); + + if (!_configuration.HasValidSetup()) + { + _introUi.IsOpen = true; + _configuration.FullPause = false; + _configuration.Save(); + return; + } + + _periodicFileScanner.StartScan(); + ReLaunchCharacterManager(); + } + + private void DalamudUtilOnLogOut() + { + Logger.Debug("Client logout"); + _characterCacheManager?.Dispose(); + _playerManager?.Dispose(); + _transientResourceManager?.Dispose(); + _pluginInterface.UiBuilder.Draw -= Draw; + _pluginInterface.UiBuilder.OpenConfigUi -= OpenUi; + _commandManager.RemoveHandler(CommandName); + } + + public void ReLaunchCharacterManager() + { + _characterCacheManager?.Dispose(); + _playerManager?.Dispose(); + _transientResourceManager?.Dispose(); + + Task.Run(WaitForPlayerAndLaunchCharacterManager); + } + + private async Task WaitForPlayerAndLaunchCharacterManager() + { + while (!_dalamudUtil.IsPlayerPresent) + { + await Task.Delay(100); + } + + try + { + _transientResourceManager = new TransientResourceManager(_ipcManager, _dalamudUtil); + var characterCacheFactory = + new CharacterDataFactory(_dalamudUtil, _ipcManager, _transientResourceManager, _fileDbManager); + _playerManager = new PlayerManager(_apiController, _ipcManager, + characterCacheFactory, _dalamudUtil, _transientResourceManager, _periodicFileScanner); + _characterCacheManager = new OnlinePlayerManager(_apiController, + _dalamudUtil, _ipcManager, _playerManager, _fileDbManager); + } + catch (Exception ex) + { + Logger.Debug(ex.Message); + } + } + + private void Draw() + { + _windowSystem.Draw(); + _fileDialogManager.Draw(); + } + + private void OnCommand(string command, string args) + { + if (string.IsNullOrEmpty(args)) + { + OpenUi(); + } + } + + private void OpenUi() + { + if (_configuration.HasValidSetup()) + _compactUi.Toggle(); + else + _introUi.Toggle(); + } } diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 6818fd2..cdc9758 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -14,550 +14,549 @@ using MareSynchronos.API; using MareSynchronos.Utils; using MareSynchronos.WebAPI; -namespace MareSynchronos.UI +namespace MareSynchronos.UI; + +public class CompactUi : Window, IDisposable { - public class CompactUi : Window, IDisposable + private readonly ApiController _apiController; + private readonly Configuration _configuration; + private readonly Dictionary _showUidForEntry = new(); + private readonly UiShared _uiShared; + private readonly WindowSystem _windowSystem; + private string _characterOrCommentFilter = string.Empty; + + private string _editCharComment = string.Empty; + private string _editNickEntry = string.Empty; + private string _pairToAdd = string.Empty; + private readonly Stopwatch _timeout = new(); + private bool _buttonState; + + private float _transferPartHeight = 0; + + private float _windowContentWidth = 0; + + public CompactUi(WindowSystem windowSystem, + UiShared uiShared, Configuration configuration, ApiController apiController) : base("###MareSynchronosMainUI") { - private readonly ApiController _apiController; - private readonly Configuration _configuration; - private readonly Dictionary _showUidForEntry = new(); - private readonly UiShared _uiShared; - private readonly WindowSystem _windowSystem; - private string _characterOrCommentFilter = string.Empty; - - private string _editCharComment = string.Empty; - private string _editNickEntry = string.Empty; - private string _pairToAdd = string.Empty; - private readonly Stopwatch _timeout = new(); - private bool _buttonState; - - private float _transferPartHeight = 0; - - private float _windowContentWidth = 0; - - public CompactUi(WindowSystem windowSystem, - UiShared uiShared, Configuration configuration, ApiController apiController) : base("###MareSynchronosMainUI") - { #if DEBUG - string dateTime = "DEV VERSION"; - try - { - dateTime = VariousExtensions.GetLinkerTime(Assembly.GetCallingAssembly()).ToString("yyyyMMddHHmmss"); - } - catch (Exception ex) - { - Logger.Warn("Could not get assembly name"); - Logger.Warn(ex.Message); - Logger.Warn(ex.StackTrace); - } - this.WindowName = "Mare Synchronos " + dateTime + "###MareSynchronosMainUI"; - Toggle(); + string dateTime = "DEV VERSION"; + try + { + dateTime = VariousExtensions.GetLinkerTime(Assembly.GetCallingAssembly()).ToString("yyyyMMddHHmmss"); + } + catch (Exception ex) + { + Logger.Warn("Could not get assembly name"); + Logger.Warn(ex.Message); + Logger.Warn(ex.StackTrace); + } + this.WindowName = "Mare Synchronos " + dateTime + "###MareSynchronosMainUI"; + Toggle(); #else - this.WindowName = "Mare Synchronos " + Assembly.GetExecutingAssembly().GetName().Version; + this.WindowName = "Mare Synchronos " + Assembly.GetExecutingAssembly().GetName().Version; #endif - Logger.Verbose("Creating " + nameof(CompactUi)); + Logger.Verbose("Creating " + nameof(CompactUi)); - _windowSystem = windowSystem; - _uiShared = uiShared; - _configuration = configuration; - _apiController = apiController; + _windowSystem = windowSystem; + _uiShared = uiShared; + _configuration = configuration; + _apiController = apiController; - SizeConstraints = new WindowSizeConstraints() - { - MinimumSize = new Vector2(300, 400), - MaximumSize = new Vector2(300, 2000), - }; - - windowSystem.AddWindow(this); - } - - public event SwitchUi? OpenSettingsUi; - public void Dispose() + SizeConstraints = new WindowSizeConstraints() { - Logger.Verbose("Disposing " + nameof(CompactUi)); - _windowSystem.RemoveWindow(this); - } + MinimumSize = new Vector2(300, 400), + MaximumSize = new Vector2(300, 2000), + }; - public override void Draw() + windowSystem.AddWindow(this); + } + + public event SwitchUi? OpenSettingsUi; + public void Dispose() + { + Logger.Verbose("Disposing " + nameof(CompactUi)); + _windowSystem.RemoveWindow(this); + } + + public override void Draw() + { + _windowContentWidth = UiShared.GetWindowContentRegionWidth(); + UiShared.DrawWithID("header", DrawUIDHeader); + ImGui.Separator(); + UiShared.DrawWithID("serverstatus", DrawServerStatus); + + if (_apiController.ServerState is ServerState.Connected) { - _windowContentWidth = UiShared.GetWindowContentRegionWidth(); - UiShared.DrawWithID("header", DrawUIDHeader); ImGui.Separator(); - UiShared.DrawWithID("serverstatus", DrawServerStatus); + UiShared.DrawWithID("pairlist", DrawPairList); + ImGui.Separator(); + UiShared.DrawWithID("transfers", DrawTransfers); + _transferPartHeight = ImGui.GetCursorPosY() - _transferPartHeight; + } + } - if (_apiController.ServerState is ServerState.Connected) + public override void OnClose() + { + _editNickEntry = string.Empty; + _editCharComment = string.Empty; + base.OnClose(); + } + private void DrawAddPair() + { + var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); + ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X); + ImGui.InputTextWithHint("##otheruid", "Other players UID", ref _pairToAdd, 10); + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) + { + if (_apiController.PairedClients.All(w => w.OtherUID != _pairToAdd)) { - ImGui.Separator(); - UiShared.DrawWithID("pairlist", DrawPairList); - ImGui.Separator(); - UiShared.DrawWithID("transfers", DrawTransfers); - _transferPartHeight = ImGui.GetCursorPosY() - _transferPartHeight; + _ = _apiController.SendPairedClientAddition(_pairToAdd); + _pairToAdd = string.Empty; } } + UiShared.AttachToolTip("Pair with " + (_pairToAdd.IsNullOrEmpty() ? "other user" : _pairToAdd)); - public override void OnClose() + ImGuiHelpers.ScaledDummy(2); + } + + private void DrawFilter() + { + var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.ArrowUp); + var playButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Play); + if (!_configuration.ReverseUserSort) { - _editNickEntry = string.Empty; - _editCharComment = string.Empty; - base.OnClose(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowDown)) + { + _configuration.ReverseUserSort = true; + _configuration.Save(); + } + UiShared.AttachToolTip("Sort by newest additions first"); } - private void DrawAddPair() + else { - var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); - ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X); - ImGui.InputTextWithHint("##otheruid", "Other players UID", ref _pairToAdd, 10); - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) + if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowUp)) { - if (_apiController.PairedClients.All(w => w.OtherUID != _pairToAdd)) - { - _ = _apiController.SendPairedClientAddition(_pairToAdd); - _pairToAdd = string.Empty; - } + _configuration.ReverseUserSort = false; + _configuration.Save(); } - UiShared.AttachToolTip("Pair with " + (_pairToAdd.IsNullOrEmpty() ? "other user" : _pairToAdd)); - - ImGuiHelpers.ScaledDummy(2); + UiShared.AttachToolTip("Sort by oldest additions first"); } + ImGui.SameLine(); + + var users = GetFilteredUsers().ToList(); + var userCount = users.Count; + + var spacing = userCount > 0 + ? playButtonSize.X + ImGui.GetStyle().ItemSpacing.X * 2 + : ImGui.GetStyle().ItemSpacing.X; + + ImGui.SetNextItemWidth(_windowContentWidth - buttonSize.X - spacing); + ImGui.InputTextWithHint("##filter", "Filter for UID/notes", ref _characterOrCommentFilter, 255); - private void DrawFilter() + if (userCount == 0) return; + ImGui.SameLine(); + + var pausedUsers = users.Where(u => u.IsPaused).ToList(); + var resumedUsers = users.Where(u => !u.IsPaused).ToList(); + + switch (_buttonState) { - var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.ArrowUp); - var playButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Play); - if (!_configuration.ReverseUserSort) - { - if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowDown)) - { - _configuration.ReverseUserSort = true; - _configuration.Save(); - } - UiShared.AttachToolTip("Sort by newest additions first"); - } - else - { - if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowUp)) - { - _configuration.ReverseUserSort = false; - _configuration.Save(); - } - UiShared.AttachToolTip("Sort by oldest additions first"); - } - ImGui.SameLine(); - - var users = GetFilteredUsers().ToList(); - var userCount = users.Count; - - var spacing = userCount > 0 - ? playButtonSize.X + ImGui.GetStyle().ItemSpacing.X * 2 - : ImGui.GetStyle().ItemSpacing.X; - - ImGui.SetNextItemWidth(_windowContentWidth - buttonSize.X - spacing); - ImGui.InputTextWithHint("##filter", "Filter for UID/notes", ref _characterOrCommentFilter, 255); - - if (userCount == 0) return; - ImGui.SameLine(); - - var pausedUsers = users.Where(u => u.IsPaused).ToList(); - var resumedUsers = users.Where(u => !u.IsPaused).ToList(); - - switch (_buttonState) - { - case true when !pausedUsers.Any(): - _buttonState = false; - break; - case false when !resumedUsers.Any(): - _buttonState = true; - break; - case true: - users = pausedUsers; - break; - case false: - users = resumedUsers; - break; - } - - var button = _buttonState ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; - - if (!_timeout.IsRunning || _timeout.ElapsedMilliseconds > 15000) - { - _timeout.Reset(); - - if (ImGuiComponents.IconButton(button)) - { - if (UiShared.CtrlPressed()) - { - Logger.Debug(users.Count.ToString()); - foreach (var entry in users) - { - _ = _apiController.SendPairedClientPauseChange(entry.OtherUID, !entry.IsPaused); - } - - _timeout.Start(); - _buttonState = !_buttonState; - } - } - UiShared.AttachToolTip($"Hold Control to {(button == FontAwesomeIcon.Play ? "resume" : "pause")} pairing with {users.Count} out of {userCount} displayed users."); - } - else - { - var availableAt = (15000 - _timeout.ElapsedMilliseconds) / 1000; - ImGuiComponents.DisabledButton(button); - UiShared.AttachToolTip($"Next execution is available at {availableAt} seconds"); - } + case true when !pausedUsers.Any(): + _buttonState = false; + break; + case false when !resumedUsers.Any(): + _buttonState = true; + break; + case true: + users = pausedUsers; + break; + case false: + users = resumedUsers; + break; } - - private void DrawPairedClient(ClientPairDto entry) + + var button = _buttonState ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; + + if (!_timeout.IsRunning || _timeout.ElapsedMilliseconds > 15000) { - var pauseIcon = entry.IsPaused ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; - - var buttonSize = UiShared.GetIconButtonSize(pauseIcon); - var trashButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Trash); - var entryUID = string.IsNullOrEmpty(entry.VanityUID) ? entry.OtherUID : entry.VanityUID; - var textSize = ImGui.CalcTextSize(entryUID); - var originalY = ImGui.GetCursorPosY(); - var buttonSizes = buttonSize.Y + trashButtonSize.Y; - - var textPos = originalY + buttonSize.Y / 2 - textSize.Y / 2; - ImGui.SetCursorPosY(textPos); - if (!entry.IsSynced) - { - ImGui.PushFont(UiBuilder.IconFont); - UiShared.ColorText(FontAwesomeIcon.ArrowUp.ToIconString(), ImGuiColors.DalamudRed); - ImGui.PopFont(); - - UiShared.AttachToolTip(entryUID + " has not added you back"); - } - else if (entry.IsPaused || entry.IsPausedFromOthers) - { - ImGui.PushFont(UiBuilder.IconFont); - UiShared.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow); - ImGui.PopFont(); - - UiShared.AttachToolTip("Pairing status with " + entryUID + " is paused"); - } - else - { - ImGui.PushFont(UiBuilder.IconFont); - UiShared.ColorText(FontAwesomeIcon.Check.ToIconString(), ImGuiColors.ParsedGreen); - ImGui.PopFont(); - - UiShared.AttachToolTip("You are paired with " + entryUID); - } - - var textIsUid = true; - _showUidForEntry.TryGetValue(entry.OtherUID, out var showUidInsteadOfName); - if (!showUidInsteadOfName && _configuration.GetCurrentServerUidComments().TryGetValue(entry.OtherUID, out var playerText)) - { - if (string.IsNullOrEmpty(playerText)) - { - playerText = entryUID; - } - else - { - textIsUid = false; - } - } - else - { - playerText = entryUID; - } - - ImGui.SameLine(); - if (_editNickEntry != entry.OtherUID) - { - ImGui.SetCursorPosY(textPos); - if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont); - ImGui.TextUnformatted(playerText); - if (textIsUid) ImGui.PopFont(); - UiShared.AttachToolTip("Left click to switch between UID display and nick" + Environment.NewLine + - "Right click to change nick for " + entryUID); - if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) - { - var prevState = textIsUid; - if (_showUidForEntry.ContainsKey(entry.OtherUID)) - { - prevState = _showUidForEntry[entry.OtherUID]; - } - - _showUidForEntry[entry.OtherUID] = !prevState; - } - - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - _configuration.SetCurrentServerUidComment(_editNickEntry, _editCharComment); - _configuration.Save(); - _editCharComment = _configuration.GetCurrentServerUidComments().ContainsKey(entry.OtherUID) - ? _configuration.GetCurrentServerUidComments()[entry.OtherUID] - : string.Empty; - _editNickEntry = entry.OtherUID; - } - } - else - { - ImGui.SetCursorPosY(originalY); - - ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * 2); - if (ImGui.InputTextWithHint("", "Nick/Notes", ref _editCharComment, 255, ImGuiInputTextFlags.EnterReturnsTrue)) - { - _configuration.SetCurrentServerUidComment(entry.OtherUID, _editCharComment); - _configuration.Save(); - _editNickEntry = string.Empty; - } - - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - _editNickEntry = string.Empty; - } - UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); - } - - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); - ImGui.SetCursorPosY(originalY); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) + _timeout.Reset(); + + if (ImGuiComponents.IconButton(button)) { if (UiShared.CtrlPressed()) { - _ = _apiController.SendPairedClientRemoval(entry.OtherUID); - _apiController.PairedClients.Remove(entry); + Logger.Debug(users.Count.ToString()); + foreach (var entry in users) + { + _ = _apiController.SendPairedClientPauseChange(entry.OtherUID, !entry.IsPaused); + } + + _timeout.Start(); + _buttonState = !_buttonState; } } - UiShared.AttachToolTip("Hold CTRL and click to unpair permanently from " + entryUID); - - if (entry.IsSynced) - { - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X - ImGui.GetStyle().ItemSpacing.X - trashButtonSize.X); - ImGui.SetCursorPosY(originalY); - if (ImGuiComponents.IconButton(pauseIcon)) - { - _ = _apiController.SendPairedClientPauseChange(entry.OtherUID, !entry.IsPaused); - } - UiShared.AttachToolTip(!entry.IsPaused - ? "Pause pairing with " + entryUID - : "Resume pairing with " + entryUID); - } + UiShared.AttachToolTip($"Hold Control to {(button == FontAwesomeIcon.Play ? "resume" : "pause")} pairing with {users.Count} out of {userCount} displayed users."); } - - private void DrawPairList() + else { - UiShared.DrawWithID("addpair", DrawAddPair); - UiShared.DrawWithID("pairs", DrawPairs); - _transferPartHeight = ImGui.GetCursorPosY(); - UiShared.DrawWithID("filter", DrawFilter); - } - - private void DrawPairs() - { - var ySize = _transferPartHeight == 0 - ? 1 - : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _transferPartHeight - ImGui.GetCursorPosY(); - var users = GetFilteredUsers(); - - if (_configuration.ReverseUserSort) users = users.Reverse(); - - ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), false); - foreach (var entry in users.ToList()) - { - UiShared.DrawWithID(entry.OtherUID, () => DrawPairedClient(entry)); - } - ImGui.EndChild(); - } - - private IEnumerable GetFilteredUsers() - { - return _apiController.PairedClients.Where(p => - { - if (_characterOrCommentFilter.IsNullOrEmpty()) return true; - _configuration.GetCurrentServerUidComments().TryGetValue(p.OtherUID, out var comment); - var uid = p.VanityUID.IsNullOrEmpty() ? p.OtherUID : p.VanityUID; - return uid.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) || - (comment?.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) ?? false); - }); - } - - private void DrawServerStatus() - { - var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Link); - var userCount = _apiController.OnlineUsers.ToString(); - var userSize = ImGui.CalcTextSize(userCount); - var textSize = ImGui.CalcTextSize("Users Online"); - - if (_apiController.ServerState is ServerState.Connected) - { - ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X) / 2 - (userSize.X + textSize.X) / 2); - ImGui.AlignTextToFramePadding(); - ImGui.TextColored(ImGuiColors.ParsedGreen, userCount); - ImGui.SameLine(); - ImGui.AlignTextToFramePadding(); - ImGui.Text("Users Online"); - } - else - { - ImGui.AlignTextToFramePadding(); - ImGui.TextColored(ImGuiColors.DalamudRed, "Not connected to any server"); - } - - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); - var color = UiShared.GetBoolColor(!_configuration.FullPause); - var connectedIcon = !_configuration.FullPause ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink; - - ImGui.PushStyleColor(ImGuiCol.Text, color); - if (ImGuiComponents.IconButton(connectedIcon)) - { - _configuration.FullPause = !_configuration.FullPause; - _configuration.Save(); - _ = _apiController.CreateConnections(); - } - ImGui.PopStyleColor(); - UiShared.AttachToolTip(!_configuration.FullPause ? "Disconnect from " + _apiController.ServerDictionary[_configuration.ApiUri] : "Connect to " + _apiController.ServerDictionary[_configuration.ApiUri]); - } - - private void DrawTransfers() - { - var currentUploads = _apiController.CurrentUploads.ToList(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.Text(FontAwesomeIcon.Upload.ToIconString()); - ImGui.PopFont(); - ImGui.SameLine(35 * ImGuiHelpers.GlobalScale); - - if (currentUploads.Any()) - { - var totalUploads = currentUploads.Count; - - var doneUploads = currentUploads.Count(c => c.IsTransferred); - var totalUploaded = currentUploads.Sum(c => c.Transferred); - var totalToUpload = currentUploads.Sum(c => c.Total); - - ImGui.Text($"{doneUploads}/{totalUploads}"); - var uploadText = $"({UiShared.ByteToString(totalUploaded)}/{UiShared.ByteToString(totalToUpload)})"; - var textSize = ImGui.CalcTextSize(uploadText); - ImGui.SameLine(_windowContentWidth - textSize.X); - ImGui.Text(uploadText); - } - else - { - ImGui.Text("No uploads in progress"); - } - - var currentDownloads = _apiController.CurrentDownloads.SelectMany(k => k.Value).ToList(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.Text(FontAwesomeIcon.Download.ToIconString()); - ImGui.PopFont(); - ImGui.SameLine(35 * ImGuiHelpers.GlobalScale); - - if (currentDownloads.Any()) - { - var totalDownloads = currentDownloads.Count(); - var doneDownloads = currentDownloads.Count(c => c.IsTransferred); - var totalDownloaded = currentDownloads.Sum(c => c.Transferred); - var totalToDownload = currentDownloads.Sum(c => c.Total); - - ImGui.Text($"{doneDownloads}/{totalDownloads}"); - var downloadText = - $"({UiShared.ByteToString(totalDownloaded)}/{UiShared.ByteToString(totalToDownload)})"; - var textSize = ImGui.CalcTextSize(downloadText); - ImGui.SameLine(_windowContentWidth - textSize.X); - ImGui.Text(downloadText); - } - else - { - ImGui.Text("No downloads in progress"); - } - - ImGui.SameLine(); - } - - private void DrawUIDHeader() - { - var uidText = GetUidText(); - var buttonSizeX = 0f; - - if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); - var uidTextSize = ImGui.CalcTextSize(uidText); - if (_uiShared.UidFontBuilt) ImGui.PopFont(); - - var originalPos = ImGui.GetCursorPos(); - ImGui.SetWindowFontScale(1.5f); - var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Cog); - buttonSizeX -= buttonSize.X - ImGui.GetStyle().ItemSpacing.X * 2; - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); - ImGui.SetCursorPosY(originalPos.Y + uidTextSize.Y / 2 - buttonSize.Y / 2); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog)) - { - OpenSettingsUi?.Invoke(); - } - UiShared.AttachToolTip("Open the Mare Synchronos Settings"); - - ImGui.SameLine(); //Important to draw the uidText consistently - ImGui.SetCursorPos(originalPos); - - if (_apiController.ServerState is ServerState.Connected) - { - buttonSizeX += UiShared.GetIconButtonSize(FontAwesomeIcon.Copy).X - ImGui.GetStyle().ItemSpacing.X * 2; - ImGui.SetCursorPosY(originalPos.Y + uidTextSize.Y / 2 - buttonSize.Y / 2); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) - { - ImGui.SetClipboardText(_apiController.UID); - } - UiShared.AttachToolTip("Copy your UID to clipboard"); - ImGui.SameLine(); - } - ImGui.SetWindowFontScale(1f); - - ImGui.SetCursorPosY(originalPos.Y + buttonSize.Y / 2 - uidTextSize.Y / 2 - ImGui.GetStyle().ItemSpacing.Y / 2); - ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMax().X + ImGui.GetWindowContentRegionMin().X) / 2 + buttonSizeX - uidTextSize.X / 2); - if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); - ImGui.TextColored(GetUidColor(), uidText); - if (_uiShared.UidFontBuilt) ImGui.PopFont(); - - if (_apiController.ServerState is not ServerState.Connected) - { - UiShared.ColorTextWrapped(GetServerError(), GetUidColor()); - } - } - - - private string GetServerError() - { - return _apiController.ServerState switch - { - ServerState.Disconnected => "You are currently disconnected from the Mare Synchronos server.", - ServerState.Unauthorized => "Your account is not present on the server anymore or you are banned.", - ServerState.Offline => "Your selected Mare Synchronos server is currently offline.", - ServerState.VersionMisMatch => - "Your plugin or the server you are connecting to is out of date. Please update your plugin now. If you already did so, contact the server provider to update their server to the latest version.", - ServerState.RateLimited => "You are rate limited for (re)connecting too often. Wait and try again later.", - ServerState.Connected => string.Empty, - _ => string.Empty - }; - } - - private Vector4 GetUidColor() - { - return _apiController.ServerState switch - { - ServerState.Connected => ImGuiColors.ParsedGreen, - ServerState.Disconnected => ImGuiColors.DalamudYellow, - ServerState.Unauthorized => ImGuiColors.DalamudRed, - ServerState.VersionMisMatch => ImGuiColors.DalamudRed, - ServerState.Offline => ImGuiColors.DalamudRed, - ServerState.RateLimited => ImGuiColors.DalamudYellow, - _ => ImGuiColors.DalamudRed - }; - } - - private string GetUidText() - { - return _apiController.ServerState switch - { - ServerState.Disconnected => "Disconnected", - ServerState.Unauthorized => "Unauthorized", - ServerState.VersionMisMatch => "Version mismatch", - ServerState.Offline => "Unavailable", - ServerState.RateLimited => "Rate Limited", - ServerState.Connected => _apiController.UID, - _ => string.Empty - }; + var availableAt = (15000 - _timeout.ElapsedMilliseconds) / 1000; + ImGuiComponents.DisabledButton(button); + UiShared.AttachToolTip($"Next execution is available at {availableAt} seconds"); } } + + private void DrawPairedClient(ClientPairDto entry) + { + var pauseIcon = entry.IsPaused ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; + + var buttonSize = UiShared.GetIconButtonSize(pauseIcon); + var trashButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Trash); + var entryUID = string.IsNullOrEmpty(entry.VanityUID) ? entry.OtherUID : entry.VanityUID; + var textSize = ImGui.CalcTextSize(entryUID); + var originalY = ImGui.GetCursorPosY(); + var buttonSizes = buttonSize.Y + trashButtonSize.Y; + + var textPos = originalY + buttonSize.Y / 2 - textSize.Y / 2; + ImGui.SetCursorPosY(textPos); + if (!entry.IsSynced) + { + ImGui.PushFont(UiBuilder.IconFont); + UiShared.ColorText(FontAwesomeIcon.ArrowUp.ToIconString(), ImGuiColors.DalamudRed); + ImGui.PopFont(); + + UiShared.AttachToolTip(entryUID + " has not added you back"); + } + else if (entry.IsPaused || entry.IsPausedFromOthers) + { + ImGui.PushFont(UiBuilder.IconFont); + UiShared.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow); + ImGui.PopFont(); + + UiShared.AttachToolTip("Pairing status with " + entryUID + " is paused"); + } + else + { + ImGui.PushFont(UiBuilder.IconFont); + UiShared.ColorText(FontAwesomeIcon.Check.ToIconString(), ImGuiColors.ParsedGreen); + ImGui.PopFont(); + + UiShared.AttachToolTip("You are paired with " + entryUID); + } + + var textIsUid = true; + _showUidForEntry.TryGetValue(entry.OtherUID, out var showUidInsteadOfName); + if (!showUidInsteadOfName && _configuration.GetCurrentServerUidComments().TryGetValue(entry.OtherUID, out var playerText)) + { + if (string.IsNullOrEmpty(playerText)) + { + playerText = entryUID; + } + else + { + textIsUid = false; + } + } + else + { + playerText = entryUID; + } + + ImGui.SameLine(); + if (_editNickEntry != entry.OtherUID) + { + ImGui.SetCursorPosY(textPos); + if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted(playerText); + if (textIsUid) ImGui.PopFont(); + UiShared.AttachToolTip("Left click to switch between UID display and nick" + Environment.NewLine + + "Right click to change nick for " + entryUID); + if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) + { + var prevState = textIsUid; + if (_showUidForEntry.ContainsKey(entry.OtherUID)) + { + prevState = _showUidForEntry[entry.OtherUID]; + } + + _showUidForEntry[entry.OtherUID] = !prevState; + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configuration.SetCurrentServerUidComment(_editNickEntry, _editCharComment); + _configuration.Save(); + _editCharComment = _configuration.GetCurrentServerUidComments().ContainsKey(entry.OtherUID) + ? _configuration.GetCurrentServerUidComments()[entry.OtherUID] + : string.Empty; + _editNickEntry = entry.OtherUID; + } + } + else + { + ImGui.SetCursorPosY(originalY); + + ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * 2); + if (ImGui.InputTextWithHint("", "Nick/Notes", ref _editCharComment, 255, ImGuiInputTextFlags.EnterReturnsTrue)) + { + _configuration.SetCurrentServerUidComment(entry.OtherUID, _editCharComment); + _configuration.Save(); + _editNickEntry = string.Empty; + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _editNickEntry = string.Empty; + } + UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); + } + + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); + ImGui.SetCursorPosY(originalY); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) + { + if (UiShared.CtrlPressed()) + { + _ = _apiController.SendPairedClientRemoval(entry.OtherUID); + _apiController.PairedClients.Remove(entry); + } + } + UiShared.AttachToolTip("Hold CTRL and click to unpair permanently from " + entryUID); + + if (entry.IsSynced) + { + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X - ImGui.GetStyle().ItemSpacing.X - trashButtonSize.X); + ImGui.SetCursorPosY(originalY); + if (ImGuiComponents.IconButton(pauseIcon)) + { + _ = _apiController.SendPairedClientPauseChange(entry.OtherUID, !entry.IsPaused); + } + UiShared.AttachToolTip(!entry.IsPaused + ? "Pause pairing with " + entryUID + : "Resume pairing with " + entryUID); + } + } + + private void DrawPairList() + { + UiShared.DrawWithID("addpair", DrawAddPair); + UiShared.DrawWithID("pairs", DrawPairs); + _transferPartHeight = ImGui.GetCursorPosY(); + UiShared.DrawWithID("filter", DrawFilter); + } + + private void DrawPairs() + { + var ySize = _transferPartHeight == 0 + ? 1 + : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _transferPartHeight - ImGui.GetCursorPosY(); + var users = GetFilteredUsers(); + + if (_configuration.ReverseUserSort) users = users.Reverse(); + + ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), false); + foreach (var entry in users.ToList()) + { + UiShared.DrawWithID(entry.OtherUID, () => DrawPairedClient(entry)); + } + ImGui.EndChild(); + } + + private IEnumerable GetFilteredUsers() + { + return _apiController.PairedClients.Where(p => + { + if (_characterOrCommentFilter.IsNullOrEmpty()) return true; + _configuration.GetCurrentServerUidComments().TryGetValue(p.OtherUID, out var comment); + var uid = p.VanityUID.IsNullOrEmpty() ? p.OtherUID : p.VanityUID; + return uid.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) || + (comment?.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) ?? false); + }); + } + + private void DrawServerStatus() + { + var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Link); + var userCount = _apiController.OnlineUsers.ToString(); + var userSize = ImGui.CalcTextSize(userCount); + var textSize = ImGui.CalcTextSize("Users Online"); + + if (_apiController.ServerState is ServerState.Connected) + { + ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X) / 2 - (userSize.X + textSize.X) / 2); + ImGui.AlignTextToFramePadding(); + ImGui.TextColored(ImGuiColors.ParsedGreen, userCount); + ImGui.SameLine(); + ImGui.AlignTextToFramePadding(); + ImGui.Text("Users Online"); + } + else + { + ImGui.AlignTextToFramePadding(); + ImGui.TextColored(ImGuiColors.DalamudRed, "Not connected to any server"); + } + + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); + var color = UiShared.GetBoolColor(!_configuration.FullPause); + var connectedIcon = !_configuration.FullPause ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink; + + ImGui.PushStyleColor(ImGuiCol.Text, color); + if (ImGuiComponents.IconButton(connectedIcon)) + { + _configuration.FullPause = !_configuration.FullPause; + _configuration.Save(); + _ = _apiController.CreateConnections(); + } + ImGui.PopStyleColor(); + UiShared.AttachToolTip(!_configuration.FullPause ? "Disconnect from " + _apiController.ServerDictionary[_configuration.ApiUri] : "Connect to " + _apiController.ServerDictionary[_configuration.ApiUri]); + } + + private void DrawTransfers() + { + var currentUploads = _apiController.CurrentUploads.ToList(); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.Text(FontAwesomeIcon.Upload.ToIconString()); + ImGui.PopFont(); + ImGui.SameLine(35 * ImGuiHelpers.GlobalScale); + + if (currentUploads.Any()) + { + var totalUploads = currentUploads.Count; + + var doneUploads = currentUploads.Count(c => c.IsTransferred); + var totalUploaded = currentUploads.Sum(c => c.Transferred); + var totalToUpload = currentUploads.Sum(c => c.Total); + + ImGui.Text($"{doneUploads}/{totalUploads}"); + var uploadText = $"({UiShared.ByteToString(totalUploaded)}/{UiShared.ByteToString(totalToUpload)})"; + var textSize = ImGui.CalcTextSize(uploadText); + ImGui.SameLine(_windowContentWidth - textSize.X); + ImGui.Text(uploadText); + } + else + { + ImGui.Text("No uploads in progress"); + } + + var currentDownloads = _apiController.CurrentDownloads.SelectMany(k => k.Value).ToList(); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.Text(FontAwesomeIcon.Download.ToIconString()); + ImGui.PopFont(); + ImGui.SameLine(35 * ImGuiHelpers.GlobalScale); + + if (currentDownloads.Any()) + { + var totalDownloads = currentDownloads.Count(); + var doneDownloads = currentDownloads.Count(c => c.IsTransferred); + var totalDownloaded = currentDownloads.Sum(c => c.Transferred); + var totalToDownload = currentDownloads.Sum(c => c.Total); + + ImGui.Text($"{doneDownloads}/{totalDownloads}"); + var downloadText = + $"({UiShared.ByteToString(totalDownloaded)}/{UiShared.ByteToString(totalToDownload)})"; + var textSize = ImGui.CalcTextSize(downloadText); + ImGui.SameLine(_windowContentWidth - textSize.X); + ImGui.Text(downloadText); + } + else + { + ImGui.Text("No downloads in progress"); + } + + ImGui.SameLine(); + } + + private void DrawUIDHeader() + { + var uidText = GetUidText(); + var buttonSizeX = 0f; + + if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); + var uidTextSize = ImGui.CalcTextSize(uidText); + if (_uiShared.UidFontBuilt) ImGui.PopFont(); + + var originalPos = ImGui.GetCursorPos(); + ImGui.SetWindowFontScale(1.5f); + var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Cog); + buttonSizeX -= buttonSize.X - ImGui.GetStyle().ItemSpacing.X * 2; + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); + ImGui.SetCursorPosY(originalPos.Y + uidTextSize.Y / 2 - buttonSize.Y / 2); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Cog)) + { + OpenSettingsUi?.Invoke(); + } + UiShared.AttachToolTip("Open the Mare Synchronos Settings"); + + ImGui.SameLine(); //Important to draw the uidText consistently + ImGui.SetCursorPos(originalPos); + + if (_apiController.ServerState is ServerState.Connected) + { + buttonSizeX += UiShared.GetIconButtonSize(FontAwesomeIcon.Copy).X - ImGui.GetStyle().ItemSpacing.X * 2; + ImGui.SetCursorPosY(originalPos.Y + uidTextSize.Y / 2 - buttonSize.Y / 2); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) + { + ImGui.SetClipboardText(_apiController.UID); + } + UiShared.AttachToolTip("Copy your UID to clipboard"); + ImGui.SameLine(); + } + ImGui.SetWindowFontScale(1f); + + ImGui.SetCursorPosY(originalPos.Y + buttonSize.Y / 2 - uidTextSize.Y / 2 - ImGui.GetStyle().ItemSpacing.Y / 2); + ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMax().X + ImGui.GetWindowContentRegionMin().X) / 2 + buttonSizeX - uidTextSize.X / 2); + if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); + ImGui.TextColored(GetUidColor(), uidText); + if (_uiShared.UidFontBuilt) ImGui.PopFont(); + + if (_apiController.ServerState is not ServerState.Connected) + { + UiShared.ColorTextWrapped(GetServerError(), GetUidColor()); + } + } + + + private string GetServerError() + { + return _apiController.ServerState switch + { + ServerState.Disconnected => "You are currently disconnected from the Mare Synchronos server.", + ServerState.Unauthorized => "Your account is not present on the server anymore or you are banned.", + ServerState.Offline => "Your selected Mare Synchronos server is currently offline.", + ServerState.VersionMisMatch => + "Your plugin or the server you are connecting to is out of date. Please update your plugin now. If you already did so, contact the server provider to update their server to the latest version.", + ServerState.RateLimited => "You are rate limited for (re)connecting too often. Wait and try again later.", + ServerState.Connected => string.Empty, + _ => string.Empty + }; + } + + private Vector4 GetUidColor() + { + return _apiController.ServerState switch + { + ServerState.Connected => ImGuiColors.ParsedGreen, + ServerState.Disconnected => ImGuiColors.DalamudYellow, + ServerState.Unauthorized => ImGuiColors.DalamudRed, + ServerState.VersionMisMatch => ImGuiColors.DalamudRed, + ServerState.Offline => ImGuiColors.DalamudRed, + ServerState.RateLimited => ImGuiColors.DalamudYellow, + _ => ImGuiColors.DalamudRed + }; + } + + private string GetUidText() + { + return _apiController.ServerState switch + { + ServerState.Disconnected => "Disconnected", + ServerState.Unauthorized => "Unauthorized", + ServerState.VersionMisMatch => "Version mismatch", + ServerState.Offline => "Unavailable", + ServerState.RateLimited => "Rate Limited", + ServerState.Connected => _apiController.UID, + _ => string.Empty + }; + } } diff --git a/MareSynchronos/UI/IntroUI.cs b/MareSynchronos/UI/IntroUI.cs index e15d85b..26239a3 100644 --- a/MareSynchronos/UI/IntroUI.cs +++ b/MareSynchronos/UI/IntroUI.cs @@ -12,287 +12,286 @@ using MareSynchronos.Localization; using Dalamud.Utility; using MareSynchronos.FileCache; -namespace MareSynchronos.UI +namespace MareSynchronos.UI; + +internal class IntroUi : Window, IDisposable { - internal class IntroUi : Window, IDisposable + private readonly UiShared _uiShared; + private readonly Configuration _pluginConfiguration; + private readonly PeriodicFileScanner _fileCacheManager; + private readonly WindowSystem _windowSystem; + private bool _readFirstPage; + + public event SwitchUi? SwitchToMainUi; + + private string[] TosParagraphs; + + private Tuple _darkSoulsCaptcha1 = new(string.Empty, string.Empty); + private Tuple _darkSoulsCaptcha2 = new(string.Empty, string.Empty); + private Tuple _darkSoulsCaptcha3 = new(string.Empty, string.Empty); + private string _enteredDarkSoulsCaptcha1 = string.Empty; + private string _enteredDarkSoulsCaptcha2 = string.Empty; + private string _enteredDarkSoulsCaptcha3 = string.Empty; + + private bool _failedOnce = false; + private Task _timeoutTask; + private string _timeoutTime; + + private Dictionary _languages = new() { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } }; + private int _currentLanguage; + + private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1.Trim() + && _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2.Trim() + && _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3.Trim(); + + + public void Dispose() { - private readonly UiShared _uiShared; - private readonly Configuration _pluginConfiguration; - private readonly PeriodicFileScanner _fileCacheManager; - private readonly WindowSystem _windowSystem; - private bool _readFirstPage; + Logger.Verbose("Disposing " + nameof(IntroUi)); - public event SwitchUi? SwitchToMainUi; + _windowSystem.RemoveWindow(this); + } - private string[] TosParagraphs; + public IntroUi(WindowSystem windowSystem, UiShared uiShared, Configuration pluginConfiguration, + PeriodicFileScanner fileCacheManager) : base("Mare Synchronos Setup") + { + Logger.Verbose("Creating " + nameof(IntroUi)); - private Tuple _darkSoulsCaptcha1 = new(string.Empty, string.Empty); - private Tuple _darkSoulsCaptcha2 = new(string.Empty, string.Empty); - private Tuple _darkSoulsCaptcha3 = new(string.Empty, string.Empty); - private string _enteredDarkSoulsCaptcha1 = string.Empty; - private string _enteredDarkSoulsCaptcha2 = string.Empty; - private string _enteredDarkSoulsCaptcha3 = string.Empty; + _uiShared = uiShared; + _pluginConfiguration = pluginConfiguration; + _fileCacheManager = fileCacheManager; + _windowSystem = windowSystem; - private bool _failedOnce = false; - private Task _timeoutTask; - private string _timeoutTime; - - private Dictionary _languages = new() { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } }; - private int _currentLanguage; - - private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1.Trim() - && _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2.Trim() - && _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3.Trim(); - - - public void Dispose() + SizeConstraints = new WindowSizeConstraints() { - Logger.Verbose("Disposing " + nameof(IntroUi)); + MinimumSize = new Vector2(600, 400), + MaximumSize = new Vector2(600, 2000) + }; - _windowSystem.RemoveWindow(this); - } + GetToSLocalization(); - public IntroUi(WindowSystem windowSystem, UiShared uiShared, Configuration pluginConfiguration, - PeriodicFileScanner fileCacheManager) : base("Mare Synchronos Setup") + _windowSystem.AddWindow(this); + } + + public override void Draw() + { + if (!_pluginConfiguration.AcceptedAgreement && !_readFirstPage) { - Logger.Verbose("Creating " + nameof(IntroUi)); + if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); + ImGui.TextUnformatted("Welcome to Mare Synchronos"); + if (_uiShared.UidFontBuilt) ImGui.PopFont(); + ImGui.Separator(); + UiShared.TextWrapped("Mare Synchronos is a plugin that will replicate your full current character state including all Penumbra mods to other paired Mare Synchronos users. " + + "Note that you will have to have Penumbra as well as Glamourer installed to use this plugin."); + UiShared.TextWrapped("We will have to setup a few things first before you can start using this plugin. Click on next to continue."); - _uiShared = uiShared; - _pluginConfiguration = pluginConfiguration; - _fileCacheManager = fileCacheManager; - _windowSystem = windowSystem; - - SizeConstraints = new WindowSizeConstraints() + UiShared.ColorTextWrapped("Note: Any modifications you have applied through anything but Penumbra cannot be shared and your character state on other clients " + + "might look broken because of this or others players mods might not apply on your end altogether. " + + "If you want to use this plugin you will have to move your mods to Penumbra.", ImGuiColors.DalamudYellow); + if (!_uiShared.DrawOtherPluginState()) return; + ImGui.Separator(); + if (ImGui.Button("Next##toAgreement")) { - MinimumSize = new Vector2(600, 400), - MaximumSize = new Vector2(600, 2000) - }; - - GetToSLocalization(); - - _windowSystem.AddWindow(this); - } - - public override void Draw() - { - if (!_pluginConfiguration.AcceptedAgreement && !_readFirstPage) - { - if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); - ImGui.TextUnformatted("Welcome to Mare Synchronos"); - if (_uiShared.UidFontBuilt) ImGui.PopFont(); - ImGui.Separator(); - UiShared.TextWrapped("Mare Synchronos is a plugin that will replicate your full current character state including all Penumbra mods to other paired Mare Synchronos users. " + - "Note that you will have to have Penumbra as well as Glamourer installed to use this plugin."); - UiShared.TextWrapped("We will have to setup a few things first before you can start using this plugin. Click on next to continue."); - - UiShared.ColorTextWrapped("Note: Any modifications you have applied through anything but Penumbra cannot be shared and your character state on other clients " + - "might look broken because of this or others players mods might not apply on your end altogether. " + - "If you want to use this plugin you will have to move your mods to Penumbra.", ImGuiColors.DalamudYellow); - if (!_uiShared.DrawOtherPluginState()) return; - ImGui.Separator(); - if (ImGui.Button("Next##toAgreement")) - { - _readFirstPage = true; - } + _readFirstPage = true; } - else if (!_pluginConfiguration.AcceptedAgreement && _readFirstPage) + } + else if (!_pluginConfiguration.AcceptedAgreement && _readFirstPage) + { + if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); + var textSize = ImGui.CalcTextSize(Strings.ToS.LanguageLabel); + ImGui.TextUnformatted(Strings.ToS.AgreementLabel); + if (_uiShared.UidFontBuilt) ImGui.PopFont(); + + ImGui.SameLine(); + var languageSize = ImGui.CalcTextSize(Strings.ToS.LanguageLabel); + ImGui.SetCursorPosX(ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - languageSize.X - 80); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + textSize.Y / 2 - languageSize.Y / 2); + + ImGui.TextUnformatted(Strings.ToS.LanguageLabel); + ImGui.SameLine(); + ImGui.SetCursorPosY(ImGui.GetCursorPosY() + textSize.Y / 2 - (languageSize.Y + ImGui.GetStyle().FramePadding.Y) / 2); + ImGui.SetNextItemWidth(80); + if (ImGui.Combo("", ref _currentLanguage, _languages.Keys.ToArray(), _languages.Count)) { - if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); - var textSize = ImGui.CalcTextSize(Strings.ToS.LanguageLabel); - ImGui.TextUnformatted(Strings.ToS.AgreementLabel); - if (_uiShared.UidFontBuilt) ImGui.PopFont(); + GetToSLocalization(_currentLanguage); + } - ImGui.SameLine(); - var languageSize = ImGui.CalcTextSize(Strings.ToS.LanguageLabel); - ImGui.SetCursorPosX(ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X - languageSize.X - 80); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + textSize.Y / 2 - languageSize.Y / 2); + ImGui.Separator(); + ImGui.SetWindowFontScale(1.5f); + string readThis = Strings.ToS.ReadLabel; + textSize = ImGui.CalcTextSize(readThis); + ImGui.SetCursorPosX(ImGui.GetWindowSize().X / 2 - textSize.X / 2); + UiShared.ColorText(readThis, ImGuiColors.DalamudRed); + ImGui.SetWindowFontScale(1.0f); + ImGui.Separator(); - ImGui.TextUnformatted(Strings.ToS.LanguageLabel); - ImGui.SameLine(); - ImGui.SetCursorPosY(ImGui.GetCursorPosY() + textSize.Y / 2 - (languageSize.Y + ImGui.GetStyle().FramePadding.Y) / 2); - ImGui.SetNextItemWidth(80); - if (ImGui.Combo("", ref _currentLanguage, _languages.Keys.ToArray(), _languages.Count)) + + UiShared.TextWrapped(TosParagraphs[0]); + UiShared.TextWrapped(TosParagraphs[1]); + UiShared.TextWrapped(TosParagraphs[2]); + UiShared.TextWrapped(TosParagraphs[3]); + UiShared.TextWrapped(TosParagraphs[4]); + UiShared.TextWrapped(TosParagraphs[5]); + + ImGui.Separator(); + if ((!_pluginConfiguration.DarkSoulsAgreement || DarkSoulsCaptchaValid) && (_timeoutTask?.IsCompleted ?? true)) + { + if (ImGui.Button(Strings.ToS.AgreeLabel + "##toSetup")) { - GetToSLocalization(_currentLanguage); - } + _enteredDarkSoulsCaptcha1 = string.Empty; + _enteredDarkSoulsCaptcha2 = string.Empty; + _enteredDarkSoulsCaptcha3 = string.Empty; - ImGui.Separator(); - ImGui.SetWindowFontScale(1.5f); - string readThis = Strings.ToS.ReadLabel; - textSize = ImGui.CalcTextSize(readThis); - ImGui.SetCursorPosX(ImGui.GetWindowSize().X / 2 - textSize.X / 2); - UiShared.ColorText(readThis, ImGuiColors.DalamudRed); - ImGui.SetWindowFontScale(1.0f); - ImGui.Separator(); - - - UiShared.TextWrapped(TosParagraphs[0]); - UiShared.TextWrapped(TosParagraphs[1]); - UiShared.TextWrapped(TosParagraphs[2]); - UiShared.TextWrapped(TosParagraphs[3]); - UiShared.TextWrapped(TosParagraphs[4]); - UiShared.TextWrapped(TosParagraphs[5]); - - ImGui.Separator(); - if ((!_pluginConfiguration.DarkSoulsAgreement || DarkSoulsCaptchaValid) && (_timeoutTask?.IsCompleted ?? true)) - { - if (ImGui.Button(Strings.ToS.AgreeLabel + "##toSetup")) + if (UiShared.CtrlPressed()) { - _enteredDarkSoulsCaptcha1 = string.Empty; - _enteredDarkSoulsCaptcha2 = string.Empty; - _enteredDarkSoulsCaptcha3 = string.Empty; - - if (UiShared.CtrlPressed()) - { - _pluginConfiguration.AcceptedAgreement = true; - _pluginConfiguration.Save(); - } - else - { - if (!_failedOnce) - { - _failedOnce = true; - _timeoutTask = Task.Run(async () => - { - for (int i = 60; i > 0; i--) - { - _timeoutTime = $"{i}s " + Strings.ToS.RemainingLabel; - Logger.Debug(_timeoutTime); - await Task.Delay(TimeSpan.FromSeconds(1)); - } - }); - } - else - { - _pluginConfiguration.DarkSoulsAgreement = true; - _pluginConfiguration.Save(); - GenerateDarkSoulsAgreementCaptcha(); - } - } - } - } - else - { - if (_failedOnce && (!_timeoutTask?.IsCompleted ?? true)) - { - UiShared.ColorTextWrapped(Strings.ToS.FailedLabel, ImGuiColors.DalamudYellow); - UiShared.TextWrapped(Strings.ToS.TimeoutLabel); - UiShared.TextWrapped(_timeoutTime); + _pluginConfiguration.AcceptedAgreement = true; + _pluginConfiguration.Save(); } else { - UiShared.ColorTextWrapped(Strings.ToS.FailedAgainLabel, ImGuiColors.DalamudYellow); - UiShared.TextWrapped(Strings.ToS.PuzzleLabel); - UiShared.TextWrapped(Strings.ToS.PuzzleDescLabel); - ImGui.SetNextItemWidth(100); - ImGui.InputText(_darkSoulsCaptcha1.Item1, ref _enteredDarkSoulsCaptcha1, 255); - ImGui.SetNextItemWidth(100); - ImGui.InputText(_darkSoulsCaptcha2.Item1, ref _enteredDarkSoulsCaptcha2, 255); - ImGui.SetNextItemWidth(100); - ImGui.InputText(_darkSoulsCaptcha3.Item1, ref _enteredDarkSoulsCaptcha3, 255); + if (!_failedOnce) + { + _failedOnce = true; + _timeoutTask = Task.Run(async () => + { + for (int i = 60; i > 0; i--) + { + _timeoutTime = $"{i}s " + Strings.ToS.RemainingLabel; + Logger.Debug(_timeoutTime); + await Task.Delay(TimeSpan.FromSeconds(1)); + } + }); + } + else + { + _pluginConfiguration.DarkSoulsAgreement = true; + _pluginConfiguration.Save(); + GenerateDarkSoulsAgreementCaptcha(); + } } } } - else if (_pluginConfiguration.AcceptedAgreement - && (string.IsNullOrEmpty(_pluginConfiguration.CacheFolder) - || _pluginConfiguration.InitialScanComplete == false - || !Directory.Exists(_pluginConfiguration.CacheFolder))) - { - if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); - ImGui.TextUnformatted("File Cache Setup"); - if (_uiShared.UidFontBuilt) ImGui.PopFont(); - ImGui.Separator(); - - if (!_uiShared.HasValidPenumbraModPath) - { - UiShared.ColorTextWrapped("You do not have a valid Penumbra path set. Open Penumbra and set up a valid path for the mod directory.", ImGuiColors.DalamudRed); - } - else - { - UiShared.TextWrapped("To not unnecessary download files already present on your computer, Mare Synchronos will have to scan your Penumbra mod directory. " + - "Additionally, a local cache folder must be set where Mare Synchronos will download its local file cache to. " + - "Once the Cache Folder is set and the scan complete, this page will automatically forward to registration at a service."); - UiShared.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed."); - UiShared.ColorTextWrapped("Warning: once past this step you should not delete the FileCache.db of Mare Synchronos in the Plugin Configurations folder of Dalamud. " + - "Otherwise on the next launch a full re-scan of the file cache database will be initiated.", ImGuiColors.DalamudYellow); - _uiShared.DrawCacheDirectorySetting(); - } - - if (!_fileCacheManager.IsScanRunning && !string.IsNullOrEmpty(_pluginConfiguration.CacheFolder) && _uiShared.HasValidPenumbraModPath && Directory.Exists(_pluginConfiguration.CacheFolder)) - { - if (ImGui.Button("Start Scan##startScan")) - { - _fileCacheManager.InvokeScan(true); - } - } - else - { - _uiShared.DrawFileScanState(); - } - } - else if (!_uiShared.ApiController.ServerAlive) - { - if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); - ImGui.TextUnformatted("Service Registration"); - if (_uiShared.UidFontBuilt) ImGui.PopFont(); - ImGui.Separator(); - UiShared.TextWrapped("To be able to use Mare Synchronos you will have to register an account."); - UiShared.TextWrapped("For the official Mare Synchronos Servers the account creation will be handled on the official Mare Synchronos Discord. Due to security risks for the server, there is no way to handle this senisibly otherwise."); - UiShared.TextWrapped("If you want to register at the main server \"" + WebAPI.ApiController.MainServer + "\" join the Discord and follow the instructions as described in #registration."); - - if (ImGui.Button("Join the Mare Synchronos Discord")) - { - Util.OpenLink("https://discord.gg/mpNdkrTRjW"); - } - - UiShared.TextWrapped("For all other non official services you will have to contact the appropriate service provider how to obtain a secret key."); - - ImGui.Separator(); - - UiShared.TextWrapped("Once you have received a secret key you can connect to the service using the tools provided below."); - - _uiShared.DrawServiceSelection(() => { }); - } else { - SwitchToMainUi?.Invoke(); - IsOpen = false; + if (_failedOnce && (!_timeoutTask?.IsCompleted ?? true)) + { + UiShared.ColorTextWrapped(Strings.ToS.FailedLabel, ImGuiColors.DalamudYellow); + UiShared.TextWrapped(Strings.ToS.TimeoutLabel); + UiShared.TextWrapped(_timeoutTime); + } + else + { + UiShared.ColorTextWrapped(Strings.ToS.FailedAgainLabel, ImGuiColors.DalamudYellow); + UiShared.TextWrapped(Strings.ToS.PuzzleLabel); + UiShared.TextWrapped(Strings.ToS.PuzzleDescLabel); + ImGui.SetNextItemWidth(100); + ImGui.InputText(_darkSoulsCaptcha1.Item1, ref _enteredDarkSoulsCaptcha1, 255); + ImGui.SetNextItemWidth(100); + ImGui.InputText(_darkSoulsCaptcha2.Item1, ref _enteredDarkSoulsCaptcha2, 255); + ImGui.SetNextItemWidth(100); + ImGui.InputText(_darkSoulsCaptcha3.Item1, ref _enteredDarkSoulsCaptcha3, 255); + } } } - - private string _secretKey = string.Empty; - - private void GetToSLocalization(int changeLanguageTo = -1) + else if (_pluginConfiguration.AcceptedAgreement + && (string.IsNullOrEmpty(_pluginConfiguration.CacheFolder) + || _pluginConfiguration.InitialScanComplete == false + || !Directory.Exists(_pluginConfiguration.CacheFolder))) { - if (changeLanguageTo != -1) + if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); + ImGui.TextUnformatted("File Cache Setup"); + if (_uiShared.UidFontBuilt) ImGui.PopFont(); + ImGui.Separator(); + + if (!_uiShared.HasValidPenumbraModPath) { - _uiShared.LoadLocalization(_languages.ElementAt(changeLanguageTo).Value); + UiShared.ColorTextWrapped("You do not have a valid Penumbra path set. Open Penumbra and set up a valid path for the mod directory.", ImGuiColors.DalamudRed); } - - TosParagraphs = new[] { Strings.ToS.Paragraph1, Strings.ToS.Paragraph2, Strings.ToS.Paragraph3, Strings.ToS.Paragraph4, Strings.ToS.Paragraph5, Strings.ToS.Paragraph6 }; - - if (_pluginConfiguration.DarkSoulsAgreement) + else { - GenerateDarkSoulsAgreementCaptcha(); + UiShared.TextWrapped("To not unnecessary download files already present on your computer, Mare Synchronos will have to scan your Penumbra mod directory. " + + "Additionally, a local cache folder must be set where Mare Synchronos will download its local file cache to. " + + "Once the Cache Folder is set and the scan complete, this page will automatically forward to registration at a service."); + UiShared.TextWrapped("Note: The initial scan, depending on the amount of mods you have, might take a while. Please wait until it is completed."); + UiShared.ColorTextWrapped("Warning: once past this step you should not delete the FileCache.db of Mare Synchronos in the Plugin Configurations folder of Dalamud. " + + "Otherwise on the next launch a full re-scan of the file cache database will be initiated.", ImGuiColors.DalamudYellow); + _uiShared.DrawCacheDirectorySetting(); + } + + if (!_fileCacheManager.IsScanRunning && !string.IsNullOrEmpty(_pluginConfiguration.CacheFolder) && _uiShared.HasValidPenumbraModPath && Directory.Exists(_pluginConfiguration.CacheFolder)) + { + if (ImGui.Button("Start Scan##startScan")) + { + _fileCacheManager.InvokeScan(true); + } + } + else + { + _uiShared.DrawFileScanState(); } } - - private void GenerateDarkSoulsAgreementCaptcha() + else if (!_uiShared.ApiController.ServerAlive) { - _darkSoulsCaptcha1 = GetCaptchaTuple(); - _darkSoulsCaptcha2 = GetCaptchaTuple(); - _darkSoulsCaptcha3 = GetCaptchaTuple(); + if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont); + ImGui.TextUnformatted("Service Registration"); + if (_uiShared.UidFontBuilt) ImGui.PopFont(); + ImGui.Separator(); + UiShared.TextWrapped("To be able to use Mare Synchronos you will have to register an account."); + UiShared.TextWrapped("For the official Mare Synchronos Servers the account creation will be handled on the official Mare Synchronos Discord. Due to security risks for the server, there is no way to handle this senisibly otherwise."); + UiShared.TextWrapped("If you want to register at the main server \"" + WebAPI.ApiController.MainServer + "\" join the Discord and follow the instructions as described in #registration."); + + if (ImGui.Button("Join the Mare Synchronos Discord")) + { + Util.OpenLink("https://discord.gg/mpNdkrTRjW"); + } + + UiShared.TextWrapped("For all other non official services you will have to contact the appropriate service provider how to obtain a secret key."); + + ImGui.Separator(); + + UiShared.TextWrapped("Once you have received a secret key you can connect to the service using the tools provided below."); + + _uiShared.DrawServiceSelection(() => { }); } - - private Tuple GetCaptchaTuple() + else { - Random random = new Random(); - var paragraphIdx = random.Next(TosParagraphs.Length); - var splitParagraph = TosParagraphs[paragraphIdx].Split(".", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray(); - var sentenceIdx = random.Next(splitParagraph.Length); - var splitSentence = splitParagraph[sentenceIdx].Split(" ").Select(c => c.Trim()).Select(c => c.Replace(".", "").Replace(",", "").Replace("'", "")).ToArray(); - var wordIdx = random.Next(splitSentence.Length); - return new($"{Strings.ToS.ParagraphLabel} {paragraphIdx + 1}, {Strings.ToS.SentenceLabel} {sentenceIdx + 1}, {Strings.ToS.WordLabel} {wordIdx + 1}", splitSentence[wordIdx]); + SwitchToMainUi?.Invoke(); + IsOpen = false; } } + + private string _secretKey = string.Empty; + + private void GetToSLocalization(int changeLanguageTo = -1) + { + if (changeLanguageTo != -1) + { + _uiShared.LoadLocalization(_languages.ElementAt(changeLanguageTo).Value); + } + + TosParagraphs = new[] { Strings.ToS.Paragraph1, Strings.ToS.Paragraph2, Strings.ToS.Paragraph3, Strings.ToS.Paragraph4, Strings.ToS.Paragraph5, Strings.ToS.Paragraph6 }; + + if (_pluginConfiguration.DarkSoulsAgreement) + { + GenerateDarkSoulsAgreementCaptcha(); + } + } + + private void GenerateDarkSoulsAgreementCaptcha() + { + _darkSoulsCaptcha1 = GetCaptchaTuple(); + _darkSoulsCaptcha2 = GetCaptchaTuple(); + _darkSoulsCaptcha3 = GetCaptchaTuple(); + } + + private Tuple GetCaptchaTuple() + { + Random random = new Random(); + var paragraphIdx = random.Next(TosParagraphs.Length); + var splitParagraph = TosParagraphs[paragraphIdx].Split(".", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray(); + var sentenceIdx = random.Next(splitParagraph.Length); + var splitSentence = splitParagraph[sentenceIdx].Split(" ").Select(c => c.Trim()).Select(c => c.Replace(".", "").Replace(",", "").Replace("'", "")).ToArray(); + var wordIdx = random.Next(splitSentence.Length); + return new($"{Strings.ToS.ParagraphLabel} {paragraphIdx + 1}, {Strings.ToS.SentenceLabel} {sentenceIdx + 1}, {Strings.ToS.WordLabel} {wordIdx + 1}", splitSentence[wordIdx]); + } } diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index 295f9cc..bfbf45d 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -14,600 +14,599 @@ using MareSynchronos.WebAPI.Utils; using System.Diagnostics; using Dalamud.Utility; -namespace MareSynchronos.UI +namespace MareSynchronos.UI; + +public delegate void SwitchUi(); +public class SettingsUi : Window, IDisposable { - public delegate void SwitchUi(); - public class SettingsUi : Window, IDisposable + private readonly Configuration _configuration; + private readonly WindowSystem _windowSystem; + private readonly ApiController _apiController; + private readonly UiShared _uiShared; + public event SwitchUi? SwitchToIntroUi; + + public SettingsUi(WindowSystem windowSystem, + UiShared uiShared, Configuration configuration, ApiController apiController) : base("Mare Synchronos Settings") { - private readonly Configuration _configuration; - private readonly WindowSystem _windowSystem; - private readonly ApiController _apiController; - private readonly UiShared _uiShared; - public event SwitchUi? SwitchToIntroUi; + Logger.Verbose("Creating " + nameof(SettingsUi)); - public SettingsUi(WindowSystem windowSystem, - UiShared uiShared, Configuration configuration, ApiController apiController) : base("Mare Synchronos Settings") + SizeConstraints = new WindowSizeConstraints() { - Logger.Verbose("Creating " + nameof(SettingsUi)); + MinimumSize = new Vector2(800, 400), + MaximumSize = new Vector2(800, 2000), + }; - SizeConstraints = new WindowSizeConstraints() + _configuration = configuration; + _windowSystem = windowSystem; + _apiController = apiController; + _uiShared = uiShared; + windowSystem.AddWindow(this); + } + + public void Dispose() + { + Logger.Verbose("Disposing " + nameof(SettingsUi)); + + _windowSystem.RemoveWindow(this); + } + + public override void Draw() + { + var pluginState = _uiShared.DrawOtherPluginState(); + + DrawSettingsContent(); + } + + private void DrawSettingsContent() + { + _uiShared.PrintServerState(); + ImGui.AlignTextToFramePadding(); + ImGui.Text("Community and Support:"); + ImGui.SameLine(); + if (ImGui.Button("Mare Synchronos Discord")) + { + Util.OpenLink("https://discord.gg/mpNdkrTRjW"); + } + ImGui.Separator(); + if (ImGui.BeginTabBar("mainTabBar")) + { + if (ImGui.BeginTabItem("Cache Settings")) { - MinimumSize = new Vector2(800, 400), - MaximumSize = new Vector2(800, 2000), - }; - - _configuration = configuration; - _windowSystem = windowSystem; - _apiController = apiController; - _uiShared = uiShared; - windowSystem.AddWindow(this); - } - - public void Dispose() - { - Logger.Verbose("Disposing " + nameof(SettingsUi)); - - _windowSystem.RemoveWindow(this); - } - - public override void Draw() - { - var pluginState = _uiShared.DrawOtherPluginState(); - - DrawSettingsContent(); - } - - private void DrawSettingsContent() - { - _uiShared.PrintServerState(); - ImGui.AlignTextToFramePadding(); - ImGui.Text("Community and Support:"); - ImGui.SameLine(); - if (ImGui.Button("Mare Synchronos Discord")) - { - Util.OpenLink("https://discord.gg/mpNdkrTRjW"); + DrawFileCacheSettings(); + ImGui.EndTabItem(); } - ImGui.Separator(); - if (ImGui.BeginTabBar("mainTabBar")) + + if (_apiController.ServerState is ServerState.Connected) { - if (ImGui.BeginTabItem("Cache Settings")) + if (ImGui.BeginTabItem("Transfers")) { - DrawFileCacheSettings(); + DrawCurrentTransfers(); ImGui.EndTabItem(); } - - if (_apiController.ServerState is ServerState.Connected) - { - if (ImGui.BeginTabItem("Transfers")) - { - DrawCurrentTransfers(); - ImGui.EndTabItem(); - } - } - - if (ImGui.BeginTabItem("Blocked Transfers")) - { - DrawBlockedTransfers(); - ImGui.EndTabItem(); - } - - if (ImGui.BeginTabItem("User Administration")) - { - DrawUserAdministration(_apiController.IsConnected); - ImGui.EndTabItem(); - } - - if (_apiController.IsConnected && _apiController.IsModerator) - { - if (ImGui.BeginTabItem("Administration")) - { - DrawAdministration(); - ImGui.EndTabItem(); - } - } - - ImGui.EndTabBar(); } - } - private string _forbiddenFileHashEntry = string.Empty; - private string _forbiddenFileHashForbiddenBy = string.Empty; - private string _bannedUserHashEntry = string.Empty; - private string _bannedUserReasonEntry = string.Empty; - - private void DrawAdministration() - { - if (ImGui.TreeNode("Forbidden Files Changes")) + if (ImGui.BeginTabItem("Blocked Transfers")) { - if (ImGui.BeginTable("ForbiddenFilesTable", 3, ImGuiTableFlags.RowBg)) + DrawBlockedTransfers(); + ImGui.EndTabItem(); + } + + if (ImGui.BeginTabItem("User Administration")) + { + DrawUserAdministration(_apiController.IsConnected); + ImGui.EndTabItem(); + } + + if (_apiController.IsConnected && _apiController.IsModerator) + { + if (ImGui.BeginTabItem("Administration")) { - ImGui.TableSetupColumn("File Hash", ImGuiTableColumnFlags.None, 290); - ImGui.TableSetupColumn("Forbidden By", ImGuiTableColumnFlags.None, 290); - ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 70); + DrawAdministration(); + ImGui.EndTabItem(); + } + } - ImGui.TableHeadersRow(); + ImGui.EndTabBar(); + } + } - foreach (var forbiddenFile in _apiController.AdminForbiddenFiles) + private string _forbiddenFileHashEntry = string.Empty; + private string _forbiddenFileHashForbiddenBy = string.Empty; + private string _bannedUserHashEntry = string.Empty; + private string _bannedUserReasonEntry = string.Empty; + + private void DrawAdministration() + { + if (ImGui.TreeNode("Forbidden Files Changes")) + { + if (ImGui.BeginTable("ForbiddenFilesTable", 3, ImGuiTableFlags.RowBg)) + { + ImGui.TableSetupColumn("File Hash", ImGuiTableColumnFlags.None, 290); + ImGui.TableSetupColumn("Forbidden By", ImGuiTableColumnFlags.None, 290); + ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 70); + + ImGui.TableHeadersRow(); + + foreach (var forbiddenFile in _apiController.AdminForbiddenFiles) + { + ImGui.TableNextColumn(); + + ImGui.Text(forbiddenFile.Hash); + ImGui.TableNextColumn(); + string by = forbiddenFile.ForbiddenBy; + if (ImGui.InputText("##forbiddenBy" + forbiddenFile.Hash, ref by, 255)) { - ImGui.TableNextColumn(); - - ImGui.Text(forbiddenFile.Hash); - ImGui.TableNextColumn(); - string by = forbiddenFile.ForbiddenBy; - if (ImGui.InputText("##forbiddenBy" + forbiddenFile.Hash, ref by, 255)) - { - forbiddenFile.ForbiddenBy = by; - } - - ImGui.TableNextColumn(); - if (_apiController.IsAdmin) - { - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button( - FontAwesomeIcon.Upload.ToIconString() + "##updateFile" + forbiddenFile.Hash)) - { - _ = _apiController.AddOrUpdateForbiddenFileEntry(forbiddenFile); - } - - ImGui.SameLine(); - if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString() + "##deleteFile" + - forbiddenFile.Hash)) - { - _ = _apiController.DeleteForbiddenFileEntry(forbiddenFile); - } - - ImGui.PopFont(); - } - + forbiddenFile.ForbiddenBy = by; } + ImGui.TableNextColumn(); if (_apiController.IsAdmin) { - ImGui.TableNextColumn(); - ImGui.InputText("##addFileHash", ref _forbiddenFileHashEntry, 255); - ImGui.TableNextColumn(); - ImGui.InputText("##addForbiddenBy", ref _forbiddenFileHashForbiddenBy, 255); - ImGui.TableNextColumn(); ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString() + "##addForbiddenFile")) + if (ImGui.Button( + FontAwesomeIcon.Upload.ToIconString() + "##updateFile" + forbiddenFile.Hash)) { - _ = _apiController.AddOrUpdateForbiddenFileEntry(new ForbiddenFileDto() - { - ForbiddenBy = _forbiddenFileHashForbiddenBy, - Hash = _forbiddenFileHashEntry - }); + _ = _apiController.AddOrUpdateForbiddenFileEntry(forbiddenFile); } - ImGui.PopFont(); - ImGui.NextColumn(); - } - - ImGui.EndTable(); - } - - ImGui.TreePop(); - } - - if (ImGui.TreeNode("Banned Users")) - { - if (ImGui.BeginTable("BannedUsersTable", 3, ImGuiTableFlags.RowBg)) - { - ImGui.TableSetupColumn("Character Hash", ImGuiTableColumnFlags.None, 290); - ImGui.TableSetupColumn("Reason", ImGuiTableColumnFlags.None, 290); - ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 70); - - ImGui.TableHeadersRow(); - - foreach (var bannedUser in _apiController.AdminBannedUsers) - { - ImGui.TableNextColumn(); - ImGui.Text(bannedUser.CharacterHash); - - ImGui.TableNextColumn(); - string reason = bannedUser.Reason; - ImGuiInputTextFlags moderatorFlags = _apiController.IsModerator - ? ImGuiInputTextFlags.ReadOnly - : ImGuiInputTextFlags.None; - if (ImGui.InputText("##bannedReason" + bannedUser.CharacterHash, ref reason, 255, - moderatorFlags)) + ImGui.SameLine(); + if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString() + "##deleteFile" + + forbiddenFile.Hash)) { - bannedUser.Reason = reason; - } - - ImGui.TableNextColumn(); - ImGui.PushFont(UiBuilder.IconFont); - if (_apiController.IsAdmin) - { - if (ImGui.Button(FontAwesomeIcon.Upload.ToIconString() + "##updateUser" + - bannedUser.CharacterHash)) - { - _ = _apiController.AddOrUpdateBannedUserEntry(bannedUser); - } - - ImGui.SameLine(); - } - - if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString() + "##deleteUser" + - bannedUser.CharacterHash)) - { - _ = _apiController.DeleteBannedUserEntry(bannedUser); + _ = _apiController.DeleteForbiddenFileEntry(forbiddenFile); } ImGui.PopFont(); } - ImGui.TableNextColumn(); - ImGui.InputText("##addUserHash", ref _bannedUserHashEntry, 255); + } + if (_apiController.IsAdmin) + { ImGui.TableNextColumn(); - if (_apiController.IsAdmin) - { - ImGui.InputText("##addUserReason", ref _bannedUserReasonEntry, 255); - } - else - { - _bannedUserReasonEntry = "Banned by " + _uiShared.PlayerName; - ImGui.InputText("##addUserReason", ref _bannedUserReasonEntry, 255, - ImGuiInputTextFlags.ReadOnly); - } - + ImGui.InputText("##addFileHash", ref _forbiddenFileHashEntry, 255); + ImGui.TableNextColumn(); + ImGui.InputText("##addForbiddenBy", ref _forbiddenFileHashForbiddenBy, 255); ImGui.TableNextColumn(); ImGui.PushFont(UiBuilder.IconFont); if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString() + "##addForbiddenFile")) { - _ = _apiController.AddOrUpdateBannedUserEntry(new BannedUserDto() + _ = _apiController.AddOrUpdateForbiddenFileEntry(new ForbiddenFileDto() { - CharacterHash = _forbiddenFileHashForbiddenBy, - Reason = _forbiddenFileHashEntry + ForbiddenBy = _forbiddenFileHashForbiddenBy, + Hash = _forbiddenFileHashEntry }); } ImGui.PopFont(); - - ImGui.EndTable(); - } - - ImGui.TreePop(); - } - - if (ImGui.TreeNode("Online Users")) - { - if (ImGui.Button("Refresh Online Users")) - { - _ = _apiController.RefreshOnlineUsers(); - } - - if (ImGui.BeginTable("OnlineUsersTable", 3, ImGuiTableFlags.RowBg)) - { - ImGui.TableSetupColumn("UID", ImGuiTableColumnFlags.None, 100); - ImGui.TableSetupColumn("Character Hash", ImGuiTableColumnFlags.None, 300); - ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 70); - - ImGui.TableHeadersRow(); - - foreach (var onlineUser in _apiController.AdminOnlineUsers) - { - ImGui.TableNextColumn(); - ImGui.PushFont(UiBuilder.IconFont); - string icon = onlineUser.IsModerator - ? FontAwesomeIcon.ChessKing.ToIconString() - : onlineUser.IsAdmin - ? FontAwesomeIcon.Crown.ToIconString() - : FontAwesomeIcon.User.ToIconString(); - ImGui.Text(icon); - ImGui.PopFont(); - ImGui.SameLine(); - - ImGui.Text(onlineUser.UID); - ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.Copy.ToIconString() + "##onlineUserCopyUID" + - onlineUser.CharacterNameHash)) - { - ImGui.SetClipboardText(onlineUser.UID); - } - - ImGui.PopFont(); - - ImGui.TableNextColumn(); - string charNameHash = onlineUser.CharacterNameHash; - ImGui.InputText("##onlineUserHash" + onlineUser.CharacterNameHash, ref charNameHash, 255, - ImGuiInputTextFlags.ReadOnly); - ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.Copy.ToIconString() + "##onlineUserCopyHash" + - onlineUser.CharacterNameHash)) - { - ImGui.SetClipboardText(onlineUser.UID); - } - - ImGui.PopFont(); - - ImGui.TableNextColumn(); - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.SkullCrossbones.ToIconString() + "##onlineUserBan" + - onlineUser.CharacterNameHash)) - { - _ = _apiController.AddOrUpdateBannedUserEntry(new BannedUserDto - { - CharacterHash = onlineUser.CharacterNameHash, - Reason = "Banned by " + _uiShared.PlayerName - }); - } - ImGui.SameLine(); - if (onlineUser.UID != _apiController.UID && _apiController.IsAdmin) - { - if (!onlineUser.IsModerator) - { - if (ImGui.Button(FontAwesomeIcon.ChessKing.ToIconString() + - "##onlineUserModerator" + - onlineUser.CharacterNameHash)) - { - _apiController.PromoteToModerator(onlineUser.UID); - } - } - else - { - if (ImGui.Button(FontAwesomeIcon.User.ToIconString() + - "##onlineUserNonModerator" + - onlineUser.CharacterNameHash)) - { - _apiController.DemoteFromModerator(onlineUser.UID); - } - } - } - - ImGui.PopFont(); - } - ImGui.EndTable(); - } - ImGui.TreePop(); - } - } - - private bool _deleteFilesPopupModalShown = false; - private bool _deleteAccountPopupModalShown = false; - - private void DrawUserAdministration(bool serverAlive) - { - if (serverAlive) - { - if (ImGui.Button("Delete all my files")) - { - _deleteFilesPopupModalShown = true; - ImGui.OpenPopup("Delete all your files?"); - } - - UiShared.DrawHelpText("Completely deletes all your uploaded files on the service."); - - if (ImGui.BeginPopupModal("Delete all your files?", ref _deleteFilesPopupModalShown, - ImGuiWindowFlags.AlwaysAutoResize)) - { - UiShared.TextWrapped( - "All your own uploaded files on the service will be deleted.\nThis operation cannot be undone."); - ImGui.Text("Are you sure you want to continue?"); - ImGui.Separator(); - if (ImGui.Button("Delete everything", new Vector2(150, 0))) - { - Task.Run(() => _apiController.DeleteAllMyFiles()); - ImGui.CloseCurrentPopup(); - _deleteFilesPopupModalShown = false; - } - - ImGui.SameLine(); - - if (ImGui.Button("Cancel##cancelDelete", new Vector2(150, 0))) - { - ImGui.CloseCurrentPopup(); - _deleteFilesPopupModalShown = false; - } - - ImGui.EndPopup(); - } - - if (ImGui.Button("Delete account")) - { - _deleteAccountPopupModalShown = true; - ImGui.OpenPopup("Delete your account?"); - } - - UiShared.DrawHelpText("Completely deletes your account and all uploaded files to the service."); - - if (ImGui.BeginPopupModal("Delete your account?", ref _deleteAccountPopupModalShown, - ImGuiWindowFlags.AlwaysAutoResize)) - { - UiShared.TextWrapped( - "Your account and all associated files and data on the service will be deleted."); - UiShared.TextWrapped("Your UID will be removed from all pairing lists."); - ImGui.Text("Are you sure you want to continue?"); - ImGui.Separator(); - if (ImGui.Button("Delete account", new Vector2(150, 0))) - { - Task.Run(() => _apiController.DeleteAccount()); - ImGui.CloseCurrentPopup(); - _deleteAccountPopupModalShown = false; - SwitchToIntroUi?.Invoke(); - } - - ImGui.SameLine(); - - if (ImGui.Button("Cancel##cancelDelete", new Vector2(150, 0))) - { - ImGui.CloseCurrentPopup(); - _deleteAccountPopupModalShown = false; - } - - ImGui.EndPopup(); - } - } - - if (!_configuration.FullPause) - { - UiShared.ColorTextWrapped("Note: to change servers you need to disconnect from your current Mare Synchronos server.", ImGuiColors.DalamudYellow); - } - - var marePaused = _configuration.FullPause; - - if (_configuration.HasValidSetup()) - { - if (ImGui.Checkbox("Disconnect Mare Synchronos", ref marePaused)) - { - _configuration.FullPause = marePaused; - _configuration.Save(); - Task.Run(_apiController.CreateConnections); - } - - UiShared.DrawHelpText("Completely pauses the sync and clears your current data (not uploaded files) on the service."); - } - else - { - UiShared.ColorText("You cannot reconnect without a valid account on the service.", ImGuiColors.DalamudYellow); - } - - if (marePaused) - { - _uiShared.DrawServiceSelection(() => { }); - } - } - - private void DrawBlockedTransfers() - { - UiShared.ColorTextWrapped("Files that you attempted to upload or download that were forbidden to be transferred by their creators will appear here. " + - "If you see file paths from your drive here, then those files were not allowed to be uploaded. If you see hashes, those files were not allowed to be downloaded. " + - "Ask your paired friend to send you the mod in question through other means, acquire the mod yourself or pester the mod creator to allow it to be sent over Mare.", - ImGuiColors.DalamudGrey); - - if (ImGui.BeginTable("TransfersTable", 2, ImGuiTableFlags.SizingStretchProp)) - { - ImGui.TableSetupColumn( - $"Hash/Filename"); - ImGui.TableSetupColumn($"Forbidden by"); - - ImGui.TableHeadersRow(); - - foreach (var item in _apiController.ForbiddenTransfers) - { - ImGui.TableNextColumn(); - if (item is UploadFileTransfer transfer) - { - ImGui.Text(transfer.LocalFile); - } - else - { - ImGui.Text(item.Hash); - } - ImGui.TableNextColumn(); - ImGui.Text(item.ForbiddenBy); - } - ImGui.EndTable(); - } - } - - private void DrawCurrentTransfers() - { - bool showTransferWindow = _configuration.ShowTransferWindow; - if (ImGui.Checkbox("Show separate Transfer window while transfers are active", ref showTransferWindow)) - { - _configuration.ShowTransferWindow = showTransferWindow; - _configuration.Save(); - } - - if (_configuration.ShowTransferWindow) - { - ImGui.Indent(); - bool editTransferWindowPosition = _uiShared.EditTrackerPosition; - if (ImGui.Checkbox("Edit Transfer Window position", ref editTransferWindowPosition)) - { - _uiShared.EditTrackerPosition = editTransferWindowPosition; - } - ImGui.Unindent(); - } - - if (ImGui.BeginTable("TransfersTable", 2)) - { - ImGui.TableSetupColumn( - $"Uploads ({UiShared.ByteToString(_apiController.CurrentUploads.Sum(a => a.Transferred))} / {UiShared.ByteToString(_apiController.CurrentUploads.Sum(a => a.Total))})"); - ImGui.TableSetupColumn($"Downloads ({UiShared.ByteToString(_apiController.CurrentDownloads.SelectMany(k => k.Value).ToList().Sum(a => a.Transferred))} / {UiShared.ByteToString(_apiController.CurrentDownloads.SelectMany(k => k.Value).ToList().Sum(a => a.Total))})"); - - ImGui.TableHeadersRow(); - - ImGui.TableNextColumn(); - if (ImGui.BeginTable("UploadsTable", 3)) - { - ImGui.TableSetupColumn("File"); - ImGui.TableSetupColumn("Uploaded"); - ImGui.TableSetupColumn("Size"); - ImGui.TableHeadersRow(); - foreach (var transfer in _apiController.CurrentUploads.ToArray()) - { - var color = UiShared.UploadColor((transfer.Transferred, transfer.Total)); - ImGui.PushStyleColor(ImGuiCol.Text, color); - ImGui.TableNextColumn(); - ImGui.Text(transfer.Hash); - ImGui.TableNextColumn(); - ImGui.Text(UiShared.ByteToString(transfer.Transferred)); - ImGui.TableNextColumn(); - ImGui.Text(UiShared.ByteToString(transfer.Total)); - ImGui.PopStyleColor(); - ImGui.TableNextRow(); - } - - ImGui.EndTable(); - } - - ImGui.TableNextColumn(); - if (ImGui.BeginTable("DownloadsTable", 3)) - { - ImGui.TableSetupColumn("File"); - ImGui.TableSetupColumn("Downloaded"); - ImGui.TableSetupColumn("Size"); - ImGui.TableHeadersRow(); - foreach (var transfer in _apiController.CurrentDownloads.SelectMany(k => k.Value).ToArray()) - { - var color = UiShared.UploadColor((transfer.Transferred, transfer.Total)); - ImGui.PushStyleColor(ImGuiCol.Text, color); - ImGui.TableNextColumn(); - ImGui.Text(transfer.Hash); - ImGui.TableNextColumn(); - ImGui.Text(UiShared.ByteToString(transfer.Transferred)); - ImGui.TableNextColumn(); - ImGui.Text(UiShared.ByteToString(transfer.Total)); - ImGui.PopStyleColor(); - ImGui.TableNextRow(); - } - - ImGui.EndTable(); + ImGui.NextColumn(); } ImGui.EndTable(); } + + ImGui.TreePop(); } - private void DrawFileCacheSettings() + if (ImGui.TreeNode("Banned Users")) { - _uiShared.DrawFileScanState(); - _uiShared.DrawTimeSpanBetweenScansSetting(); - _uiShared.DrawCacheDirectorySetting(); - ImGui.Text($"Local cache size: {UiShared.ByteToString(_uiShared.FileCacheSize)}"); - ImGui.SameLine(); - if (ImGui.Button("Clear local cache")) + if (ImGui.BeginTable("BannedUsersTable", 3, ImGuiTableFlags.RowBg)) { - Task.Run(() => + ImGui.TableSetupColumn("Character Hash", ImGuiTableColumnFlags.None, 290); + ImGui.TableSetupColumn("Reason", ImGuiTableColumnFlags.None, 290); + ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 70); + + ImGui.TableHeadersRow(); + + foreach (var bannedUser in _apiController.AdminBannedUsers) { - foreach (var file in Directory.GetFiles(_configuration.CacheFolder)) + ImGui.TableNextColumn(); + ImGui.Text(bannedUser.CharacterHash); + + ImGui.TableNextColumn(); + string reason = bannedUser.Reason; + ImGuiInputTextFlags moderatorFlags = _apiController.IsModerator + ? ImGuiInputTextFlags.ReadOnly + : ImGuiInputTextFlags.None; + if (ImGui.InputText("##bannedReason" + bannedUser.CharacterHash, ref reason, 255, + moderatorFlags)) { - File.Delete(file); + bannedUser.Reason = reason; } - _uiShared.RecalculateFileCacheSize(); - }); + ImGui.TableNextColumn(); + ImGui.PushFont(UiBuilder.IconFont); + if (_apiController.IsAdmin) + { + if (ImGui.Button(FontAwesomeIcon.Upload.ToIconString() + "##updateUser" + + bannedUser.CharacterHash)) + { + _ = _apiController.AddOrUpdateBannedUserEntry(bannedUser); + } + + ImGui.SameLine(); + } + + if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString() + "##deleteUser" + + bannedUser.CharacterHash)) + { + _ = _apiController.DeleteBannedUserEntry(bannedUser); + } + + ImGui.PopFont(); + } + + ImGui.TableNextColumn(); + ImGui.InputText("##addUserHash", ref _bannedUserHashEntry, 255); + + ImGui.TableNextColumn(); + if (_apiController.IsAdmin) + { + ImGui.InputText("##addUserReason", ref _bannedUserReasonEntry, 255); + } + else + { + _bannedUserReasonEntry = "Banned by " + _uiShared.PlayerName; + ImGui.InputText("##addUserReason", ref _bannedUserReasonEntry, 255, + ImGuiInputTextFlags.ReadOnly); + } + + ImGui.TableNextColumn(); + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString() + "##addForbiddenFile")) + { + _ = _apiController.AddOrUpdateBannedUserEntry(new BannedUserDto() + { + CharacterHash = _forbiddenFileHashForbiddenBy, + Reason = _forbiddenFileHashEntry + }); + } + + ImGui.PopFont(); + + ImGui.EndTable(); } + + ImGui.TreePop(); } - public override void OnClose() + if (ImGui.TreeNode("Online Users")) { - _uiShared.EditTrackerPosition = false; - base.OnClose(); + if (ImGui.Button("Refresh Online Users")) + { + _ = _apiController.RefreshOnlineUsers(); + } + + if (ImGui.BeginTable("OnlineUsersTable", 3, ImGuiTableFlags.RowBg)) + { + ImGui.TableSetupColumn("UID", ImGuiTableColumnFlags.None, 100); + ImGui.TableSetupColumn("Character Hash", ImGuiTableColumnFlags.None, 300); + ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 70); + + ImGui.TableHeadersRow(); + + foreach (var onlineUser in _apiController.AdminOnlineUsers) + { + ImGui.TableNextColumn(); + ImGui.PushFont(UiBuilder.IconFont); + string icon = onlineUser.IsModerator + ? FontAwesomeIcon.ChessKing.ToIconString() + : onlineUser.IsAdmin + ? FontAwesomeIcon.Crown.ToIconString() + : FontAwesomeIcon.User.ToIconString(); + ImGui.Text(icon); + ImGui.PopFont(); + ImGui.SameLine(); + + ImGui.Text(onlineUser.UID); + ImGui.SameLine(); + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.Copy.ToIconString() + "##onlineUserCopyUID" + + onlineUser.CharacterNameHash)) + { + ImGui.SetClipboardText(onlineUser.UID); + } + + ImGui.PopFont(); + + ImGui.TableNextColumn(); + string charNameHash = onlineUser.CharacterNameHash; + ImGui.InputText("##onlineUserHash" + onlineUser.CharacterNameHash, ref charNameHash, 255, + ImGuiInputTextFlags.ReadOnly); + ImGui.SameLine(); + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.Copy.ToIconString() + "##onlineUserCopyHash" + + onlineUser.CharacterNameHash)) + { + ImGui.SetClipboardText(onlineUser.UID); + } + + ImGui.PopFont(); + + ImGui.TableNextColumn(); + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.SkullCrossbones.ToIconString() + "##onlineUserBan" + + onlineUser.CharacterNameHash)) + { + _ = _apiController.AddOrUpdateBannedUserEntry(new BannedUserDto + { + CharacterHash = onlineUser.CharacterNameHash, + Reason = "Banned by " + _uiShared.PlayerName + }); + } + ImGui.SameLine(); + if (onlineUser.UID != _apiController.UID && _apiController.IsAdmin) + { + if (!onlineUser.IsModerator) + { + if (ImGui.Button(FontAwesomeIcon.ChessKing.ToIconString() + + "##onlineUserModerator" + + onlineUser.CharacterNameHash)) + { + _apiController.PromoteToModerator(onlineUser.UID); + } + } + else + { + if (ImGui.Button(FontAwesomeIcon.User.ToIconString() + + "##onlineUserNonModerator" + + onlineUser.CharacterNameHash)) + { + _apiController.DemoteFromModerator(onlineUser.UID); + } + } + } + + ImGui.PopFont(); + } + ImGui.EndTable(); + } + ImGui.TreePop(); } } + + private bool _deleteFilesPopupModalShown = false; + private bool _deleteAccountPopupModalShown = false; + + private void DrawUserAdministration(bool serverAlive) + { + if (serverAlive) + { + if (ImGui.Button("Delete all my files")) + { + _deleteFilesPopupModalShown = true; + ImGui.OpenPopup("Delete all your files?"); + } + + UiShared.DrawHelpText("Completely deletes all your uploaded files on the service."); + + if (ImGui.BeginPopupModal("Delete all your files?", ref _deleteFilesPopupModalShown, + ImGuiWindowFlags.AlwaysAutoResize)) + { + UiShared.TextWrapped( + "All your own uploaded files on the service will be deleted.\nThis operation cannot be undone."); + ImGui.Text("Are you sure you want to continue?"); + ImGui.Separator(); + if (ImGui.Button("Delete everything", new Vector2(150, 0))) + { + Task.Run(() => _apiController.DeleteAllMyFiles()); + ImGui.CloseCurrentPopup(); + _deleteFilesPopupModalShown = false; + } + + ImGui.SameLine(); + + if (ImGui.Button("Cancel##cancelDelete", new Vector2(150, 0))) + { + ImGui.CloseCurrentPopup(); + _deleteFilesPopupModalShown = false; + } + + ImGui.EndPopup(); + } + + if (ImGui.Button("Delete account")) + { + _deleteAccountPopupModalShown = true; + ImGui.OpenPopup("Delete your account?"); + } + + UiShared.DrawHelpText("Completely deletes your account and all uploaded files to the service."); + + if (ImGui.BeginPopupModal("Delete your account?", ref _deleteAccountPopupModalShown, + ImGuiWindowFlags.AlwaysAutoResize)) + { + UiShared.TextWrapped( + "Your account and all associated files and data on the service will be deleted."); + UiShared.TextWrapped("Your UID will be removed from all pairing lists."); + ImGui.Text("Are you sure you want to continue?"); + ImGui.Separator(); + if (ImGui.Button("Delete account", new Vector2(150, 0))) + { + Task.Run(() => _apiController.DeleteAccount()); + ImGui.CloseCurrentPopup(); + _deleteAccountPopupModalShown = false; + SwitchToIntroUi?.Invoke(); + } + + ImGui.SameLine(); + + if (ImGui.Button("Cancel##cancelDelete", new Vector2(150, 0))) + { + ImGui.CloseCurrentPopup(); + _deleteAccountPopupModalShown = false; + } + + ImGui.EndPopup(); + } + } + + if (!_configuration.FullPause) + { + UiShared.ColorTextWrapped("Note: to change servers you need to disconnect from your current Mare Synchronos server.", ImGuiColors.DalamudYellow); + } + + var marePaused = _configuration.FullPause; + + if (_configuration.HasValidSetup()) + { + if (ImGui.Checkbox("Disconnect Mare Synchronos", ref marePaused)) + { + _configuration.FullPause = marePaused; + _configuration.Save(); + Task.Run(_apiController.CreateConnections); + } + + UiShared.DrawHelpText("Completely pauses the sync and clears your current data (not uploaded files) on the service."); + } + else + { + UiShared.ColorText("You cannot reconnect without a valid account on the service.", ImGuiColors.DalamudYellow); + } + + if (marePaused) + { + _uiShared.DrawServiceSelection(() => { }); + } + } + + private void DrawBlockedTransfers() + { + UiShared.ColorTextWrapped("Files that you attempted to upload or download that were forbidden to be transferred by their creators will appear here. " + + "If you see file paths from your drive here, then those files were not allowed to be uploaded. If you see hashes, those files were not allowed to be downloaded. " + + "Ask your paired friend to send you the mod in question through other means, acquire the mod yourself or pester the mod creator to allow it to be sent over Mare.", + ImGuiColors.DalamudGrey); + + if (ImGui.BeginTable("TransfersTable", 2, ImGuiTableFlags.SizingStretchProp)) + { + ImGui.TableSetupColumn( + $"Hash/Filename"); + ImGui.TableSetupColumn($"Forbidden by"); + + ImGui.TableHeadersRow(); + + foreach (var item in _apiController.ForbiddenTransfers) + { + ImGui.TableNextColumn(); + if (item is UploadFileTransfer transfer) + { + ImGui.Text(transfer.LocalFile); + } + else + { + ImGui.Text(item.Hash); + } + ImGui.TableNextColumn(); + ImGui.Text(item.ForbiddenBy); + } + ImGui.EndTable(); + } + } + + private void DrawCurrentTransfers() + { + bool showTransferWindow = _configuration.ShowTransferWindow; + if (ImGui.Checkbox("Show separate Transfer window while transfers are active", ref showTransferWindow)) + { + _configuration.ShowTransferWindow = showTransferWindow; + _configuration.Save(); + } + + if (_configuration.ShowTransferWindow) + { + ImGui.Indent(); + bool editTransferWindowPosition = _uiShared.EditTrackerPosition; + if (ImGui.Checkbox("Edit Transfer Window position", ref editTransferWindowPosition)) + { + _uiShared.EditTrackerPosition = editTransferWindowPosition; + } + ImGui.Unindent(); + } + + if (ImGui.BeginTable("TransfersTable", 2)) + { + ImGui.TableSetupColumn( + $"Uploads ({UiShared.ByteToString(_apiController.CurrentUploads.Sum(a => a.Transferred))} / {UiShared.ByteToString(_apiController.CurrentUploads.Sum(a => a.Total))})"); + ImGui.TableSetupColumn($"Downloads ({UiShared.ByteToString(_apiController.CurrentDownloads.SelectMany(k => k.Value).ToList().Sum(a => a.Transferred))} / {UiShared.ByteToString(_apiController.CurrentDownloads.SelectMany(k => k.Value).ToList().Sum(a => a.Total))})"); + + ImGui.TableHeadersRow(); + + ImGui.TableNextColumn(); + if (ImGui.BeginTable("UploadsTable", 3)) + { + ImGui.TableSetupColumn("File"); + ImGui.TableSetupColumn("Uploaded"); + ImGui.TableSetupColumn("Size"); + ImGui.TableHeadersRow(); + foreach (var transfer in _apiController.CurrentUploads.ToArray()) + { + var color = UiShared.UploadColor((transfer.Transferred, transfer.Total)); + ImGui.PushStyleColor(ImGuiCol.Text, color); + ImGui.TableNextColumn(); + ImGui.Text(transfer.Hash); + ImGui.TableNextColumn(); + ImGui.Text(UiShared.ByteToString(transfer.Transferred)); + ImGui.TableNextColumn(); + ImGui.Text(UiShared.ByteToString(transfer.Total)); + ImGui.PopStyleColor(); + ImGui.TableNextRow(); + } + + ImGui.EndTable(); + } + + ImGui.TableNextColumn(); + if (ImGui.BeginTable("DownloadsTable", 3)) + { + ImGui.TableSetupColumn("File"); + ImGui.TableSetupColumn("Downloaded"); + ImGui.TableSetupColumn("Size"); + ImGui.TableHeadersRow(); + foreach (var transfer in _apiController.CurrentDownloads.SelectMany(k => k.Value).ToArray()) + { + var color = UiShared.UploadColor((transfer.Transferred, transfer.Total)); + ImGui.PushStyleColor(ImGuiCol.Text, color); + ImGui.TableNextColumn(); + ImGui.Text(transfer.Hash); + ImGui.TableNextColumn(); + ImGui.Text(UiShared.ByteToString(transfer.Transferred)); + ImGui.TableNextColumn(); + ImGui.Text(UiShared.ByteToString(transfer.Total)); + ImGui.PopStyleColor(); + ImGui.TableNextRow(); + } + + ImGui.EndTable(); + } + + ImGui.EndTable(); + } + } + + private void DrawFileCacheSettings() + { + _uiShared.DrawFileScanState(); + _uiShared.DrawTimeSpanBetweenScansSetting(); + _uiShared.DrawCacheDirectorySetting(); + ImGui.Text($"Local cache size: {UiShared.ByteToString(_uiShared.FileCacheSize)}"); + ImGui.SameLine(); + if (ImGui.Button("Clear local cache")) + { + Task.Run(() => + { + foreach (var file in Directory.GetFiles(_configuration.CacheFolder)) + { + File.Delete(file); + } + + _uiShared.RecalculateFileCacheSize(); + }); + } + } + + public override void OnClose() + { + _uiShared.EditTrackerPosition = false; + base.OnClose(); + } } diff --git a/MareSynchronos/UI/UIShared.cs b/MareSynchronos/UI/UIShared.cs index 3164223..7534b34 100644 --- a/MareSynchronos/UI/UIShared.cs +++ b/MareSynchronos/UI/UIShared.cs @@ -18,539 +18,538 @@ using MareSynchronos.Managers; using MareSynchronos.Utils; using MareSynchronos.WebAPI; -namespace MareSynchronos.UI +namespace MareSynchronos.UI; + +public class UiShared : IDisposable { - public class UiShared : IDisposable + [DllImport("user32")] + public static extern short GetKeyState(int nVirtKey); + + private readonly IpcManager _ipcManager; + private readonly ApiController _apiController; + private readonly PeriodicFileScanner _cacheScanner; + private readonly FileDialogManager _fileDialogManager; + private readonly Configuration _pluginConfiguration; + private readonly DalamudUtil _dalamudUtil; + private readonly DalamudPluginInterface _pluginInterface; + private readonly Dalamud.Localization _localization; + public long FileCacheSize => _cacheScanner.FileCacheSize; + public string PlayerName => _dalamudUtil.PlayerName; + public bool HasValidPenumbraModPath => !(_ipcManager.PenumbraModDirectory() ?? string.Empty).IsNullOrEmpty() && Directory.Exists(_ipcManager.PenumbraModDirectory()); + public bool EditTrackerPosition { get; set; } + public ImFontPtr UidFont { get; private set; } + public bool UidFontBuilt { get; private set; } + + public static bool CtrlPressed() => (GetKeyState(0xA2) & 0x8000) != 0 || (GetKeyState(0xA3) & 0x8000) != 0; + + public ApiController ApiController => _apiController; + + public UiShared(IpcManager ipcManager, ApiController apiController, PeriodicFileScanner cacheScanner, FileDialogManager fileDialogManager, + Configuration pluginConfiguration, DalamudUtil dalamudUtil, DalamudPluginInterface pluginInterface, Dalamud.Localization localization) { - [DllImport("user32")] - public static extern short GetKeyState(int nVirtKey); + _ipcManager = ipcManager; + _apiController = apiController; + _cacheScanner = cacheScanner; + _fileDialogManager = fileDialogManager; + _pluginConfiguration = pluginConfiguration; + _dalamudUtil = dalamudUtil; + _pluginInterface = pluginInterface; + _localization = localization; + _isDirectoryWritable = IsDirectoryWritable(_pluginConfiguration.CacheFolder); - private readonly IpcManager _ipcManager; - private readonly ApiController _apiController; - private readonly PeriodicFileScanner _cacheScanner; - private readonly FileDialogManager _fileDialogManager; - private readonly Configuration _pluginConfiguration; - private readonly DalamudUtil _dalamudUtil; - private readonly DalamudPluginInterface _pluginInterface; - private readonly Dalamud.Localization _localization; - public long FileCacheSize => _cacheScanner.FileCacheSize; - public string PlayerName => _dalamudUtil.PlayerName; - public bool HasValidPenumbraModPath => !(_ipcManager.PenumbraModDirectory() ?? string.Empty).IsNullOrEmpty() && Directory.Exists(_ipcManager.PenumbraModDirectory()); - public bool EditTrackerPosition { get; set; } - public ImFontPtr UidFont { get; private set; } - public bool UidFontBuilt { get; private set; } + _pluginInterface.UiBuilder.BuildFonts += BuildFont; + _pluginInterface.UiBuilder.RebuildFonts(); + } - public static bool CtrlPressed() => (GetKeyState(0xA2) & 0x8000) != 0 || (GetKeyState(0xA3) & 0x8000) != 0; + public static float GetWindowContentRegionWidth() + { + return ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; + } - public ApiController ApiController => _apiController; + public static Vector2 GetIconButtonSize(FontAwesomeIcon icon) + { + ImGui.PushFont(UiBuilder.IconFont); + var buttonSize = ImGuiHelpers.GetButtonSize(icon.ToIconString()); + ImGui.PopFont(); + return buttonSize; + } - public UiShared(IpcManager ipcManager, ApiController apiController, PeriodicFileScanner cacheScanner, FileDialogManager fileDialogManager, - Configuration pluginConfiguration, DalamudUtil dalamudUtil, DalamudPluginInterface pluginInterface, Dalamud.Localization localization) - { - _ipcManager = ipcManager; - _apiController = apiController; - _cacheScanner = cacheScanner; - _fileDialogManager = fileDialogManager; - _pluginConfiguration = pluginConfiguration; - _dalamudUtil = dalamudUtil; - _pluginInterface = pluginInterface; - _localization = localization; - _isDirectoryWritable = IsDirectoryWritable(_pluginConfiguration.CacheFolder); + private void BuildFont() + { + var fontFile = Path.Combine(_pluginInterface.DalamudAssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Medium.otf"); + UidFontBuilt = false; - _pluginInterface.UiBuilder.BuildFonts += BuildFont; - _pluginInterface.UiBuilder.RebuildFonts(); - } - - public static float GetWindowContentRegionWidth() - { - return ImGui.GetWindowContentRegionMax().X - ImGui.GetWindowContentRegionMin().X; - } - - public static Vector2 GetIconButtonSize(FontAwesomeIcon icon) - { - ImGui.PushFont(UiBuilder.IconFont); - var buttonSize = ImGuiHelpers.GetButtonSize(icon.ToIconString()); - ImGui.PopFont(); - return buttonSize; - } - - private void BuildFont() - { - var fontFile = Path.Combine(_pluginInterface.DalamudAssetDirectory.FullName, "UIRes", "NotoSansCJKjp-Medium.otf"); - UidFontBuilt = false; - - if (File.Exists(fontFile)) - { - try - { - UidFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontFile, 35); - UidFontBuilt = true; - } - catch (Exception ex) - { - Logger.Debug($"Font failed to load. {fontFile}"); - Logger.Debug(ex.ToString()); - } - } - else - { - Logger.Debug($"Font doesn't exist. {fontFile}"); - } - } - - public static void DrawWithID(string id, Action drawSubSection) - { - ImGui.PushID(id); - drawSubSection.Invoke(); - ImGui.PopID(); - } - - public static void AttachToolTip(string text) - { - if (ImGui.IsItemHovered()) - { - ImGui.SetTooltip(text); - } - } - - public bool DrawOtherPluginState() - { - var penumbraExists = _ipcManager.CheckPenumbraApi(); - var glamourerExists = _ipcManager.CheckGlamourerApi(); - var heelsExists = _ipcManager.CheckHeelsApi(); - - var penumbraColor = penumbraExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - var glamourerColor = glamourerExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - var heelsColor = heelsExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - ImGui.Text("Penumbra:"); - ImGui.SameLine(); - ImGui.TextColored(penumbraColor, penumbraExists ? "Available" : "Unavailable"); - ImGui.SameLine(); - ImGui.Text("Glamourer:"); - ImGui.SameLine(); - ImGui.TextColored(glamourerColor, glamourerExists ? "Available" : "Unavailable"); - ImGui.Text("Optional Addons"); - ImGui.SameLine(); - ImGui.Text("Heels:"); - ImGui.SameLine(); - ImGui.TextColored(heelsColor, heelsExists ? "Available" : "Unavailable"); - - if (!penumbraExists || !glamourerExists) - { - ImGui.TextColored(ImGuiColors.DalamudRed, "You need to install both Penumbra and Glamourer and keep them up to date to use Mare Synchronos."); - return false; - } - - return true; - } - - public void DrawFileScanState() - { - ImGui.Text("File Scanner Status"); - ImGui.SameLine(); - if (_cacheScanner.IsScanRunning) - { - ImGui.Text("Scan is running"); - ImGui.Text("Current Progress:"); - ImGui.SameLine(); - ImGui.Text(_cacheScanner.TotalFiles == 1 - ? "Collecting files" - : $"Processing {_cacheScanner.CurrentFileProgress} / {_cacheScanner.TotalFiles} files"); - } - else if (_pluginConfiguration.FileScanPaused) - { - ImGui.Text("File scanner is paused"); - ImGui.SameLine(); - if (ImGui.Button("Force Rescan##forcedrescan")) - { - _cacheScanner.InvokeScan(true); - } - } - else if (_cacheScanner.haltScanLocks.Any(f => f.Value > 0)) - { - ImGui.Text("Halted (" + string.Join(", ", _cacheScanner.haltScanLocks.Where(f => f.Value > 0).Select(locker => locker.Key + ": " + locker.Value + " halt requests")) + ")"); - ImGui.SameLine(); - if (ImGui.Button("Reset halt requests##clearlocks")) - { - _cacheScanner.ResetLocks(); - } - } - else - { - ImGui.Text("Next scan in " + _cacheScanner.TimeUntilNextScan); - } - } - - public void PrintServerState() - { - var serverName = _apiController.ServerDictionary.ContainsKey(_pluginConfiguration.ApiUri) - ? _apiController.ServerDictionary[_pluginConfiguration.ApiUri] - : _pluginConfiguration.ApiUri; - if (_apiController.ServerState is ServerState.Connected) - { - ImGui.TextUnformatted("Service " + serverName + ":"); - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.ParsedGreen, "Available"); - ImGui.SameLine(); - ImGui.TextUnformatted("("); - ImGui.SameLine(); - ImGui.TextColored(ImGuiColors.ParsedGreen, _apiController.OnlineUsers.ToString()); - ImGui.SameLine(); - ImGui.Text("Users Online"); - ImGui.SameLine(); - ImGui.Text(")"); - } - } - - public static void ColorText(string text, Vector4 color) - { - ImGui.PushStyleColor(ImGuiCol.Text, color); - ImGui.TextUnformatted(text); - ImGui.PopStyleColor(); - } - - public static void ColorTextWrapped(string text, Vector4 color) - { - ImGui.PushStyleColor(ImGuiCol.Text, color); - TextWrapped(text); - ImGui.PopStyleColor(); - } - - public static void TextWrapped(string text) - { - ImGui.PushTextWrapPos(0); - ImGui.TextUnformatted(text); - ImGui.PopTextWrapPos(); - } - - public static Vector4 GetCpuLoadColor(double input) => input < 50 ? ImGuiColors.ParsedGreen : - input < 90 ? ImGuiColors.DalamudYellow : ImGuiColors.DalamudRed; - - public static Vector4 GetBoolColor(bool input) => input ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; - - public static Vector4 UploadColor((long, long) data) => data.Item1 == 0 ? ImGuiColors.DalamudGrey : - data.Item1 == data.Item2 ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudYellow; - - public void LoadLocalization(string languageCode) - { - _localization.SetupWithLangCode(languageCode); - Strings.ToS = new Strings.ToSStrings(); - } - - public static uint Color(byte r, byte g, byte b, byte a) - { uint ret = a; ret <<= 8; ret += b; ret <<= 8; ret += g; ret <<= 8; ret += r; return ret; } - - public static void DrawOutlinedFont(ImDrawListPtr drawList, string text, Vector2 textPos, uint fontColor, uint outlineColor, int thickness) - { - drawList.AddText(textPos with { Y = textPos.Y - thickness }, - outlineColor, text); - drawList.AddText(textPos with { X = textPos.X - thickness }, - outlineColor, text); - drawList.AddText(textPos with { Y = textPos.Y + thickness }, - outlineColor, text); - drawList.AddText(textPos with { X = textPos.X + thickness }, - outlineColor, text); - drawList.AddText(new Vector2(textPos.X - thickness, textPos.Y - thickness), - outlineColor, text); - drawList.AddText(new Vector2(textPos.X + thickness, textPos.Y + thickness), - outlineColor, text); - drawList.AddText(new Vector2(textPos.X - thickness, textPos.Y + thickness), - outlineColor, text); - drawList.AddText(new Vector2(textPos.X + thickness, textPos.Y - thickness), - outlineColor, text); - - drawList.AddText(textPos, fontColor, text); - drawList.AddText(textPos, fontColor, text); - } - - public static string ByteToString(long bytes) - { - string[] suffix = { "B", "KiB", "MiB", "GiB", "TiB" }; - int i; - double dblSByte = bytes; - for (i = 0; i < suffix.Length && bytes >= 1024; i++, bytes /= 1024) - { - dblSByte = bytes / 1024.0; - } - - return $"{dblSByte:0.00} {suffix[i]}"; - } - - private int _serverSelectionIndex = 0; - private string _customServerName = ""; - private string _customServerUri = ""; - private bool _enterSecretKey = false; - private bool _cacheDirectoryHasOtherFilesThanCache = false; - private bool _cacheDirectoryIsValidPath = true; - - public void DrawServiceSelection(Action? callBackOnExit = null) - { - string[] comboEntries = _apiController.ServerDictionary.Values.ToArray(); - _serverSelectionIndex = Array.IndexOf(_apiController.ServerDictionary.Keys.ToArray(), _pluginConfiguration.ApiUri); - if (ImGui.BeginCombo("Select Service", comboEntries[_serverSelectionIndex])) - { - for (int i = 0; i < comboEntries.Length; i++) - { - bool isSelected = _serverSelectionIndex == i; - if (ImGui.Selectable(comboEntries[i], isSelected)) - { - _pluginConfiguration.ApiUri = _apiController.ServerDictionary.Single(k => k.Value == comboEntries[i]).Key; - _pluginConfiguration.Save(); - _ = _apiController.CreateConnections(); - } - - if (isSelected) - { - ImGui.SetItemDefaultFocus(); - } - } - - ImGui.EndCombo(); - } - - if (_serverSelectionIndex != 0) - { - ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString() + "##deleteService")) - { - _pluginConfiguration.CustomServerList.Remove(_pluginConfiguration.ApiUri); - _pluginConfiguration.ApiUri = _apiController.ServerDictionary.First().Key; - _pluginConfiguration.Save(); - } - ImGui.PopFont(); - } - - if (ImGui.TreeNode("Add Custom Service")) - { - ImGui.SetNextItemWidth(250); - ImGui.InputText("Custom Service Name", ref _customServerName, 255); - ImGui.SetNextItemWidth(250); - ImGui.InputText("Custom Service Address", ref _customServerUri, 255); - if (ImGui.Button("Add Custom Service")) - { - if (!string.IsNullOrEmpty(_customServerUri) - && !string.IsNullOrEmpty(_customServerName) - && !_pluginConfiguration.CustomServerList.ContainsValue(_customServerName) - && !_pluginConfiguration.CustomServerList.ContainsKey(_customServerUri)) - { - _pluginConfiguration.CustomServerList[_customServerUri] = _customServerName; - _customServerUri = string.Empty; - _customServerName = string.Empty; - _pluginConfiguration.Save(); - } - } - ImGui.TreePop(); - } - - PrintServerState(); - - if (!_apiController.ServerAlive && (_pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri) && !_pluginConfiguration.ClientSecret[_pluginConfiguration.ApiUri].IsNullOrEmpty())) - { - ColorTextWrapped("You already have an account on this server.", ImGuiColors.DalamudYellow); - ImGui.SameLine(); - if (ImGui.Button("Connect##connectToService")) - { - _pluginConfiguration.FullPause = false; - _pluginConfiguration.Save(); - Task.Run(_apiController.CreateConnections); - } - } - - string checkboxText = _pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri) - ? "I want to switch accounts" - : "I have an account"; - ImGui.Checkbox(checkboxText, ref _enterSecretKey); - - if (_enterSecretKey) - { - ColorTextWrapped("This will overwrite your currently used secret key for the selected service. Make sure to have a backup for the current secret key if you want to switch back to this account.", ImGuiColors.DalamudYellow); - if (!_pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri)) - { - ColorTextWrapped("IF YOU HAVE NEVER MADE AN ACCOUNT BEFORE DO NOT ENTER ANYTHING HERE", ImGuiColors.DalamudYellow); - } - - var text = "Enter Secret Key"; - var buttonText = "Save"; - var buttonWidth = _secretKey.Length != 64 ? 0 : ImGuiHelpers.GetButtonSize(buttonText).X + ImGui.GetStyle().ItemSpacing.X; - var textSize = ImGui.CalcTextSize(text); - ImGui.AlignTextToFramePadding(); - ImGui.Text(text); - ImGui.SameLine(); - ImGui.SetNextItemWidth(GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonWidth - textSize.X); - ImGui.InputText("", ref _secretKey, 64); - if (_secretKey.Length > 0 && _secretKey.Length != 64) - { - ColorTextWrapped("Your secret key must be exactly 64 characters long. Don't enter your Lodestone auth here.", ImGuiColors.DalamudRed); - } - else if (_secretKey.Length == 64) - { - ImGui.SameLine(); - if (ImGui.Button(buttonText)) - { - _pluginConfiguration.ClientSecret[_pluginConfiguration.ApiUri] = _secretKey; - _pluginConfiguration.Save(); - _secretKey = string.Empty; - Task.Run(_apiController.CreateConnections); - _enterSecretKey = false; - callBackOnExit?.Invoke(); - } - } - } - } - - private string _secretKey = ""; - - public static void OutlineTextWrapped(string text, Vector4 textcolor, Vector4 outlineColor, float dist = 3) - { - var cursorPos = ImGui.GetCursorPos(); - UiShared.ColorTextWrapped(text, outlineColor); - ImGui.SetCursorPos(new(cursorPos.X, cursorPos.Y + dist)); - UiShared.ColorTextWrapped(text, outlineColor); - ImGui.SetCursorPos(new(cursorPos.X + dist, cursorPos.Y)); - UiShared.ColorTextWrapped(text, outlineColor); - ImGui.SetCursorPos(new(cursorPos.X + dist, cursorPos.Y + dist)); - UiShared.ColorTextWrapped(text, outlineColor); - - ImGui.SetCursorPos(new(cursorPos.X + dist / 2, cursorPos.Y + dist / 2)); - UiShared.ColorTextWrapped(text, textcolor); - ImGui.SetCursorPos(new(cursorPos.X + dist / 2, cursorPos.Y + dist / 2)); - UiShared.ColorTextWrapped(text, textcolor); - } - - public static void DrawHelpText(string helpText) - { - ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.SetWindowFontScale(0.8f); - ImGui.TextDisabled(FontAwesomeIcon.Question.ToIconString()); - ImGui.SetWindowFontScale(1.0f); - ImGui.PopFont(); - if (ImGui.IsItemHovered()) - { - ImGui.BeginTooltip(); - ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f); - ImGui.TextUnformatted(helpText); - ImGui.PopTextWrapPos(); - ImGui.EndTooltip(); - } - } - - public void DrawCacheDirectorySetting() - { - ColorTextWrapped("Note: The cache folder should be somewhere close to root (i.e. C:\\MareCache) in a new empty folder. DO NOT point this to your game folder. DO NOT point this to your Penumbra folder.", ImGuiColors.DalamudYellow); - var cacheDirectory = _pluginConfiguration.CacheFolder; - ImGui.InputText("Cache Folder##cache", ref cacheDirectory, 255, ImGuiInputTextFlags.ReadOnly); - - ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - string folderIcon = FontAwesomeIcon.Folder.ToIconString(); - if (ImGui.Button(folderIcon + "##chooseCacheFolder")) - { - _fileDialogManager.OpenFolderDialog("Pick Mare Synchronos Cache Folder", (success, path) => - { - if (!success) return; - - _isPenumbraDirectory = path.ToLowerInvariant() == _ipcManager.PenumbraModDirectory()?.ToLowerInvariant(); - _isDirectoryWritable = IsDirectoryWritable(path); - _cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => new FileInfo(f).Name.Length != 40); - _cacheDirectoryIsValidPath = Regex.IsMatch(path, @"^(?:[a-zA-Z]:\\[\w\s\-\\]+?|\/(?:[\w\s\-\/])+?)$", RegexOptions.ECMAScript); - - if (!string.IsNullOrEmpty(path) - && Directory.Exists(path) - && _isDirectoryWritable - && !_isPenumbraDirectory - && !_cacheDirectoryHasOtherFilesThanCache - && _cacheDirectoryIsValidPath) - { - _pluginConfiguration.CacheFolder = path; - _pluginConfiguration.Save(); - _cacheScanner.StartScan(); - } - }); - } - ImGui.PopFont(); - - if (_isPenumbraDirectory) - { - ColorTextWrapped("Do not point the cache path directly to the Penumbra directory. If necessary, make a subfolder in it.", ImGuiColors.DalamudRed); - } - else if (!_isDirectoryWritable) - { - ColorTextWrapped("The folder you selected does not exist or cannot be written to. Please provide a valid path.", ImGuiColors.DalamudRed); - } - else if (_cacheDirectoryHasOtherFilesThanCache) - { - ColorTextWrapped("Your selected directory has files inside that are not Mare related. Use an empty directory or a previous Mare cache directory only.", ImGuiColors.DalamudRed); - } - else if (!_cacheDirectoryIsValidPath) - { - ColorTextWrapped("Your selected directory contains illegal characters unreadable by FFXIV. " + - "Restrict yourself to latin letters (A-Z), underscores (_), dashes (-) and arabic numbers (0-9).", ImGuiColors.DalamudRed); - } - - int maxCacheSize = _pluginConfiguration.MaxLocalCacheInGiB; - if (ImGui.SliderInt("Maximum Cache Size in GB", ref maxCacheSize, 1, 50, "%d GB")) - { - _pluginConfiguration.MaxLocalCacheInGiB = maxCacheSize; - _pluginConfiguration.Save(); - } - DrawHelpText("The cache is automatically governed by Mare. It will clear itself automatically once it reaches the set capacity by removing the oldest unused files. You typically do not need to clear it yourself."); - } - - private bool _isDirectoryWritable = false; - private bool _isPenumbraDirectory = false; - - public bool IsDirectoryWritable(string dirPath, bool throwIfFails = false) + if (File.Exists(fontFile)) { try { - using (FileStream fs = File.Create( - Path.Combine( - dirPath, - Path.GetRandomFileName() - ), - 1, - FileOptions.DeleteOnClose) - ) - { } - return true; + UidFont = ImGui.GetIO().Fonts.AddFontFromFileTTF(fontFile, 35); + UidFontBuilt = true; } - catch + catch (Exception ex) { - if (throwIfFails) - throw; - else - return false; + Logger.Debug($"Font failed to load. {fontFile}"); + Logger.Debug(ex.ToString()); } } - - public void RecalculateFileCacheSize() + else { - _cacheScanner.InvokeScan(true); - } - - public void DrawTimeSpanBetweenScansSetting() - { - var timeSpan = _pluginConfiguration.TimeSpanBetweenScansInSeconds; - if (ImGui.SliderInt("Seconds between scans##timespan", ref timeSpan, 20, 60)) - { - _pluginConfiguration.TimeSpanBetweenScansInSeconds = timeSpan; - _pluginConfiguration.Save(); - } - DrawHelpText("This is the time in seconds between file scans. Increase it to reduce system load. A too high setting can cause issues when manually fumbling about in the cache or Penumbra mods folders."); - var isPaused = _pluginConfiguration.FileScanPaused; - if (ImGui.Checkbox("Pause periodic file scan##filescanpause", ref isPaused)) - { - _pluginConfiguration.FileScanPaused = isPaused; - _pluginConfiguration.Save(); - } - DrawHelpText("This allows you to stop the periodic scans of your Penumbra and Mare cache directories. Use this to move the Mare cache and Penumbra mod folders around. If you enable this permanently, run a Force rescan after adding mods to Penumbra."); - } - - public void Dispose() - { - _pluginInterface.UiBuilder.BuildFonts -= BuildFont; + Logger.Debug($"Font doesn't exist. {fontFile}"); } } + + public static void DrawWithID(string id, Action drawSubSection) + { + ImGui.PushID(id); + drawSubSection.Invoke(); + ImGui.PopID(); + } + + public static void AttachToolTip(string text) + { + if (ImGui.IsItemHovered()) + { + ImGui.SetTooltip(text); + } + } + + public bool DrawOtherPluginState() + { + var penumbraExists = _ipcManager.CheckPenumbraApi(); + var glamourerExists = _ipcManager.CheckGlamourerApi(); + var heelsExists = _ipcManager.CheckHeelsApi(); + + var penumbraColor = penumbraExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; + var glamourerColor = glamourerExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; + var heelsColor = heelsExists ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; + ImGui.Text("Penumbra:"); + ImGui.SameLine(); + ImGui.TextColored(penumbraColor, penumbraExists ? "Available" : "Unavailable"); + ImGui.SameLine(); + ImGui.Text("Glamourer:"); + ImGui.SameLine(); + ImGui.TextColored(glamourerColor, glamourerExists ? "Available" : "Unavailable"); + ImGui.Text("Optional Addons"); + ImGui.SameLine(); + ImGui.Text("Heels:"); + ImGui.SameLine(); + ImGui.TextColored(heelsColor, heelsExists ? "Available" : "Unavailable"); + + if (!penumbraExists || !glamourerExists) + { + ImGui.TextColored(ImGuiColors.DalamudRed, "You need to install both Penumbra and Glamourer and keep them up to date to use Mare Synchronos."); + return false; + } + + return true; + } + + public void DrawFileScanState() + { + ImGui.Text("File Scanner Status"); + ImGui.SameLine(); + if (_cacheScanner.IsScanRunning) + { + ImGui.Text("Scan is running"); + ImGui.Text("Current Progress:"); + ImGui.SameLine(); + ImGui.Text(_cacheScanner.TotalFiles == 1 + ? "Collecting files" + : $"Processing {_cacheScanner.CurrentFileProgress} / {_cacheScanner.TotalFiles} files"); + } + else if (_pluginConfiguration.FileScanPaused) + { + ImGui.Text("File scanner is paused"); + ImGui.SameLine(); + if (ImGui.Button("Force Rescan##forcedrescan")) + { + _cacheScanner.InvokeScan(true); + } + } + else if (_cacheScanner.haltScanLocks.Any(f => f.Value > 0)) + { + ImGui.Text("Halted (" + string.Join(", ", _cacheScanner.haltScanLocks.Where(f => f.Value > 0).Select(locker => locker.Key + ": " + locker.Value + " halt requests")) + ")"); + ImGui.SameLine(); + if (ImGui.Button("Reset halt requests##clearlocks")) + { + _cacheScanner.ResetLocks(); + } + } + else + { + ImGui.Text("Next scan in " + _cacheScanner.TimeUntilNextScan); + } + } + + public void PrintServerState() + { + var serverName = _apiController.ServerDictionary.ContainsKey(_pluginConfiguration.ApiUri) + ? _apiController.ServerDictionary[_pluginConfiguration.ApiUri] + : _pluginConfiguration.ApiUri; + if (_apiController.ServerState is ServerState.Connected) + { + ImGui.TextUnformatted("Service " + serverName + ":"); + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.ParsedGreen, "Available"); + ImGui.SameLine(); + ImGui.TextUnformatted("("); + ImGui.SameLine(); + ImGui.TextColored(ImGuiColors.ParsedGreen, _apiController.OnlineUsers.ToString()); + ImGui.SameLine(); + ImGui.Text("Users Online"); + ImGui.SameLine(); + ImGui.Text(")"); + } + } + + public static void ColorText(string text, Vector4 color) + { + ImGui.PushStyleColor(ImGuiCol.Text, color); + ImGui.TextUnformatted(text); + ImGui.PopStyleColor(); + } + + public static void ColorTextWrapped(string text, Vector4 color) + { + ImGui.PushStyleColor(ImGuiCol.Text, color); + TextWrapped(text); + ImGui.PopStyleColor(); + } + + public static void TextWrapped(string text) + { + ImGui.PushTextWrapPos(0); + ImGui.TextUnformatted(text); + ImGui.PopTextWrapPos(); + } + + public static Vector4 GetCpuLoadColor(double input) => input < 50 ? ImGuiColors.ParsedGreen : + input < 90 ? ImGuiColors.DalamudYellow : ImGuiColors.DalamudRed; + + public static Vector4 GetBoolColor(bool input) => input ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed; + + public static Vector4 UploadColor((long, long) data) => data.Item1 == 0 ? ImGuiColors.DalamudGrey : + data.Item1 == data.Item2 ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudYellow; + + public void LoadLocalization(string languageCode) + { + _localization.SetupWithLangCode(languageCode); + Strings.ToS = new Strings.ToSStrings(); + } + + public static uint Color(byte r, byte g, byte b, byte a) + { uint ret = a; ret <<= 8; ret += b; ret <<= 8; ret += g; ret <<= 8; ret += r; return ret; } + + public static void DrawOutlinedFont(ImDrawListPtr drawList, string text, Vector2 textPos, uint fontColor, uint outlineColor, int thickness) + { + drawList.AddText(textPos with { Y = textPos.Y - thickness }, + outlineColor, text); + drawList.AddText(textPos with { X = textPos.X - thickness }, + outlineColor, text); + drawList.AddText(textPos with { Y = textPos.Y + thickness }, + outlineColor, text); + drawList.AddText(textPos with { X = textPos.X + thickness }, + outlineColor, text); + drawList.AddText(new Vector2(textPos.X - thickness, textPos.Y - thickness), + outlineColor, text); + drawList.AddText(new Vector2(textPos.X + thickness, textPos.Y + thickness), + outlineColor, text); + drawList.AddText(new Vector2(textPos.X - thickness, textPos.Y + thickness), + outlineColor, text); + drawList.AddText(new Vector2(textPos.X + thickness, textPos.Y - thickness), + outlineColor, text); + + drawList.AddText(textPos, fontColor, text); + drawList.AddText(textPos, fontColor, text); + } + + public static string ByteToString(long bytes) + { + string[] suffix = { "B", "KiB", "MiB", "GiB", "TiB" }; + int i; + double dblSByte = bytes; + for (i = 0; i < suffix.Length && bytes >= 1024; i++, bytes /= 1024) + { + dblSByte = bytes / 1024.0; + } + + return $"{dblSByte:0.00} {suffix[i]}"; + } + + private int _serverSelectionIndex = 0; + private string _customServerName = ""; + private string _customServerUri = ""; + private bool _enterSecretKey = false; + private bool _cacheDirectoryHasOtherFilesThanCache = false; + private bool _cacheDirectoryIsValidPath = true; + + public void DrawServiceSelection(Action? callBackOnExit = null) + { + string[] comboEntries = _apiController.ServerDictionary.Values.ToArray(); + _serverSelectionIndex = Array.IndexOf(_apiController.ServerDictionary.Keys.ToArray(), _pluginConfiguration.ApiUri); + if (ImGui.BeginCombo("Select Service", comboEntries[_serverSelectionIndex])) + { + for (int i = 0; i < comboEntries.Length; i++) + { + bool isSelected = _serverSelectionIndex == i; + if (ImGui.Selectable(comboEntries[i], isSelected)) + { + _pluginConfiguration.ApiUri = _apiController.ServerDictionary.Single(k => k.Value == comboEntries[i]).Key; + _pluginConfiguration.Save(); + _ = _apiController.CreateConnections(); + } + + if (isSelected) + { + ImGui.SetItemDefaultFocus(); + } + } + + ImGui.EndCombo(); + } + + if (_serverSelectionIndex != 0) + { + ImGui.SameLine(); + ImGui.PushFont(UiBuilder.IconFont); + if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString() + "##deleteService")) + { + _pluginConfiguration.CustomServerList.Remove(_pluginConfiguration.ApiUri); + _pluginConfiguration.ApiUri = _apiController.ServerDictionary.First().Key; + _pluginConfiguration.Save(); + } + ImGui.PopFont(); + } + + if (ImGui.TreeNode("Add Custom Service")) + { + ImGui.SetNextItemWidth(250); + ImGui.InputText("Custom Service Name", ref _customServerName, 255); + ImGui.SetNextItemWidth(250); + ImGui.InputText("Custom Service Address", ref _customServerUri, 255); + if (ImGui.Button("Add Custom Service")) + { + if (!string.IsNullOrEmpty(_customServerUri) + && !string.IsNullOrEmpty(_customServerName) + && !_pluginConfiguration.CustomServerList.ContainsValue(_customServerName) + && !_pluginConfiguration.CustomServerList.ContainsKey(_customServerUri)) + { + _pluginConfiguration.CustomServerList[_customServerUri] = _customServerName; + _customServerUri = string.Empty; + _customServerName = string.Empty; + _pluginConfiguration.Save(); + } + } + ImGui.TreePop(); + } + + PrintServerState(); + + if (!_apiController.ServerAlive && (_pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri) && !_pluginConfiguration.ClientSecret[_pluginConfiguration.ApiUri].IsNullOrEmpty())) + { + ColorTextWrapped("You already have an account on this server.", ImGuiColors.DalamudYellow); + ImGui.SameLine(); + if (ImGui.Button("Connect##connectToService")) + { + _pluginConfiguration.FullPause = false; + _pluginConfiguration.Save(); + Task.Run(_apiController.CreateConnections); + } + } + + string checkboxText = _pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri) + ? "I want to switch accounts" + : "I have an account"; + ImGui.Checkbox(checkboxText, ref _enterSecretKey); + + if (_enterSecretKey) + { + ColorTextWrapped("This will overwrite your currently used secret key for the selected service. Make sure to have a backup for the current secret key if you want to switch back to this account.", ImGuiColors.DalamudYellow); + if (!_pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri)) + { + ColorTextWrapped("IF YOU HAVE NEVER MADE AN ACCOUNT BEFORE DO NOT ENTER ANYTHING HERE", ImGuiColors.DalamudYellow); + } + + var text = "Enter Secret Key"; + var buttonText = "Save"; + var buttonWidth = _secretKey.Length != 64 ? 0 : ImGuiHelpers.GetButtonSize(buttonText).X + ImGui.GetStyle().ItemSpacing.X; + var textSize = ImGui.CalcTextSize(text); + ImGui.AlignTextToFramePadding(); + ImGui.Text(text); + ImGui.SameLine(); + ImGui.SetNextItemWidth(GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonWidth - textSize.X); + ImGui.InputText("", ref _secretKey, 64); + if (_secretKey.Length > 0 && _secretKey.Length != 64) + { + ColorTextWrapped("Your secret key must be exactly 64 characters long. Don't enter your Lodestone auth here.", ImGuiColors.DalamudRed); + } + else if (_secretKey.Length == 64) + { + ImGui.SameLine(); + if (ImGui.Button(buttonText)) + { + _pluginConfiguration.ClientSecret[_pluginConfiguration.ApiUri] = _secretKey; + _pluginConfiguration.Save(); + _secretKey = string.Empty; + Task.Run(_apiController.CreateConnections); + _enterSecretKey = false; + callBackOnExit?.Invoke(); + } + } + } + } + + private string _secretKey = ""; + + public static void OutlineTextWrapped(string text, Vector4 textcolor, Vector4 outlineColor, float dist = 3) + { + var cursorPos = ImGui.GetCursorPos(); + UiShared.ColorTextWrapped(text, outlineColor); + ImGui.SetCursorPos(new(cursorPos.X, cursorPos.Y + dist)); + UiShared.ColorTextWrapped(text, outlineColor); + ImGui.SetCursorPos(new(cursorPos.X + dist, cursorPos.Y)); + UiShared.ColorTextWrapped(text, outlineColor); + ImGui.SetCursorPos(new(cursorPos.X + dist, cursorPos.Y + dist)); + UiShared.ColorTextWrapped(text, outlineColor); + + ImGui.SetCursorPos(new(cursorPos.X + dist / 2, cursorPos.Y + dist / 2)); + UiShared.ColorTextWrapped(text, textcolor); + ImGui.SetCursorPos(new(cursorPos.X + dist / 2, cursorPos.Y + dist / 2)); + UiShared.ColorTextWrapped(text, textcolor); + } + + public static void DrawHelpText(string helpText) + { + ImGui.SameLine(); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.SetWindowFontScale(0.8f); + ImGui.TextDisabled(FontAwesomeIcon.Question.ToIconString()); + ImGui.SetWindowFontScale(1.0f); + ImGui.PopFont(); + if (ImGui.IsItemHovered()) + { + ImGui.BeginTooltip(); + ImGui.PushTextWrapPos(ImGui.GetFontSize() * 35.0f); + ImGui.TextUnformatted(helpText); + ImGui.PopTextWrapPos(); + ImGui.EndTooltip(); + } + } + + public void DrawCacheDirectorySetting() + { + ColorTextWrapped("Note: The cache folder should be somewhere close to root (i.e. C:\\MareCache) in a new empty folder. DO NOT point this to your game folder. DO NOT point this to your Penumbra folder.", ImGuiColors.DalamudYellow); + var cacheDirectory = _pluginConfiguration.CacheFolder; + ImGui.InputText("Cache Folder##cache", ref cacheDirectory, 255, ImGuiInputTextFlags.ReadOnly); + + ImGui.SameLine(); + ImGui.PushFont(UiBuilder.IconFont); + string folderIcon = FontAwesomeIcon.Folder.ToIconString(); + if (ImGui.Button(folderIcon + "##chooseCacheFolder")) + { + _fileDialogManager.OpenFolderDialog("Pick Mare Synchronos Cache Folder", (success, path) => + { + if (!success) return; + + _isPenumbraDirectory = path.ToLowerInvariant() == _ipcManager.PenumbraModDirectory()?.ToLowerInvariant(); + _isDirectoryWritable = IsDirectoryWritable(path); + _cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => new FileInfo(f).Name.Length != 40); + _cacheDirectoryIsValidPath = Regex.IsMatch(path, @"^(?:[a-zA-Z]:\\[\w\s\-\\]+?|\/(?:[\w\s\-\/])+?)$", RegexOptions.ECMAScript); + + if (!string.IsNullOrEmpty(path) + && Directory.Exists(path) + && _isDirectoryWritable + && !_isPenumbraDirectory + && !_cacheDirectoryHasOtherFilesThanCache + && _cacheDirectoryIsValidPath) + { + _pluginConfiguration.CacheFolder = path; + _pluginConfiguration.Save(); + _cacheScanner.StartScan(); + } + }); + } + ImGui.PopFont(); + + if (_isPenumbraDirectory) + { + ColorTextWrapped("Do not point the cache path directly to the Penumbra directory. If necessary, make a subfolder in it.", ImGuiColors.DalamudRed); + } + else if (!_isDirectoryWritable) + { + ColorTextWrapped("The folder you selected does not exist or cannot be written to. Please provide a valid path.", ImGuiColors.DalamudRed); + } + else if (_cacheDirectoryHasOtherFilesThanCache) + { + ColorTextWrapped("Your selected directory has files inside that are not Mare related. Use an empty directory or a previous Mare cache directory only.", ImGuiColors.DalamudRed); + } + else if (!_cacheDirectoryIsValidPath) + { + ColorTextWrapped("Your selected directory contains illegal characters unreadable by FFXIV. " + + "Restrict yourself to latin letters (A-Z), underscores (_), dashes (-) and arabic numbers (0-9).", ImGuiColors.DalamudRed); + } + + int maxCacheSize = _pluginConfiguration.MaxLocalCacheInGiB; + if (ImGui.SliderInt("Maximum Cache Size in GB", ref maxCacheSize, 1, 50, "%d GB")) + { + _pluginConfiguration.MaxLocalCacheInGiB = maxCacheSize; + _pluginConfiguration.Save(); + } + DrawHelpText("The cache is automatically governed by Mare. It will clear itself automatically once it reaches the set capacity by removing the oldest unused files. You typically do not need to clear it yourself."); + } + + private bool _isDirectoryWritable = false; + private bool _isPenumbraDirectory = false; + + public bool IsDirectoryWritable(string dirPath, bool throwIfFails = false) + { + try + { + using (FileStream fs = File.Create( + Path.Combine( + dirPath, + Path.GetRandomFileName() + ), + 1, + FileOptions.DeleteOnClose) + ) + { } + return true; + } + catch + { + if (throwIfFails) + throw; + else + return false; + } + } + + public void RecalculateFileCacheSize() + { + _cacheScanner.InvokeScan(true); + } + + public void DrawTimeSpanBetweenScansSetting() + { + var timeSpan = _pluginConfiguration.TimeSpanBetweenScansInSeconds; + if (ImGui.SliderInt("Seconds between scans##timespan", ref timeSpan, 20, 60)) + { + _pluginConfiguration.TimeSpanBetweenScansInSeconds = timeSpan; + _pluginConfiguration.Save(); + } + DrawHelpText("This is the time in seconds between file scans. Increase it to reduce system load. A too high setting can cause issues when manually fumbling about in the cache or Penumbra mods folders."); + var isPaused = _pluginConfiguration.FileScanPaused; + if (ImGui.Checkbox("Pause periodic file scan##filescanpause", ref isPaused)) + { + _pluginConfiguration.FileScanPaused = isPaused; + _pluginConfiguration.Save(); + } + DrawHelpText("This allows you to stop the periodic scans of your Penumbra and Mare cache directories. Use this to move the Mare cache and Penumbra mod folders around. If you enable this permanently, run a Force rescan after adding mods to Penumbra."); + } + + public void Dispose() + { + _pluginInterface.UiBuilder.BuildFonts -= BuildFont; + } } diff --git a/MareSynchronos/Utils/Crypto.cs b/MareSynchronos/Utils/Crypto.cs index 36dba7c..a4062c0 100644 --- a/MareSynchronos/Utils/Crypto.cs +++ b/MareSynchronos/Utils/Crypto.cs @@ -4,32 +4,31 @@ using System.Security.Cryptography; using System.Text; using Dalamud.Game.ClientState.Objects.SubKinds; -namespace MareSynchronos.Utils +namespace MareSynchronos.Utils; + +public class Crypto { - public class Crypto + public static string GetFileHash(string filePath) { - public static string GetFileHash(string filePath) - { - using SHA1CryptoServiceProvider cryptoProvider = new(); - return BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", ""); - } + using SHA1CryptoServiceProvider cryptoProvider = new(); + return BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", ""); + } - public static string GetHash(string stringToHash) - { - using SHA1CryptoServiceProvider cryptoProvider = new(); - return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", ""); - } + public static string GetHash(string stringToHash) + { + using SHA1CryptoServiceProvider cryptoProvider = new(); + return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", ""); + } - public static string GetHash256(string stringToHash) - { - using SHA256CryptoServiceProvider cryptoProvider = new(); - return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", ""); - } + public static string GetHash256(string stringToHash) + { + using SHA256CryptoServiceProvider cryptoProvider = new(); + return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", ""); + } - public static string GetHash256(PlayerCharacter character) - { - using SHA256CryptoServiceProvider cryptoProvider = new(); - return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", ""); - } + public static string GetHash256(PlayerCharacter character) + { + using SHA256CryptoServiceProvider cryptoProvider = new(); + return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", ""); } } diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index d2eba06..4a9b979 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -11,232 +11,231 @@ using Dalamud.Game.ClientState.Objects.SubKinds; using FFXIVClientStructs.FFXIV.Client.Game.Character; using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; -namespace MareSynchronos.Utils +namespace MareSynchronos.Utils; + +public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor); + +public delegate void LogIn(); +public delegate void LogOut(); +public delegate void ClassJobChanged(); + +public delegate void FrameworkUpdate(); +public delegate void VoidDelegate(); + +public class DalamudUtil : IDisposable { - public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor); + private readonly ClientState _clientState; + private readonly ObjectTable _objectTable; + private readonly Framework _framework; + private readonly Condition _condition; - public delegate void LogIn(); - public delegate void LogOut(); - public delegate void ClassJobChanged(); + public event LogIn? LogIn; + public event LogOut? LogOut; + public event FrameworkUpdate? FrameworkUpdate; + public event ClassJobChanged? ClassJobChanged; + private uint? classJobId = 0; + public event FrameworkUpdate? DelayedFrameworkUpdate; + public event VoidDelegate? ZoneSwitchStart; + public event VoidDelegate? ZoneSwitchEnd; + private DateTime _delayedFrameworkUpdateCheck = DateTime.Now; + private bool _sentBetweenAreas = false; - public delegate void FrameworkUpdate(); - public delegate void VoidDelegate(); - - public class DalamudUtil : IDisposable + public unsafe bool IsGameObjectPresent(IntPtr key) { - private readonly ClientState _clientState; - private readonly ObjectTable _objectTable; - private readonly Framework _framework; - private readonly Condition _condition; - - public event LogIn? LogIn; - public event LogOut? LogOut; - public event FrameworkUpdate? FrameworkUpdate; - public event ClassJobChanged? ClassJobChanged; - private uint? classJobId = 0; - public event FrameworkUpdate? DelayedFrameworkUpdate; - public event VoidDelegate? ZoneSwitchStart; - public event VoidDelegate? ZoneSwitchEnd; - private DateTime _delayedFrameworkUpdateCheck = DateTime.Now; - private bool _sentBetweenAreas = false; - - public unsafe bool IsGameObjectPresent(IntPtr key) + foreach (var obj in _objectTable) { - foreach (var obj in _objectTable) + if (obj.Address == key) { - if (obj.Address == key) - { - return true; - } - } - - return false; - } - - public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework, Condition condition) - { - _clientState = clientState; - _objectTable = objectTable; - _framework = framework; - _condition = condition; - _clientState.Login += ClientStateOnLogin; - _clientState.Logout += ClientStateOnLogout; - _framework.Update += FrameworkOnUpdate; - if (IsLoggedIn) - { - classJobId = _clientState.LocalPlayer!.ClassJob.Id; - ClientStateOnLogin(null, EventArgs.Empty); + return true; } } - private void FrameworkOnUpdate(Framework framework) - { - if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || IsInGpose) - { - if (!_sentBetweenAreas) - { - Logger.Debug("Zone switch/Gpose start"); - _sentBetweenAreas = true; - ZoneSwitchStart?.Invoke(); - } + return false; + } - return; - } - else if (_sentBetweenAreas) + public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework, Condition condition) + { + _clientState = clientState; + _objectTable = objectTable; + _framework = framework; + _condition = condition; + _clientState.Login += ClientStateOnLogin; + _clientState.Logout += ClientStateOnLogout; + _framework.Update += FrameworkOnUpdate; + if (IsLoggedIn) + { + classJobId = _clientState.LocalPlayer!.ClassJob.Id; + ClientStateOnLogin(null, EventArgs.Empty); + } + } + + private void FrameworkOnUpdate(Framework framework) + { + if (_condition[ConditionFlag.BetweenAreas] || _condition[ConditionFlag.BetweenAreas51] || IsInGpose) + { + if (!_sentBetweenAreas) { - Logger.Debug("Zone switch/Gpose end"); - _sentBetweenAreas = false; - ZoneSwitchEnd?.Invoke(); + Logger.Debug("Zone switch/Gpose start"); + _sentBetweenAreas = true; + ZoneSwitchStart?.Invoke(); } - foreach (FrameworkUpdate? frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty()).Cast()) + return; + } + else if (_sentBetweenAreas) + { + Logger.Debug("Zone switch/Gpose end"); + _sentBetweenAreas = false; + ZoneSwitchEnd?.Invoke(); + } + + foreach (FrameworkUpdate? frameworkInvocation in (FrameworkUpdate?.GetInvocationList() ?? Array.Empty()).Cast()) + { + try { - try - { - frameworkInvocation?.Invoke(); - } - catch (Exception ex) - { - Logger.Warn(ex.Message); - Logger.Warn(ex.StackTrace ?? string.Empty); - } + frameworkInvocation?.Invoke(); } - - if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return; - if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid()) + catch (Exception ex) { - var newclassJobId = _clientState.LocalPlayer.ClassJob.Id; - - if (classJobId != newclassJobId) - { - classJobId = newclassJobId; - ClassJobChanged?.Invoke(); - } + Logger.Warn(ex.Message); + Logger.Warn(ex.StackTrace ?? string.Empty); } + } - foreach (FrameworkUpdate? frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty()).Cast()) + if (DateTime.Now < _delayedFrameworkUpdateCheck.AddSeconds(1)) return; + if (_clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid()) + { + var newclassJobId = _clientState.LocalPlayer.ClassJob.Id; + + if (classJobId != newclassJobId) { - try - { - frameworkInvocation?.Invoke(); - } - catch (Exception ex) - { - Logger.Warn(ex.Message); - Logger.Warn(ex.StackTrace ?? string.Empty); - } + classJobId = newclassJobId; + ClassJobChanged?.Invoke(); } - _delayedFrameworkUpdateCheck = DateTime.Now; } - private void ClientStateOnLogout(object? sender, EventArgs e) + foreach (FrameworkUpdate? frameworkInvocation in (DelayedFrameworkUpdate?.GetInvocationList() ?? Array.Empty()).Cast()) { - LogOut?.Invoke(); - } - - private void ClientStateOnLogin(object? sender, EventArgs e) - { - LogIn?.Invoke(); - } - - public Dalamud.Game.ClientState.Objects.Types.GameObject? CreateGameObject(IntPtr reference) - { - return _objectTable.CreateObjectReference(reference); - } - - public bool IsLoggedIn => _clientState.IsLoggedIn; - - public bool IsPlayerPresent => _clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid(); - - public bool IsObjectPresent(Dalamud.Game.ClientState.Objects.Types.GameObject? obj) - { - return obj != null && obj.IsValid(); - } - - public unsafe IntPtr GetMinion() - { - return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)PlayerPointer)->CompanionObject; - } - - public unsafe IntPtr GetPet(IntPtr? playerPointer = null) - { - var mgr = CharacterManager.Instance(); - if (playerPointer == null) playerPointer = PlayerPointer; - return (IntPtr)mgr->LookupPetByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer); - } - - public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null) - { - var mgr = CharacterManager.Instance(); - if (playerPointer == null) playerPointer = PlayerPointer; - return (IntPtr)mgr->LookupBuddyByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer); - } - - public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--"; - - public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero; - - public PlayerCharacter PlayerCharacter => _clientState.LocalPlayer!; - - public string PlayerNameHashed => Crypto.GetHash256(PlayerName + _clientState.LocalPlayer!.HomeWorld.Id); - - public bool IsInGpose => _objectTable[201] != null; - - public List GetPlayerCharacters() - { - return _objectTable.Where(obj => - obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player && - obj.Name.ToString() != PlayerName).Select(p => (PlayerCharacter)p).ToList(); - } - - public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index) - { - var objTableObj = _objectTable[index]; - if (objTableObj!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) return null; - return (Dalamud.Game.ClientState.Objects.Types.Character)objTableObj; - } - - public PlayerCharacter? GetPlayerCharacterFromObjectTableByName(string characterName) - { - foreach (var item in _objectTable) + try { - if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue; - if (item.Name.ToString() == characterName) return (PlayerCharacter)item; + frameworkInvocation?.Invoke(); } - - return null; - } - - public async Task RunOnFrameworkThread(Func func) - { - return await _framework.RunOnFrameworkThread(func); - } - - public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, int timeOut = 5000, CancellationToken? ct = null) - { - if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return; - - var obj = (GameObject*)characterAddress; - const int tick = 250; - int curWaitTime = 0; - // ReSharper disable once LoopVariableIsNeverChangedInsideLoop - while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something + catch (Exception ex) { - Logger.Verbose($"Waiting for {name} to finish drawing"); - curWaitTime += tick; - Thread.Sleep(tick); + Logger.Warn(ex.Message); + Logger.Warn(ex.StackTrace ?? string.Empty); } + } + _delayedFrameworkUpdateCheck = DateTime.Now; + } - if (ct?.IsCancellationRequested ?? false) return; - // wait quarter a second just in case + private void ClientStateOnLogout(object? sender, EventArgs e) + { + LogOut?.Invoke(); + } + + private void ClientStateOnLogin(object? sender, EventArgs e) + { + LogIn?.Invoke(); + } + + public Dalamud.Game.ClientState.Objects.Types.GameObject? CreateGameObject(IntPtr reference) + { + return _objectTable.CreateObjectReference(reference); + } + + public bool IsLoggedIn => _clientState.IsLoggedIn; + + public bool IsPlayerPresent => _clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid(); + + public bool IsObjectPresent(Dalamud.Game.ClientState.Objects.Types.GameObject? obj) + { + return obj != null && obj.IsValid(); + } + + public unsafe IntPtr GetMinion() + { + return (IntPtr)((FFXIVClientStructs.FFXIV.Client.Game.Character.Character*)PlayerPointer)->CompanionObject; + } + + public unsafe IntPtr GetPet(IntPtr? playerPointer = null) + { + var mgr = CharacterManager.Instance(); + if (playerPointer == null) playerPointer = PlayerPointer; + return (IntPtr)mgr->LookupPetByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer); + } + + public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null) + { + var mgr = CharacterManager.Instance(); + if (playerPointer == null) playerPointer = PlayerPointer; + return (IntPtr)mgr->LookupBuddyByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer); + } + + public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--"; + + public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero; + + public PlayerCharacter PlayerCharacter => _clientState.LocalPlayer!; + + public string PlayerNameHashed => Crypto.GetHash256(PlayerName + _clientState.LocalPlayer!.HomeWorld.Id); + + public bool IsInGpose => _objectTable[201] != null; + + public List GetPlayerCharacters() + { + return _objectTable.Where(obj => + obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player && + obj.Name.ToString() != PlayerName).Select(p => (PlayerCharacter)p).ToList(); + } + + public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index) + { + var objTableObj = _objectTable[index]; + if (objTableObj!.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) return null; + return (Dalamud.Game.ClientState.Objects.Types.Character)objTableObj; + } + + public PlayerCharacter? GetPlayerCharacterFromObjectTableByName(string characterName) + { + foreach (var item in _objectTable) + { + if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue; + if (item.Name.ToString() == characterName) return (PlayerCharacter)item; + } + + return null; + } + + public async Task RunOnFrameworkThread(Func func) + { + return await _framework.RunOnFrameworkThread(func); + } + + public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, int timeOut = 5000, CancellationToken? ct = null) + { + if (!_clientState.IsLoggedIn || characterAddress == IntPtr.Zero) return; + + var obj = (GameObject*)characterAddress; + const int tick = 250; + int curWaitTime = 0; + // ReSharper disable once LoopVariableIsNeverChangedInsideLoop + while ((obj->RenderFlags & 0b100000000000) == 0b100000000000 && (!ct?.IsCancellationRequested ?? true) && curWaitTime < timeOut) // 0b100000000000 is "still rendering" or something + { + Logger.Verbose($"Waiting for {name} to finish drawing"); + curWaitTime += tick; Thread.Sleep(tick); } - public void Dispose() - { - _clientState.Login -= ClientStateOnLogin; - _clientState.Logout -= ClientStateOnLogout; - _framework.Update -= FrameworkOnUpdate; - } + if (ct?.IsCancellationRequested ?? false) return; + // wait quarter a second just in case + Thread.Sleep(tick); + } + + public void Dispose() + { + _clientState.Login -= ClientStateOnLogin; + _clientState.Logout -= ClientStateOnLogout; + _framework.Update -= FrameworkOnUpdate; } } diff --git a/MareSynchronos/Utils/Logger.cs b/MareSynchronos/Utils/Logger.cs index dcf0b61..4d90bac 100644 --- a/MareSynchronos/Utils/Logger.cs +++ b/MareSynchronos/Utils/Logger.cs @@ -5,108 +5,107 @@ using Dalamud.Logging; using Dalamud.Utility; using Microsoft.Extensions.Logging; -namespace MareSynchronos.Utils +namespace MareSynchronos.Utils; + +[ProviderAlias("Dalamud")] +public class DalamudLoggingProvider : ILoggerProvider { - [ProviderAlias("Dalamud")] - public class DalamudLoggingProvider : ILoggerProvider + private readonly ConcurrentDictionary _loggers = + new(StringComparer.OrdinalIgnoreCase); + + public DalamudLoggingProvider() { - private readonly ConcurrentDictionary _loggers = - new(StringComparer.OrdinalIgnoreCase); - - public DalamudLoggingProvider() - { - } - - public ILogger CreateLogger(string categoryName) - { - return _loggers.GetOrAdd(categoryName, name => new Logger(categoryName)); - } - - public void Dispose() - { - _loggers.Clear(); - } } - internal class Logger : ILogger + public ILogger CreateLogger(string categoryName) { - private readonly string name; + return _loggers.GetOrAdd(categoryName, name => new Logger(categoryName)); + } - public static void Info(string info) - { - var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; - PluginLog.Information($"[{caller}] {info}"); - } - - public static void Debug(string debug, string stringToHighlight = "") - { - var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; - if (debug.Contains(stringToHighlight) && !stringToHighlight.IsNullOrEmpty()) - { - PluginLog.Warning($"[{caller}] {debug}"); - } - else - { - PluginLog.Debug($"[{caller}] {debug}"); - } - } - - public static void Warn(string warn) - { - var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; - PluginLog.Warning($"[{caller}] {warn}"); - } - - public static void Verbose(string verbose) - { - var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; -#if DEBUG - PluginLog.Debug($"[{caller}] {verbose}"); -#else - PluginLog.Verbose($"[{caller}] {verbose}"); -#endif - } - - public Logger(string name) - { - this.name = name; - } - - public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) - { - if (!IsEnabled(logLevel)) return; - - switch (logLevel) - { - case LogLevel.Debug: - PluginLog.Debug($"[{name}] [{eventId}] {formatter(state, exception)}"); - break; - case LogLevel.Error: - case LogLevel.Critical: - PluginLog.Error($"[{name}] [{eventId}] {formatter(state, exception)}"); - break; - case LogLevel.Information: - PluginLog.Information($"[{name}] [{eventId}] {formatter(state, exception)}"); - break; - case LogLevel.Warning: - PluginLog.Warning($"[{name}] [{eventId}] {formatter(state, exception)}"); - break; - case LogLevel.Trace: - default: -#if DEBUG - PluginLog.Verbose($"[{name}] [{eventId}] {formatter(state, exception)}"); -#else - PluginLog.Verbose($"[{name}] {eventId} {state} {formatter(state, exception)}"); -#endif - break; - } - } - - public bool IsEnabled(LogLevel logLevel) - { - return true; - } - - public IDisposable BeginScope(TState state) => default!; + public void Dispose() + { + _loggers.Clear(); } } + +internal class Logger : ILogger +{ + private readonly string name; + + public static void Info(string info) + { + var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; + PluginLog.Information($"[{caller}] {info}"); + } + + public static void Debug(string debug, string stringToHighlight = "") + { + var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; + if (debug.Contains(stringToHighlight) && !stringToHighlight.IsNullOrEmpty()) + { + PluginLog.Warning($"[{caller}] {debug}"); + } + else + { + PluginLog.Debug($"[{caller}] {debug}"); + } + } + + public static void Warn(string warn) + { + var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; + PluginLog.Warning($"[{caller}] {warn}"); + } + + public static void Verbose(string verbose) + { + var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; +#if DEBUG + PluginLog.Debug($"[{caller}] {verbose}"); +#else + PluginLog.Verbose($"[{caller}] {verbose}"); +#endif + } + + public Logger(string name) + { + this.name = name; + } + + public void Log(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func formatter) + { + if (!IsEnabled(logLevel)) return; + + switch (logLevel) + { + case LogLevel.Debug: + PluginLog.Debug($"[{name}] [{eventId}] {formatter(state, exception)}"); + break; + case LogLevel.Error: + case LogLevel.Critical: + PluginLog.Error($"[{name}] [{eventId}] {formatter(state, exception)}"); + break; + case LogLevel.Information: + PluginLog.Information($"[{name}] [{eventId}] {formatter(state, exception)}"); + break; + case LogLevel.Warning: + PluginLog.Warning($"[{name}] [{eventId}] {formatter(state, exception)}"); + break; + case LogLevel.Trace: + default: +#if DEBUG + PluginLog.Verbose($"[{name}] [{eventId}] {formatter(state, exception)}"); +#else + PluginLog.Verbose($"[{name}] {eventId} {state} {formatter(state, exception)}"); +#endif + break; + } + } + + public bool IsEnabled(LogLevel logLevel) + { + return true; + } + + public IDisposable BeginScope(TState state) => default!; +} diff --git a/MareSynchronos/Utils/Various.cs b/MareSynchronos/Utils/Various.cs index fa96e17..98fb104 100644 --- a/MareSynchronos/Utils/Various.cs +++ b/MareSynchronos/Utils/Various.cs @@ -6,27 +6,26 @@ using System.Reflection; using System.Text; using System.Threading.Tasks; -namespace MareSynchronos.Utils +namespace MareSynchronos.Utils; + +public static class VariousExtensions { - public static class VariousExtensions + public static DateTime GetLinkerTime(Assembly assembly) { - public static DateTime GetLinkerTime(Assembly assembly) + const string BuildVersionMetadataPrefix = "+build"; + + var attribute = assembly.GetCustomAttribute(); + if (attribute?.InformationalVersion != null) { - const string BuildVersionMetadataPrefix = "+build"; - - var attribute = assembly.GetCustomAttribute(); - if (attribute?.InformationalVersion != null) + var value = attribute.InformationalVersion; + var index = value.IndexOf(BuildVersionMetadataPrefix); + if (index > 0) { - var value = attribute.InformationalVersion; - var index = value.IndexOf(BuildVersionMetadataPrefix); - if (index > 0) - { - value = value[(index + BuildVersionMetadataPrefix.Length)..]; - return DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss:fffZ", CultureInfo.InvariantCulture); - } + value = value[(index + BuildVersionMetadataPrefix.Length)..]; + return DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss:fffZ", CultureInfo.InvariantCulture); } - - return default; } + + return default; } } diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs index 48e2712..cd6b637 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs @@ -13,306 +13,305 @@ using MareSynchronos.Utils; using MareSynchronos.WebAPI.Utils; using Microsoft.AspNetCore.SignalR.Client; -namespace MareSynchronos.WebAPI +namespace MareSynchronos.WebAPI; + +public partial class ApiController { - public partial class ApiController + private readonly HashSet _verifiedUploadedHashes; + + private int _downloadId = 0; + public void CancelUpload() { - private readonly HashSet _verifiedUploadedHashes; - - private int _downloadId = 0; - public void CancelUpload() + if (_uploadCancellationTokenSource != null) { - if (_uploadCancellationTokenSource != null) - { - Logger.Debug("Cancelling upload"); - _uploadCancellationTokenSource?.Cancel(); - _mareHub!.SendAsync(Api.SendFileAbortUpload); - CurrentUploads.Clear(); - } - } - - public async Task DeleteAllMyFiles() - { - await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles); - } - - private async Task DownloadFile(int downloadId, string hash, Uri downloadUri, CancellationToken ct) - { - using WebClient wc = new(); - wc.Headers.Add("Authorization", SecretKey); - DownloadProgressChangedEventHandler progChanged = (s, e) => - { - try - { - CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = e.BytesReceived; - } - catch (Exception ex) - { - Logger.Warn("Could not set download progress for " + hash); - Logger.Warn(ex.Message); - Logger.Warn(ex.StackTrace ?? string.Empty); - } - }; - wc.DownloadProgressChanged += progChanged; - - string fileName = Path.GetTempFileName(); - - ct.Register(wc.CancelAsync); - - try - { - await wc.DownloadFileTaskAsync(downloadUri, fileName); - } - catch { } - - CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = CurrentDownloads[downloadId].Single(f => f.Hash == hash).Total; - - wc.DownloadProgressChanged -= progChanged; - return fileName; - } - - public int GetDownloadId() => _downloadId++; - - public async Task DownloadFiles(int currentDownloadId, List fileReplacementDto, CancellationToken ct) - { - DownloadStarted?.Invoke(); - try - { - await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct); - } - catch - { - CancelDownload(currentDownloadId); - } - finally - { - DownloadFinished?.Invoke(); - } - } - - private async Task DownloadFilesInternal(int currentDownloadId, List fileReplacementDto, CancellationToken ct) - { - Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")"); - - List downloadFileInfoFromService = new List(); - downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct)); - - Logger.Debug("Files with size 0 or less: " + string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash))); - - CurrentDownloads[currentDownloadId] = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d)) - .Where(d => d.CanBeTransferred).ToList(); - - foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden)) - { - if (ForbiddenTransfers.All(f => f.Hash != dto.Hash)) - { - ForbiddenTransfers.Add(new DownloadFileTransfer(dto)); - } - } - - await Parallel.ForEachAsync(CurrentDownloads[currentDownloadId].Where(f => f.CanBeTransferred), new ParallelOptions() - { - MaxDegreeOfParallelism = 5, - CancellationToken = ct - }, - async (file, token) => - { - var hash = file.Hash; - var tempFile = await DownloadFile(currentDownloadId, file.Hash, file.DownloadUri, token); - if (token.IsCancellationRequested) - { - File.Delete(tempFile); - Logger.Debug("Detected cancellation, removing " + currentDownloadId); - DownloadFinished?.Invoke(); - CancelDownload(currentDownloadId); - return; - } - - var tempFileData = await File.ReadAllBytesAsync(tempFile, token); - var extractedFile = LZ4Codec.Unwrap(tempFileData); - File.Delete(tempFile); - var filePath = Path.Combine(_pluginConfiguration.CacheFolder, file.Hash); - await File.WriteAllBytesAsync(filePath, extractedFile, token); - var fi = new FileInfo(filePath); - Func RandomDayFunc() - { - DateTime start = new DateTime(1995, 1, 1); - Random gen = new Random(); - int range = (DateTime.Today - start).Days; - return () => start.AddDays(gen.Next(range)); - } - - fi.CreationTime = RandomDayFunc().Invoke(); - fi.LastAccessTime = RandomDayFunc().Invoke(); - fi.LastWriteTime = RandomDayFunc().Invoke(); - try - { - _ = _fileDbManager.CreateCacheEntry(filePath); - } - catch (Exception ex) - { - Logger.Warn("Issue adding file to the DB"); - Logger.Warn(ex.Message); - Logger.Warn(ex.StackTrace); - } - }); - - Logger.Debug("Download complete, removing " + currentDownloadId); - CancelDownload(currentDownloadId); - } - - public async Task PushCharacterData(CharacterCacheDto character, List visibleCharacterIds) - { - if (!IsConnected || SecretKey == "-") return; - Logger.Debug("Sending Character data to service " + ApiUri); - - CancelUpload(); - _uploadCancellationTokenSource = new CancellationTokenSource(); - var uploadToken = _uploadCancellationTokenSource.Token; - Logger.Verbose("New Token Created"); - - List unverifiedUploadHashes = new(); - foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct()).Distinct().ToList()) - { - if (!_verifiedUploadedHashes.Contains(item)) - { - unverifiedUploadHashes.Add(item); - } - } - - if (unverifiedUploadHashes.Any()) - { - Logger.Debug("Verifying " + unverifiedUploadHashes.Count + " files"); - var filesToUpload = await _mareHub!.InvokeAsync>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken); - - foreach (var file in filesToUpload.Where(f => !f.IsForbidden)) - { - try - { - CurrentUploads.Add(new UploadFileTransfer(file) - { - Total = new FileInfo(_fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath).Length - }); - } - catch (Exception ex) - { - Logger.Warn("Tried to request file " + file.Hash + " but file was not present"); - Logger.Warn(ex.StackTrace!); - } - } - - foreach (var file in filesToUpload.Where(c => c.IsForbidden)) - { - if (ForbiddenTransfers.All(f => f.Hash != file.Hash)) - { - ForbiddenTransfers.Add(new UploadFileTransfer(file) - { - LocalFile = _fileDbManager.GetFileCacheByHash(file.Hash)?.ResolvedFilepath ?? string.Empty - }); - } - } - - var totalSize = CurrentUploads.Sum(c => c.Total); - Logger.Debug("Compressing and uploading files"); - foreach (var file in CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList()) - { - Logger.Debug("Compressing and uploading " + file); - var data = await GetCompressedFileData(file.Hash, uploadToken); - CurrentUploads.Single(e => e.Hash == data.Item1).Total = data.Item2.Length; - await UploadFile(data.Item2, file.Hash, uploadToken); - if (!uploadToken.IsCancellationRequested) continue; - Logger.Warn("Cancel in filesToUpload loop detected"); - CurrentUploads.Clear(); - break; - } - - if (CurrentUploads.Any()) - { - var compressedSize = CurrentUploads.Sum(c => c.Total); - Logger.Debug($"Compressed {totalSize} to {compressedSize} ({(compressedSize / (double)totalSize):P2})"); - } - - Logger.Debug("Upload tasks complete, waiting for server to confirm"); - var anyUploadsOpen = await _mareHub!.InvokeAsync(Api.InvokeFileIsUploadFinished, uploadToken); - Logger.Debug("Uploads open: " + anyUploadsOpen); - while (anyUploadsOpen && !uploadToken.IsCancellationRequested) - { - anyUploadsOpen = await _mareHub!.InvokeAsync(Api.InvokeFileIsUploadFinished, uploadToken); - await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken); - Logger.Debug("Waiting for uploads to finish"); - } - - foreach (var item in unverifiedUploadHashes) - { - _verifiedUploadedHashes.Add(item); - } - - CurrentUploads.Clear(); - } - else - { - Logger.Debug("All files already verified"); - } - - if (!uploadToken.IsCancellationRequested) - { - Logger.Info("Pushing character data for " + character.GetHashCode() + " to " + string.Join(", ", visibleCharacterIds)); - StringBuilder sb = new StringBuilder(); - foreach (var item in character.FileReplacements) - { - sb.AppendLine($"FileReplacements for {item.Key}: {item.Value.Count}"); - } - foreach (var item in character.GlamourerData) - { - sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}"); - } - Logger.Debug("Chara data contained: " + Environment.NewLine + sb.ToString()); - await _mareHub!.InvokeAsync(Api.InvokeUserPushCharacterDataToVisibleClients, character, visibleCharacterIds, uploadToken); - } - else - { - Logger.Warn("=== Upload operation was cancelled ==="); - } - - Logger.Verbose("Upload complete for " + character.GetHashCode()); - _uploadCancellationTokenSource = null; - } - - private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken) - { - var fileCache = _fileDbManager.GetFileCacheByHash(fileHash)!.ResolvedFilepath; - return (fileHash, LZ4Codec.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken), 0, - (int)new FileInfo(fileCache).Length)); - } - - private async Task UploadFile(byte[] compressedFile, string fileHash, CancellationToken uploadToken) - { - if (uploadToken.IsCancellationRequested) return; - - async IAsyncEnumerable AsyncFileData([EnumeratorCancellation] CancellationToken token) - { - var chunkSize = 1024 * 512; // 512kb - using var ms = new MemoryStream(compressedFile); - var buffer = new byte[chunkSize]; - int bytesRead; - while ((bytesRead = await ms.ReadAsync(buffer, 0, chunkSize, token)) > 0 && !token.IsCancellationRequested) - { - CurrentUploads.Single(f => f.Hash == fileHash).Transferred += bytesRead; - token.ThrowIfCancellationRequested(); - yield return bytesRead == chunkSize ? buffer.ToArray() : buffer.Take(bytesRead).ToArray(); - } - } - - await _mareHub!.SendAsync(Api.SendFileUploadFileStreamAsync, fileHash, AsyncFileData(uploadToken), uploadToken); - } - - public void CancelDownload(int downloadId) - { - while (CurrentDownloads.ContainsKey(downloadId)) - { - CurrentDownloads.TryRemove(downloadId, out _); - } + Logger.Debug("Cancelling upload"); + _uploadCancellationTokenSource?.Cancel(); + _mareHub!.SendAsync(Api.SendFileAbortUpload); + CurrentUploads.Clear(); } } + public async Task DeleteAllMyFiles() + { + await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles); + } + + private async Task DownloadFile(int downloadId, string hash, Uri downloadUri, CancellationToken ct) + { + using WebClient wc = new(); + wc.Headers.Add("Authorization", SecretKey); + DownloadProgressChangedEventHandler progChanged = (s, e) => + { + try + { + CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = e.BytesReceived; + } + catch (Exception ex) + { + Logger.Warn("Could not set download progress for " + hash); + Logger.Warn(ex.Message); + Logger.Warn(ex.StackTrace ?? string.Empty); + } + }; + wc.DownloadProgressChanged += progChanged; + + string fileName = Path.GetTempFileName(); + + ct.Register(wc.CancelAsync); + + try + { + await wc.DownloadFileTaskAsync(downloadUri, fileName); + } + catch { } + + CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = CurrentDownloads[downloadId].Single(f => f.Hash == hash).Total; + + wc.DownloadProgressChanged -= progChanged; + return fileName; + } + + public int GetDownloadId() => _downloadId++; + + public async Task DownloadFiles(int currentDownloadId, List fileReplacementDto, CancellationToken ct) + { + DownloadStarted?.Invoke(); + try + { + await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct); + } + catch + { + CancelDownload(currentDownloadId); + } + finally + { + DownloadFinished?.Invoke(); + } + } + + private async Task DownloadFilesInternal(int currentDownloadId, List fileReplacementDto, CancellationToken ct) + { + Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")"); + + List downloadFileInfoFromService = new List(); + downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct)); + + Logger.Debug("Files with size 0 or less: " + string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash))); + + CurrentDownloads[currentDownloadId] = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d)) + .Where(d => d.CanBeTransferred).ToList(); + + foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden)) + { + if (ForbiddenTransfers.All(f => f.Hash != dto.Hash)) + { + ForbiddenTransfers.Add(new DownloadFileTransfer(dto)); + } + } + + await Parallel.ForEachAsync(CurrentDownloads[currentDownloadId].Where(f => f.CanBeTransferred), new ParallelOptions() + { + MaxDegreeOfParallelism = 5, + CancellationToken = ct + }, + async (file, token) => + { + var hash = file.Hash; + var tempFile = await DownloadFile(currentDownloadId, file.Hash, file.DownloadUri, token); + if (token.IsCancellationRequested) + { + File.Delete(tempFile); + Logger.Debug("Detected cancellation, removing " + currentDownloadId); + DownloadFinished?.Invoke(); + CancelDownload(currentDownloadId); + return; + } + + var tempFileData = await File.ReadAllBytesAsync(tempFile, token); + var extractedFile = LZ4Codec.Unwrap(tempFileData); + File.Delete(tempFile); + var filePath = Path.Combine(_pluginConfiguration.CacheFolder, file.Hash); + await File.WriteAllBytesAsync(filePath, extractedFile, token); + var fi = new FileInfo(filePath); + Func RandomDayFunc() + { + DateTime start = new DateTime(1995, 1, 1); + Random gen = new Random(); + int range = (DateTime.Today - start).Days; + return () => start.AddDays(gen.Next(range)); + } + + fi.CreationTime = RandomDayFunc().Invoke(); + fi.LastAccessTime = RandomDayFunc().Invoke(); + fi.LastWriteTime = RandomDayFunc().Invoke(); + try + { + _ = _fileDbManager.CreateCacheEntry(filePath); + } + catch (Exception ex) + { + Logger.Warn("Issue adding file to the DB"); + Logger.Warn(ex.Message); + Logger.Warn(ex.StackTrace); + } + }); + + Logger.Debug("Download complete, removing " + currentDownloadId); + CancelDownload(currentDownloadId); + } + + public async Task PushCharacterData(CharacterCacheDto character, List visibleCharacterIds) + { + if (!IsConnected || SecretKey == "-") return; + Logger.Debug("Sending Character data to service " + ApiUri); + + CancelUpload(); + _uploadCancellationTokenSource = new CancellationTokenSource(); + var uploadToken = _uploadCancellationTokenSource.Token; + Logger.Verbose("New Token Created"); + + List unverifiedUploadHashes = new(); + foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct()).Distinct().ToList()) + { + if (!_verifiedUploadedHashes.Contains(item)) + { + unverifiedUploadHashes.Add(item); + } + } + + if (unverifiedUploadHashes.Any()) + { + Logger.Debug("Verifying " + unverifiedUploadHashes.Count + " files"); + var filesToUpload = await _mareHub!.InvokeAsync>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken); + + foreach (var file in filesToUpload.Where(f => !f.IsForbidden)) + { + try + { + CurrentUploads.Add(new UploadFileTransfer(file) + { + Total = new FileInfo(_fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath).Length + }); + } + catch (Exception ex) + { + Logger.Warn("Tried to request file " + file.Hash + " but file was not present"); + Logger.Warn(ex.StackTrace!); + } + } + + foreach (var file in filesToUpload.Where(c => c.IsForbidden)) + { + if (ForbiddenTransfers.All(f => f.Hash != file.Hash)) + { + ForbiddenTransfers.Add(new UploadFileTransfer(file) + { + LocalFile = _fileDbManager.GetFileCacheByHash(file.Hash)?.ResolvedFilepath ?? string.Empty + }); + } + } + + var totalSize = CurrentUploads.Sum(c => c.Total); + Logger.Debug("Compressing and uploading files"); + foreach (var file in CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList()) + { + Logger.Debug("Compressing and uploading " + file); + var data = await GetCompressedFileData(file.Hash, uploadToken); + CurrentUploads.Single(e => e.Hash == data.Item1).Total = data.Item2.Length; + await UploadFile(data.Item2, file.Hash, uploadToken); + if (!uploadToken.IsCancellationRequested) continue; + Logger.Warn("Cancel in filesToUpload loop detected"); + CurrentUploads.Clear(); + break; + } + + if (CurrentUploads.Any()) + { + var compressedSize = CurrentUploads.Sum(c => c.Total); + Logger.Debug($"Compressed {totalSize} to {compressedSize} ({(compressedSize / (double)totalSize):P2})"); + } + + Logger.Debug("Upload tasks complete, waiting for server to confirm"); + var anyUploadsOpen = await _mareHub!.InvokeAsync(Api.InvokeFileIsUploadFinished, uploadToken); + Logger.Debug("Uploads open: " + anyUploadsOpen); + while (anyUploadsOpen && !uploadToken.IsCancellationRequested) + { + anyUploadsOpen = await _mareHub!.InvokeAsync(Api.InvokeFileIsUploadFinished, uploadToken); + await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken); + Logger.Debug("Waiting for uploads to finish"); + } + + foreach (var item in unverifiedUploadHashes) + { + _verifiedUploadedHashes.Add(item); + } + + CurrentUploads.Clear(); + } + else + { + Logger.Debug("All files already verified"); + } + + if (!uploadToken.IsCancellationRequested) + { + Logger.Info("Pushing character data for " + character.GetHashCode() + " to " + string.Join(", ", visibleCharacterIds)); + StringBuilder sb = new StringBuilder(); + foreach (var item in character.FileReplacements) + { + sb.AppendLine($"FileReplacements for {item.Key}: {item.Value.Count}"); + } + foreach (var item in character.GlamourerData) + { + sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}"); + } + Logger.Debug("Chara data contained: " + Environment.NewLine + sb.ToString()); + await _mareHub!.InvokeAsync(Api.InvokeUserPushCharacterDataToVisibleClients, character, visibleCharacterIds, uploadToken); + } + else + { + Logger.Warn("=== Upload operation was cancelled ==="); + } + + Logger.Verbose("Upload complete for " + character.GetHashCode()); + _uploadCancellationTokenSource = null; + } + + private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken) + { + var fileCache = _fileDbManager.GetFileCacheByHash(fileHash)!.ResolvedFilepath; + return (fileHash, LZ4Codec.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken), 0, + (int)new FileInfo(fileCache).Length)); + } + + private async Task UploadFile(byte[] compressedFile, string fileHash, CancellationToken uploadToken) + { + if (uploadToken.IsCancellationRequested) return; + + async IAsyncEnumerable AsyncFileData([EnumeratorCancellation] CancellationToken token) + { + var chunkSize = 1024 * 512; // 512kb + using var ms = new MemoryStream(compressedFile); + var buffer = new byte[chunkSize]; + int bytesRead; + while ((bytesRead = await ms.ReadAsync(buffer, 0, chunkSize, token)) > 0 && !token.IsCancellationRequested) + { + CurrentUploads.Single(f => f.Hash == fileHash).Transferred += bytesRead; + token.ThrowIfCancellationRequested(); + yield return bytesRead == chunkSize ? buffer.ToArray() : buffer.Take(bytesRead).ToArray(); + } + } + + await _mareHub!.SendAsync(Api.SendFileUploadFileStreamAsync, fileHash, AsyncFileData(uploadToken), uploadToken); + } + + public void CancelDownload(int downloadId) + { + while (CurrentDownloads.ContainsKey(downloadId)) + { + CurrentDownloads.TryRemove(downloadId, out _); + } + } } + diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Users.cs b/MareSynchronos/WebAPI/ApIController.Functions.Users.cs index 967045f..1e0223a 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Users.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Users.cs @@ -1,44 +1,43 @@ using System.Collections.Generic; using System.Threading.Tasks; using MareSynchronos.API; -using MareSynchronos.Utils; using Microsoft.AspNetCore.SignalR.Client; -namespace MareSynchronos.WebAPI +namespace MareSynchronos.WebAPI; + +public partial class ApiController { - public partial class ApiController + public async Task DeleteAccount() { - public async Task DeleteAccount() - { - _pluginConfiguration.ClientSecret.Remove(ApiUri); - _pluginConfiguration.Save(); - await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles); - await _mareHub!.SendAsync(Api.SendUserDeleteAccount); - await CreateConnections(); - } - - public async Task> GetOnlineCharacters() - { - return await _mareHub!.InvokeAsync>(Api.InvokeUserGetOnlineCharacters); - } - - public async Task SendPairedClientAddition(string uid) - { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendUserPairedClientAddition, uid); - } - - public async Task SendPairedClientPauseChange(string uid, bool paused) - { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendUserPairedClientPauseChange, uid, paused); - } - - public async Task SendPairedClientRemoval(string uid) - { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendUserPairedClientRemoval, uid); - } + _pluginConfiguration.ClientSecret.Remove(ApiUri); + _pluginConfiguration.Save(); + await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles); + await _mareHub!.SendAsync(Api.SendUserDeleteAccount); + await CreateConnections(); } + public async Task> GetOnlineCharacters() + { + return await _mareHub!.InvokeAsync>(Api.InvokeUserGetOnlineCharacters); + } + + public async Task SendPairedClientAddition(string uid) + { + if (!IsConnected || SecretKey == "-") return; + await _mareHub!.SendAsync(Api.SendUserPairedClientAddition, uid); + } + + public async Task SendPairedClientPauseChange(string uid, bool paused) + { + if (!IsConnected || SecretKey == "-") return; + await _mareHub!.SendAsync(Api.SendUserPairedClientPauseChange, uid, paused); + } + + public async Task SendPairedClientRemoval(string uid) + { + if (!IsConnected || SecretKey == "-") return; + await _mareHub!.SendAsync(Api.SendUserPairedClientRemoval, uid); + } } + + diff --git a/MareSynchronos/WebAPI/ApiController.Connectivity.cs b/MareSynchronos/WebAPI/ApiController.Connectivity.cs deleted file mode 100644 index 6088d99..0000000 --- a/MareSynchronos/WebAPI/ApiController.Connectivity.cs +++ /dev/null @@ -1,372 +0,0 @@ -using System; -using System.Collections.Concurrent; -using System.Collections.Generic; -using System.Linq; -using System.Net.Http; -using System.Threading; -using System.Threading.Tasks; -using MareSynchronos.API; -using MareSynchronos.FileCache; -using MareSynchronos.Utils; -using MareSynchronos.WebAPI.Utils; -using Microsoft.AspNetCore.Http.Connections; -using Microsoft.AspNetCore.SignalR; -using Microsoft.AspNetCore.SignalR.Client; -using Microsoft.Extensions.Logging; - -namespace MareSynchronos.WebAPI -{ - public delegate void SimpleStringDelegate(string str); - public enum ServerState - { - Offline, - Disconnected, - Connected, - Unauthorized, - VersionMisMatch, - RateLimited - } - - public partial class ApiController : IDisposable - { - public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)"; - public const string MainServiceUri = "wss://maresynchronos.com"; - - public readonly int[] SupportedServerVersions = { Api.Version }; - - private readonly Configuration _pluginConfiguration; - private readonly DalamudUtil _dalamudUtil; - private readonly FileCacheManager _fileDbManager; - private CancellationTokenSource _connectionCancellationTokenSource; - - private HubConnection? _mareHub; - - private CancellationTokenSource? _uploadCancellationTokenSource = new(); - - private ConnectionDto? _connectionDto; - public SystemInfoDto SystemInfoDto { get; private set; } = new(); - public bool IsModerator => (_connectionDto?.IsAdmin ?? false) || (_connectionDto?.IsModerator ?? false); - - public bool IsAdmin => _connectionDto?.IsAdmin ?? false; - - public ApiController(Configuration pluginConfiguration, DalamudUtil dalamudUtil, FileCacheManager fileDbManager) - { - Logger.Verbose("Creating " + nameof(ApiController)); - - _pluginConfiguration = pluginConfiguration; - _dalamudUtil = dalamudUtil; - _fileDbManager = fileDbManager; - _connectionCancellationTokenSource = new CancellationTokenSource(); - _dalamudUtil.LogIn += DalamudUtilOnLogIn; - _dalamudUtil.LogOut += DalamudUtilOnLogOut; - ServerState = ServerState.Offline; - _verifiedUploadedHashes = new(); - - if (_dalamudUtil.IsLoggedIn) - { - DalamudUtilOnLogIn(); - } - } - - private void DalamudUtilOnLogOut() - { - Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token)); - ServerState = ServerState.Offline; - } - - private void DalamudUtilOnLogIn() - { - Task.Run(CreateConnections); - } - - - public event EventHandler? CharacterReceived; - - public event VoidDelegate? Connected; - - public event VoidDelegate? Disconnected; - - public event SimpleStringDelegate? PairedClientOffline; - - public event SimpleStringDelegate? PairedClientOnline; - - public event SimpleStringDelegate? PairedWithOther; - - public event SimpleStringDelegate? UnpairedFromOther; - public event VoidDelegate? DownloadStarted; - public event VoidDelegate? DownloadFinished; - - public ConcurrentDictionary> CurrentDownloads { get; } = new(); - - public List CurrentUploads { get; } = new(); - - public List ForbiddenTransfers { get; } = new(); - - public List AdminBannedUsers { get; private set; } = new(); - - public List AdminForbiddenFiles { get; private set; } = new(); - - public bool IsConnected => ServerState == ServerState.Connected; - - public bool IsDownloading => CurrentDownloads.Count > 0; - - public bool IsUploading => CurrentUploads.Count > 0; - - public List PairedClients { get; set; } = new(); - - public string SecretKey => _pluginConfiguration.ClientSecret.ContainsKey(ApiUri) - ? _pluginConfiguration.ClientSecret[ApiUri] : string.Empty; - - public bool ServerAlive => ServerState is ServerState.Connected or ServerState.RateLimited or ServerState.Unauthorized or ServerState.Disconnected; - - public Dictionary ServerDictionary => new Dictionary() - { { MainServiceUri, MainServer } } - .Concat(_pluginConfiguration.CustomServerList) - .ToDictionary(k => k.Key, k => k.Value); - - public string UID => _connectionDto?.UID ?? string.Empty; - private string ApiUri => _pluginConfiguration.ApiUri; - public int OnlineUsers => SystemInfoDto.OnlineUsers; - - private ServerState _serverState; - public ServerState ServerState - { - get => _serverState; - private set - { - Logger.Debug($"New ServerState: {value}, prev ServerState: {_serverState}"); - _serverState = value; - } - } - - public async Task CreateConnections() - { - Logger.Debug("CreateConnections called"); - - if (_pluginConfiguration.FullPause) - { - Logger.Info("Not recreating Connection, paused"); - ServerState = ServerState.Disconnected; - _connectionDto = null; - await StopConnection(_connectionCancellationTokenSource.Token); - return; - } - - await StopConnection(_connectionCancellationTokenSource.Token); - - Logger.Info("Recreating Connection"); - - _connectionCancellationTokenSource.Cancel(); - _connectionCancellationTokenSource = new CancellationTokenSource(); - var token = _connectionCancellationTokenSource.Token; - _verifiedUploadedHashes.Clear(); - while (ServerState is not ServerState.Connected && !token.IsCancellationRequested) - { - if (string.IsNullOrEmpty(SecretKey)) - { - await Task.Delay(TimeSpan.FromSeconds(2)); - continue; - } - - await StopConnection(token); - - try - { - Logger.Debug("Building connection"); - - while (!_dalamudUtil.IsPlayerPresent && !token.IsCancellationRequested) - { - Logger.Debug("Player not loaded in yet, waiting"); - await Task.Delay(TimeSpan.FromSeconds(1), token); - } - - if (token.IsCancellationRequested) break; - - _mareHub = BuildHubConnection(Api.Path); - - await _mareHub.StartAsync(token); - - _mareHub.On(Api.OnUpdateSystemInfo, (dto) => SystemInfoDto = dto); - - _connectionDto = - await _mareHub.InvokeAsync(Api.InvokeHeartbeat, _dalamudUtil.PlayerNameHashed, token); - - ServerState = ServerState.Connected; - - if (_connectionDto.ServerVersion != Api.Version) - { - ServerState = ServerState.VersionMisMatch; - await StopConnection(token); - return; - } - - if (ServerState is ServerState.Connected) // user is authorized && server is legit - { - await InitializeData(token); - - _mareHub.Closed += MareHubOnClosed; - _mareHub.Reconnecting += MareHubOnReconnecting; - _mareHub.Reconnected += MareHubOnReconnected; - } - } - catch (HubException ex) - { - Logger.Warn(ex.GetType().ToString()); - Logger.Warn(ex.Message); - Logger.Warn(ex.StackTrace ?? string.Empty); - - ServerState = ServerState.RateLimited; - await StopConnection(token); - return; - } - catch (HttpRequestException ex) - { - Logger.Warn(ex.GetType().ToString()); - Logger.Warn(ex.Message); - Logger.Warn(ex.StackTrace ?? string.Empty); - - if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) - { - ServerState = ServerState.Unauthorized; - await StopConnection(token); - return; - } - else - { - ServerState = ServerState.Offline; - Logger.Info("Failed to establish connection, retrying"); - await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token); - } - } - catch (Exception ex) - { - Logger.Warn(ex.GetType().ToString()); - Logger.Warn(ex.Message); - Logger.Warn(ex.StackTrace ?? string.Empty); - Logger.Info("Failed to establish connection, retrying"); - await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token); - } - } - } - - private Task MareHubOnReconnected(string? arg) - { - _ = Task.Run(CreateConnections); - return Task.CompletedTask; - } - - private async Task InitializeData(CancellationToken token) - { - if (_mareHub == null) return; - - Logger.Debug("Initializing data"); - _mareHub.On(Api.OnUserUpdateClientPairs, - UpdateLocalClientPairsCallback); - _mareHub.On(Api.OnUserReceiveCharacterData, - ReceiveCharacterDataCallback); - _mareHub.On(Api.OnUserRemoveOnlinePairedPlayer, - (s) => PairedClientOffline?.Invoke(s)); - _mareHub.On(Api.OnUserAddOnlinePairedPlayer, - (s) => PairedClientOnline?.Invoke(s)); - _mareHub.On(Api.OnAdminForcedReconnect, UserForcedReconnectCallback); - - PairedClients = - await _mareHub!.InvokeAsync>(Api.InvokeUserGetPairedClients, token); - - if (IsModerator) - { - AdminForbiddenFiles = - await _mareHub.InvokeAsync>(Api.InvokeAdminGetForbiddenFiles, - token); - AdminBannedUsers = - await _mareHub.InvokeAsync>(Api.InvokeAdminGetBannedUsers, - token); - _mareHub.On(Api.OnAdminUpdateOrAddBannedUser, - UpdateOrAddBannedUserCallback); - _mareHub.On(Api.OnAdminDeleteBannedUser, DeleteBannedUserCallback); - _mareHub.On(Api.OnAdminUpdateOrAddForbiddenFile, - UpdateOrAddForbiddenFileCallback); - _mareHub.On(Api.OnAdminDeleteForbiddenFile, - DeleteForbiddenFileCallback); - } - - Connected?.Invoke(); - } - - public void Dispose() - { - Logger.Verbose("Disposing " + nameof(ApiController)); - - _dalamudUtil.LogIn -= DalamudUtilOnLogIn; - _dalamudUtil.LogOut -= DalamudUtilOnLogOut; - - Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token)); - _connectionCancellationTokenSource?.Cancel(); - } - - private HubConnection BuildHubConnection(string hubName) - { - return new HubConnectionBuilder() - .WithUrl(ApiUri + hubName, options => - { - options.Headers.Add("Authorization", SecretKey); - options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling; - }) - .WithAutomaticReconnect(new ForeverRetryPolicy()) - .ConfigureLogging(a => { - a.ClearProviders().AddProvider(new DalamudLoggingProvider()); - a.SetMinimumLevel(LogLevel.Warning); - }) - .Build(); - } - - private Task MareHubOnClosed(Exception? arg) - { - CurrentUploads.Clear(); - CurrentDownloads.Clear(); - _uploadCancellationTokenSource?.Cancel(); - Disconnected?.Invoke(); - ServerState = ServerState.Offline; - Logger.Info("Connection closed"); - return Task.CompletedTask; - } - - private Task MareHubOnReconnecting(Exception? arg) - { - ServerState = ServerState.Disconnected; - Logger.Warn("Connection closed... Reconnecting"); - Logger.Warn(arg?.Message ?? string.Empty); - Logger.Warn(arg?.StackTrace ?? string.Empty); - Disconnected?.Invoke(); - ServerState = ServerState.Offline; - return Task.CompletedTask; - } - - private async Task StopConnection(CancellationToken token) - { - if (_mareHub is not null) - { - _uploadCancellationTokenSource?.Cancel(); - Logger.Info("Stopping existing connection"); - _mareHub.Closed -= MareHubOnClosed; - _mareHub.Reconnecting -= MareHubOnReconnecting; - _mareHub.Reconnected -= MareHubOnReconnected; - await _mareHub.StopAsync(token); - await _mareHub.DisposeAsync(); - CurrentUploads.Clear(); - CurrentDownloads.Clear(); - _uploadCancellationTokenSource?.Cancel(); - Disconnected?.Invoke(); - _mareHub = null; - } - - if (ServerState != ServerState.Disconnected) - { - while (ServerState != ServerState.Offline) - { - await Task.Delay(16); - } - } - } - } -} diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs b/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs index 868c4e9..33720c4 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs @@ -3,45 +3,44 @@ using System.Threading.Tasks; using MareSynchronos.API; using Microsoft.AspNetCore.SignalR.Client; -namespace MareSynchronos.WebAPI +namespace MareSynchronos.WebAPI; + +public partial class ApiController { - public partial class ApiController + public async Task AddOrUpdateForbiddenFileEntry(ForbiddenFileDto forbiddenFile) { - public async Task AddOrUpdateForbiddenFileEntry(ForbiddenFileDto forbiddenFile) - { - await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile); - } + await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile); + } - public async Task DeleteForbiddenFileEntry(ForbiddenFileDto forbiddenFile) - { - await _mareHub!.SendAsync(Api.SendAdminDeleteForbiddenFile, forbiddenFile); - } + public async Task DeleteForbiddenFileEntry(ForbiddenFileDto forbiddenFile) + { + await _mareHub!.SendAsync(Api.SendAdminDeleteForbiddenFile, forbiddenFile); + } - public async Task AddOrUpdateBannedUserEntry(BannedUserDto bannedUser) - { - await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddBannedUser, bannedUser); - } + public async Task AddOrUpdateBannedUserEntry(BannedUserDto bannedUser) + { + await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddBannedUser, bannedUser); + } - public async Task DeleteBannedUserEntry(BannedUserDto bannedUser) - { - await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser); - } + public async Task DeleteBannedUserEntry(BannedUserDto bannedUser) + { + await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser); + } - public async Task RefreshOnlineUsers() - { - AdminOnlineUsers = await _mareHub!.InvokeAsync>(Api.InvokeAdminGetOnlineUsers); - } + public async Task RefreshOnlineUsers() + { + AdminOnlineUsers = await _mareHub!.InvokeAsync>(Api.InvokeAdminGetOnlineUsers); + } - public List AdminOnlineUsers { get; set; } = new List(); + public List AdminOnlineUsers { get; set; } = new List(); - public void PromoteToModerator(string onlineUserUID) - { - _mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, true); - } + public void PromoteToModerator(string onlineUserUID) + { + _mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, true); + } - public void DemoteFromModerator(string onlineUserUID) - { - _mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false); - } + public void DemoteFromModerator(string onlineUserUID) + { + _mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false); } } diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs index e565e71..1109b35 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs @@ -4,75 +4,84 @@ using MareSynchronos.API; using MareSynchronos.Utils; using MareSynchronos.WebAPI.Utils; -namespace MareSynchronos.WebAPI +namespace MareSynchronos.WebAPI; + +public partial class ApiController { - public partial class ApiController + private void UserForcedReconnectCallback() { - private void UserForcedReconnectCallback() + _ = CreateConnections(); + } + + private void UpdateLocalClientPairsCallback(ClientPairDto dto) + { + var entry = PairedClients.SingleOrDefault(e => e.OtherUID == dto.OtherUID); + if (dto.IsRemoved) { - _ = CreateConnections(); + PairedClients.RemoveAll(p => p.OtherUID == dto.OtherUID); + return; + } + if (entry == null) + { + PairedClients.Add(dto); + return; } - private void UpdateLocalClientPairsCallback(ClientPairDto dto) - { - var entry = PairedClients.SingleOrDefault(e => e.OtherUID == dto.OtherUID); - if (dto.IsRemoved) - { - PairedClients.RemoveAll(p => p.OtherUID == dto.OtherUID); - return; - } - if (entry == null) - { - PairedClients.Add(dto); - return; - } + entry.IsPaused = dto.IsPaused; + entry.IsPausedFromOthers = dto.IsPausedFromOthers; + entry.IsSynced = dto.IsSynced; + } - entry.IsPaused = dto.IsPaused; - entry.IsPausedFromOthers = dto.IsPausedFromOthers; - entry.IsSynced = dto.IsSynced; + private Task ReceiveCharacterDataCallback(CharacterCacheDto character, string characterHash) + { + Logger.Verbose("Received DTO for " + characterHash); + CharacterReceived?.Invoke(null, new CharacterReceivedEventArgs(characterHash, character)); + return Task.CompletedTask; + } + + private void UpdateOrAddBannedUserCallback(BannedUserDto obj) + { + var user = AdminBannedUsers.SingleOrDefault(b => b.CharacterHash == obj.CharacterHash); + if (user == null) + { + AdminBannedUsers.Add(obj); } - - private Task ReceiveCharacterDataCallback(CharacterCacheDto character, string characterHash) + else { - Logger.Verbose("Received DTO for " + characterHash); - CharacterReceived?.Invoke(null, new CharacterReceivedEventArgs(characterHash, character)); - return Task.CompletedTask; - } - - private void UpdateOrAddBannedUserCallback(BannedUserDto obj) - { - var user = AdminBannedUsers.SingleOrDefault(b => b.CharacterHash == obj.CharacterHash); - if (user == null) - { - AdminBannedUsers.Add(obj); - } - else - { - user.Reason = obj.Reason; - } - } - - private void DeleteBannedUserCallback(BannedUserDto obj) - { - AdminBannedUsers.RemoveAll(a => a.CharacterHash == obj.CharacterHash); - } - - private void UpdateOrAddForbiddenFileCallback(ForbiddenFileDto obj) - { - var user = AdminForbiddenFiles.SingleOrDefault(b => b.Hash == obj.Hash); - if (user == null) - { - AdminForbiddenFiles.Add(obj); - } - else - { - user.ForbiddenBy = obj.ForbiddenBy; - } - } - - private void DeleteForbiddenFileCallback(ForbiddenFileDto obj) - { - AdminForbiddenFiles.RemoveAll(f => f.Hash == obj.Hash); + user.Reason = obj.Reason; } } + + private void DeleteBannedUserCallback(BannedUserDto obj) + { + AdminBannedUsers.RemoveAll(a => a.CharacterHash == obj.CharacterHash); + } + + private void UpdateOrAddForbiddenFileCallback(ForbiddenFileDto obj) + { + var user = AdminForbiddenFiles.SingleOrDefault(b => b.Hash == obj.Hash); + if (user == null) + { + AdminForbiddenFiles.Add(obj); + } + else + { + user.ForbiddenBy = obj.ForbiddenBy; + } + } + + private void DeleteForbiddenFileCallback(ForbiddenFileDto obj) + { + AdminForbiddenFiles.RemoveAll(f => f.Hash == obj.Hash); + } + + private void GroupPairChangedCallback(GroupPairDto dto) + { + + } + + private void GroupChangedCallback(GroupDto dto) + { + + } } diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs b/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs new file mode 100644 index 0000000..618c7bf --- /dev/null +++ b/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs @@ -0,0 +1,64 @@ +using MareSynchronos.API; +using Microsoft.AspNetCore.SignalR.Client; +using System.Collections.Generic; +using System.Threading.Tasks; + +namespace MareSynchronos.WebAPI; +public partial class ApiController +{ + public async Task CreateGroup() + { + return await _mareHub!.InvokeAsync(Api.InvokeGroupCreate); + } + + public async Task ChangeGroupPassword(string gid, string newpassword) + { + return await _mareHub!.InvokeAsync(Api.InvokeGroupChangePassword, gid, newpassword); + } + + public async Task> GetGroups() + { + return await _mareHub!.InvokeAsync>(Api.InvokeGroupGetGroups); + } + + public async Task> GetUsersInGroup(string gid) + { + return await _mareHub!.InvokeAsync>(Api.InvokeGroupGetUsersInGroup, gid); + } + + public async Task SendGroupJoin(string gid, string password) + { + if (!IsConnected || SecretKey == "-") return; + await _mareHub!.SendAsync(Api.SendGroupJoin, gid, password); + } + + public async Task SendGroupChangeInviteState(string gid, bool opened) + { + await _mareHub!.SendAsync(Api.SendGroupChangeInviteState, gid, opened); + } + + public async Task SendDeleteGroup(string gid) + { + await _mareHub!.SendAsync(Api.SendGroupDelete, gid); + } + + public async Task SendLeaveGroup(string gid) + { + await _mareHub!.SendAsync(Api.SendGroupLeave, gid); + } + + public async Task SendPauseGroup(string gid, bool isPaused) + { + await _mareHub!.SendAsync(Api.SendGroupPause, gid, isPaused); + } + + public async Task SendRemoveUserFromGroup(string gid, string uid) + { + await _mareHub!.SendAsync(Api.SendGroupRemoveUser, gid, uid); + } + + public async Task ChangeOwnerOfGroup(string gid, string uid) + { + await _mareHub!.SendAsync(Api.SendGroupChangeOwner, gid, uid); + } +} \ No newline at end of file diff --git a/MareSynchronos/WebAPI/ApiController.cs b/MareSynchronos/WebAPI/ApiController.cs new file mode 100644 index 0000000..f0e6481 --- /dev/null +++ b/MareSynchronos/WebAPI/ApiController.cs @@ -0,0 +1,372 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Linq; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using MareSynchronos.API; +using MareSynchronos.FileCache; +using MareSynchronos.Utils; +using MareSynchronos.WebAPI.Utils; +using Microsoft.AspNetCore.Http.Connections; +using Microsoft.AspNetCore.SignalR; +using Microsoft.AspNetCore.SignalR.Client; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.WebAPI; + +public delegate void SimpleStringDelegate(string str); + +public partial class ApiController : IDisposable +{ + public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)"; + public const string MainServiceUri = "wss://maresynchronos.com"; + + public readonly int[] SupportedServerVersions = { Api.Version }; + + private readonly Configuration _pluginConfiguration; + private readonly DalamudUtil _dalamudUtil; + private readonly FileCacheManager _fileDbManager; + private CancellationTokenSource _connectionCancellationTokenSource; + + private HubConnection? _mareHub; + + private CancellationTokenSource? _uploadCancellationTokenSource = new(); + + private ConnectionDto? _connectionDto; + public SystemInfoDto SystemInfoDto { get; private set; } = new(); + public bool IsModerator => (_connectionDto?.IsAdmin ?? false) || (_connectionDto?.IsModerator ?? false); + + public bool IsAdmin => _connectionDto?.IsAdmin ?? false; + + public ApiController(Configuration pluginConfiguration, DalamudUtil dalamudUtil, FileCacheManager fileDbManager) + { + Logger.Verbose("Creating " + nameof(ApiController)); + + _pluginConfiguration = pluginConfiguration; + _dalamudUtil = dalamudUtil; + _fileDbManager = fileDbManager; + _connectionCancellationTokenSource = new CancellationTokenSource(); + _dalamudUtil.LogIn += DalamudUtilOnLogIn; + _dalamudUtil.LogOut += DalamudUtilOnLogOut; + ServerState = ServerState.Offline; + _verifiedUploadedHashes = new(); + + if (_dalamudUtil.IsLoggedIn) + { + DalamudUtilOnLogIn(); + } + } + + private void DalamudUtilOnLogOut() + { + Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token)); + ServerState = ServerState.Offline; + } + + private void DalamudUtilOnLogIn() + { + Task.Run(CreateConnections); + } + + + public event EventHandler? CharacterReceived; + + public event VoidDelegate? Connected; + + public event VoidDelegate? Disconnected; + + public event SimpleStringDelegate? PairedClientOffline; + + public event SimpleStringDelegate? PairedClientOnline; + + public event SimpleStringDelegate? PairedWithOther; + + public event SimpleStringDelegate? UnpairedFromOther; + public event VoidDelegate? DownloadStarted; + public event VoidDelegate? DownloadFinished; + + public ConcurrentDictionary> CurrentDownloads { get; } = new(); + + public List CurrentUploads { get; } = new(); + + public List ForbiddenTransfers { get; } = new(); + + public List AdminBannedUsers { get; private set; } = new(); + + public List AdminForbiddenFiles { get; private set; } = new(); + + public bool IsConnected => ServerState == ServerState.Connected; + + public bool IsDownloading => CurrentDownloads.Count > 0; + + public bool IsUploading => CurrentUploads.Count > 0; + + public List PairedClients { get; set; } = new(); + public List GroupPairedClients { get; set; } = new(); + public List Groups { get; set; } = new(); + + public string SecretKey => _pluginConfiguration.ClientSecret.ContainsKey(ApiUri) + ? _pluginConfiguration.ClientSecret[ApiUri] : string.Empty; + + public bool ServerAlive => ServerState is ServerState.Connected or ServerState.RateLimited or ServerState.Unauthorized or ServerState.Disconnected; + + public Dictionary ServerDictionary => new Dictionary() + { { MainServiceUri, MainServer } } + .Concat(_pluginConfiguration.CustomServerList) + .ToDictionary(k => k.Key, k => k.Value); + + public string UID => _connectionDto?.UID ?? string.Empty; + private string ApiUri => _pluginConfiguration.ApiUri; + public int OnlineUsers => SystemInfoDto.OnlineUsers; + + private ServerState _serverState; + public ServerState ServerState + { + get => _serverState; + private set + { + Logger.Debug($"New ServerState: {value}, prev ServerState: {_serverState}"); + _serverState = value; + } + } + + public async Task CreateConnections() + { + Logger.Debug("CreateConnections called"); + + if (_pluginConfiguration.FullPause) + { + Logger.Info("Not recreating Connection, paused"); + ServerState = ServerState.Disconnected; + _connectionDto = null; + await StopConnection(_connectionCancellationTokenSource.Token); + return; + } + + await StopConnection(_connectionCancellationTokenSource.Token); + + Logger.Info("Recreating Connection"); + + _connectionCancellationTokenSource.Cancel(); + _connectionCancellationTokenSource = new CancellationTokenSource(); + var token = _connectionCancellationTokenSource.Token; + _verifiedUploadedHashes.Clear(); + while (ServerState is not ServerState.Connected && !token.IsCancellationRequested) + { + if (string.IsNullOrEmpty(SecretKey)) + { + await Task.Delay(TimeSpan.FromSeconds(2)); + continue; + } + + await StopConnection(token); + + try + { + Logger.Debug("Building connection"); + + while (!_dalamudUtil.IsPlayerPresent && !token.IsCancellationRequested) + { + Logger.Debug("Player not loaded in yet, waiting"); + await Task.Delay(TimeSpan.FromSeconds(1), token); + } + + if (token.IsCancellationRequested) break; + + _mareHub = BuildHubConnection(Api.Path); + + await _mareHub.StartAsync(token); + + _mareHub.On(Api.OnUpdateSystemInfo, (dto) => SystemInfoDto = dto); + + _connectionDto = + await _mareHub.InvokeAsync(Api.InvokeHeartbeat, _dalamudUtil.PlayerNameHashed, token); + + ServerState = ServerState.Connected; + + if (_connectionDto.ServerVersion != Api.Version) + { + ServerState = ServerState.VersionMisMatch; + await StopConnection(token); + return; + } + + if (ServerState is ServerState.Connected) // user is authorized && server is legit + { + await InitializeData(token); + + _mareHub.Closed += MareHubOnClosed; + _mareHub.Reconnecting += MareHubOnReconnecting; + _mareHub.Reconnected += MareHubOnReconnected; + } + } + catch (HubException ex) + { + Logger.Warn(ex.GetType().ToString()); + Logger.Warn(ex.Message); + Logger.Warn(ex.StackTrace ?? string.Empty); + + ServerState = ServerState.RateLimited; + await StopConnection(token); + return; + } + catch (HttpRequestException ex) + { + Logger.Warn(ex.GetType().ToString()); + Logger.Warn(ex.Message); + Logger.Warn(ex.StackTrace ?? string.Empty); + + if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) + { + ServerState = ServerState.Unauthorized; + await StopConnection(token); + return; + } + else + { + ServerState = ServerState.Offline; + Logger.Info("Failed to establish connection, retrying"); + await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token); + } + } + catch (Exception ex) + { + Logger.Warn(ex.GetType().ToString()); + Logger.Warn(ex.Message); + Logger.Warn(ex.StackTrace ?? string.Empty); + Logger.Info("Failed to establish connection, retrying"); + await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token); + } + } + } + + private Task MareHubOnReconnected(string? arg) + { + _ = Task.Run(CreateConnections); + return Task.CompletedTask; + } + + private async Task InitializeData(CancellationToken token) + { + if (_mareHub == null) return; + + Logger.Debug("Initializing data"); + _mareHub.On(Api.OnUserUpdateClientPairs, + UpdateLocalClientPairsCallback); + _mareHub.On(Api.OnUserReceiveCharacterData, + ReceiveCharacterDataCallback); + _mareHub.On(Api.OnUserRemoveOnlinePairedPlayer, + (s) => PairedClientOffline?.Invoke(s)); + _mareHub.On(Api.OnUserAddOnlinePairedPlayer, + (s) => PairedClientOnline?.Invoke(s)); + _mareHub.On(Api.OnAdminForcedReconnect, UserForcedReconnectCallback); + _mareHub.On(Api.OnGroupChange, GroupChangedCallback); + _mareHub.On(Api.OnGroupUserChange, GroupPairChangedCallback); + + PairedClients = + await _mareHub!.InvokeAsync>(Api.InvokeUserGetPairedClients, token); + Groups = await GetGroups(); + foreach (var group in Groups) + { + GroupPairedClients.AddRange(await GetUsersInGroup(group.GID)); + } + + if (IsModerator) + { + AdminForbiddenFiles = + await _mareHub.InvokeAsync>(Api.InvokeAdminGetForbiddenFiles, + token); + AdminBannedUsers = + await _mareHub.InvokeAsync>(Api.InvokeAdminGetBannedUsers, + token); + _mareHub.On(Api.OnAdminUpdateOrAddBannedUser, + UpdateOrAddBannedUserCallback); + _mareHub.On(Api.OnAdminDeleteBannedUser, DeleteBannedUserCallback); + _mareHub.On(Api.OnAdminUpdateOrAddForbiddenFile, + UpdateOrAddForbiddenFileCallback); + _mareHub.On(Api.OnAdminDeleteForbiddenFile, + DeleteForbiddenFileCallback); + } + + Connected?.Invoke(); + } + + public void Dispose() + { + Logger.Verbose("Disposing " + nameof(ApiController)); + + _dalamudUtil.LogIn -= DalamudUtilOnLogIn; + _dalamudUtil.LogOut -= DalamudUtilOnLogOut; + + Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token)); + _connectionCancellationTokenSource?.Cancel(); + } + + private HubConnection BuildHubConnection(string hubName) + { + return new HubConnectionBuilder() + .WithUrl(ApiUri + hubName, options => + { + options.Headers.Add("Authorization", SecretKey); + options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling; + }) + .WithAutomaticReconnect(new ForeverRetryPolicy()) + .ConfigureLogging(a => + { + a.ClearProviders().AddProvider(new DalamudLoggingProvider()); + a.SetMinimumLevel(LogLevel.Warning); + }) + .Build(); + } + + private Task MareHubOnClosed(Exception? arg) + { + CurrentUploads.Clear(); + CurrentDownloads.Clear(); + _uploadCancellationTokenSource?.Cancel(); + Disconnected?.Invoke(); + ServerState = ServerState.Offline; + Logger.Info("Connection closed"); + return Task.CompletedTask; + } + + private Task MareHubOnReconnecting(Exception? arg) + { + ServerState = ServerState.Disconnected; + Logger.Warn("Connection closed... Reconnecting"); + Logger.Warn(arg?.Message ?? string.Empty); + Logger.Warn(arg?.StackTrace ?? string.Empty); + Disconnected?.Invoke(); + ServerState = ServerState.Offline; + return Task.CompletedTask; + } + + private async Task StopConnection(CancellationToken token) + { + if (_mareHub is not null) + { + _uploadCancellationTokenSource?.Cancel(); + Logger.Info("Stopping existing connection"); + _mareHub.Closed -= MareHubOnClosed; + _mareHub.Reconnecting -= MareHubOnReconnecting; + _mareHub.Reconnected -= MareHubOnReconnected; + await _mareHub.StopAsync(token); + await _mareHub.DisposeAsync(); + CurrentUploads.Clear(); + CurrentDownloads.Clear(); + _uploadCancellationTokenSource?.Cancel(); + Disconnected?.Invoke(); + _mareHub = null; + } + + if (ServerState != ServerState.Disconnected) + { + while (ServerState != ServerState.Offline) + { + await Task.Delay(16); + } + } + } +} diff --git a/MareSynchronos/WebAPI/ServerState.cs b/MareSynchronos/WebAPI/ServerState.cs new file mode 100644 index 0000000..d146f26 --- /dev/null +++ b/MareSynchronos/WebAPI/ServerState.cs @@ -0,0 +1,11 @@ +namespace MareSynchronos.WebAPI; + +public enum ServerState +{ + Offline, + Disconnected, + Connected, + Unauthorized, + VersionMisMatch, + RateLimited +} From d2bb4e01a4a3692116c1d9b60f40de6dcf376cc6 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Thu, 29 Sep 2022 16:01:25 +0200 Subject: [PATCH 03/17] implement callbacks --- MareAPI | 2 +- .../WebAPI/ApiController.Functions.Admin.cs | 3 +- .../ApiController.Functions.Callbacks.cs | 29 +++++++++++++++++++ 3 files changed, 32 insertions(+), 2 deletions(-) diff --git a/MareAPI b/MareAPI index 57a7ab8..008f886 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit 57a7ab82625ca350bce0442c60f457e5e5e172c3 +Subproject commit 008f88674347b5230b860366017eb3ec157b2090 diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs b/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs index 33720c4..c301460 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs @@ -1,4 +1,5 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; using System.Threading.Tasks; using MareSynchronos.API; using Microsoft.AspNetCore.SignalR.Client; diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs index 1109b35..ac04131 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs @@ -77,11 +77,40 @@ public partial class ApiController private void GroupPairChangedCallback(GroupPairDto dto) { + if (dto.IsRemoved.GetValueOrDefault(false)) + { + GroupPairedClients.RemoveAll(g => g.GroupGID == dto.GroupGID && g.UserUID == dto.UserUID); + } + var existingUser = GroupPairedClients.FirstOrDefault(f => f.GroupGID == dto.GroupGID && f.UserUID == dto.UserUID); + if (existingUser == null) + { + GroupPairedClients.Add(dto); + return; + } + + existingUser.IsPaused = dto.IsPaused ?? existingUser.IsPaused; + existingUser.UserAlias = dto.UserAlias ?? existingUser.UserAlias; } private void GroupChangedCallback(GroupDto dto) { + if (dto.IsDeleted.GetValueOrDefault(false)) + { + Groups.RemoveAll(g => g.GID == dto.GID); + GroupPairedClients.RemoveAll(g => g.GroupGID == dto.GID); + return; + } + var existingGroup = Groups.FirstOrDefault(g => g.GID == dto.GID); + if (existingGroup == null) + { + Groups.Add(dto); + return; + } + + existingGroup.OwnedBy = dto.OwnedBy ?? existingGroup.OwnedBy; + existingGroup.InvitesEnabled = dto.InvitesEnabled ?? existingGroup.InvitesEnabled; + existingGroup.IsPaused = dto.IsPaused ?? existingGroup.IsPaused; } } From f6d1309d591ab62a367d94bb5eb3e459b061b1ca Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Thu, 29 Sep 2022 16:27:04 +0200 Subject: [PATCH 04/17] add test commands --- MareSynchronos/Plugin.cs | 41 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index fbee90d..573bfcc 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -14,6 +14,7 @@ using MareSynchronos.UI; using MareSynchronos.Utils; using Dalamud.Game.ClientState.Conditions; using MareSynchronos.FileCache; +using Dalamud.Logging; namespace MareSynchronos; @@ -205,6 +206,46 @@ public sealed class Plugin : IDalamudPlugin { OpenUi(); } + + var parsed = args.Split(" ", StringSplitOptions.RemoveEmptyEntries); + + if (parsed[0] == "creategroup") + { + var groupDto = _apiController.CreateGroup().Result; + PluginLog.Debug("GID:" + groupDto.GID); + PluginLog.Debug("PW:" + groupDto.Password); + } + + if (parsed[0] == "joingroup" && parsed.Length == 3) + { + _ = _apiController.SendGroupJoin(parsed[1], parsed[2]); + } + + if (parsed[0] == "listgroups") + { + foreach (var grp in _apiController.Groups) + { + PluginLog.Debug("GID: " + grp.GID); + PluginLog.Debug("OwnedBy: " + grp.OwnedBy); + PluginLog.Debug("Alias: " + grp.Alias); + PluginLog.Debug("Invites Enabled: " + grp.InvitesEnabled); + PluginLog.Debug("IsPaused: " + grp.IsPaused); + PluginLog.Debug("==="); + } + } + + if (parsed[0] == "listgroupusers") + { + foreach (var grp in _apiController.GroupPairedClients) + { + PluginLog.Debug("GID: " + grp.GroupGID); + PluginLog.Debug("UID: " + grp.UserUID); + PluginLog.Debug("Alias: " + grp.UserAlias); + PluginLog.Debug("IsPaused: " + grp.IsPaused); + PluginLog.Debug("==="); + } + + } } private void OpenUi() From 2ca284546d690cd26b5690c3afa64ef0f8d83860 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Fri, 30 Sep 2022 01:18:57 +0200 Subject: [PATCH 05/17] rudimentary implementation and ui for syncshells --- MareAPI | 2 +- MareSynchronos/Plugin.cs | 40 -- MareSynchronos/UI/CompactUI.cs | 374 +++++++++++++++++- .../ApiController.Functions.Callbacks.cs | 8 +- .../WebAPI/ApiController.Functions.Groups.cs | 17 +- MareSynchronos/WebAPI/ApiController.cs | 2 + 6 files changed, 386 insertions(+), 57 deletions(-) diff --git a/MareAPI b/MareAPI index 008f886..cfd0ba4 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit 008f88674347b5230b860366017eb3ec157b2090 +Subproject commit cfd0ba4c234b89667b11b5eefa83c9fc55baca10 diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 573bfcc..5918753 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -206,46 +206,6 @@ public sealed class Plugin : IDalamudPlugin { OpenUi(); } - - var parsed = args.Split(" ", StringSplitOptions.RemoveEmptyEntries); - - if (parsed[0] == "creategroup") - { - var groupDto = _apiController.CreateGroup().Result; - PluginLog.Debug("GID:" + groupDto.GID); - PluginLog.Debug("PW:" + groupDto.Password); - } - - if (parsed[0] == "joingroup" && parsed.Length == 3) - { - _ = _apiController.SendGroupJoin(parsed[1], parsed[2]); - } - - if (parsed[0] == "listgroups") - { - foreach (var grp in _apiController.Groups) - { - PluginLog.Debug("GID: " + grp.GID); - PluginLog.Debug("OwnedBy: " + grp.OwnedBy); - PluginLog.Debug("Alias: " + grp.Alias); - PluginLog.Debug("Invites Enabled: " + grp.InvitesEnabled); - PluginLog.Debug("IsPaused: " + grp.IsPaused); - PluginLog.Debug("==="); - } - } - - if (parsed[0] == "listgroupusers") - { - foreach (var grp in _apiController.GroupPairedClients) - { - PluginLog.Debug("GID: " + grp.GroupGID); - PluginLog.Debug("UID: " + grp.UserUID); - PluginLog.Debug("Alias: " + grp.UserAlias); - PluginLog.Debug("IsPaused: " + grp.IsPaused); - PluginLog.Debug("==="); - } - - } } private void OpenUi() diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index cdc9758..8f8c37b 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -1,9 +1,11 @@ using System; using System.Collections.Generic; +using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Numerics; using System.Reflection; +using System.Threading.Tasks; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; @@ -28,6 +30,8 @@ public class CompactUi : Window, IDisposable private string _editCharComment = string.Empty; private string _editNickEntry = string.Empty; private string _pairToAdd = string.Empty; + private string _syncShellPassword = string.Empty; + private string _syncShellToJoin = string.Empty; private readonly Stopwatch _timeout = new(); private bool _buttonState; @@ -35,6 +39,15 @@ public class CompactUi : Window, IDisposable private float _windowContentWidth = 0; + private bool _showModalEnterPassword; + private bool _showModalCreateGroup; + private bool _showModalChangePassword; + private string _newSyncShellPassword = string.Empty; + private bool _isPasswordValid; + private bool _errorGroupJoin; + private bool _errorGroupCreate = false; + private GroupCreatedDto? _lastCreatedGroup = null; + public CompactUi(WindowSystem windowSystem, UiShared uiShared, Configuration configuration, ApiController apiController) : base("###MareSynchronosMainUI") { @@ -89,7 +102,20 @@ public class CompactUi : Window, IDisposable if (_apiController.ServerState is ServerState.Connected) { ImGui.Separator(); - UiShared.DrawWithID("pairlist", DrawPairList); + if (ImGui.BeginTabBar("maintabs")) + { + if (ImGui.BeginTabItem("Individuals")) + { + UiShared.DrawWithID("pairlist", DrawPairList); + ImGui.EndTabItem(); + } + if (ImGui.BeginTabItem("Syncshells")) + { + UiShared.DrawWithID("synchshells", DrawSyncshells); + ImGui.EndTabItem(); + } + ImGui.EndTabBar(); + } ImGui.Separator(); UiShared.DrawWithID("transfers", DrawTransfers); _transferPartHeight = ImGui.GetCursorPosY() - _transferPartHeight; @@ -144,23 +170,23 @@ public class CompactUi : Window, IDisposable UiShared.AttachToolTip("Sort by oldest additions first"); } ImGui.SameLine(); - + var users = GetFilteredUsers().ToList(); var userCount = users.Count; - + var spacing = userCount > 0 ? playButtonSize.X + ImGui.GetStyle().ItemSpacing.X * 2 : ImGui.GetStyle().ItemSpacing.X; - + ImGui.SetNextItemWidth(_windowContentWidth - buttonSize.X - spacing); ImGui.InputTextWithHint("##filter", "Filter for UID/notes", ref _characterOrCommentFilter, 255); if (userCount == 0) return; ImGui.SameLine(); - + var pausedUsers = users.Where(u => u.IsPaused).ToList(); var resumedUsers = users.Where(u => !u.IsPaused).ToList(); - + switch (_buttonState) { case true when !pausedUsers.Any(): @@ -176,13 +202,13 @@ public class CompactUi : Window, IDisposable users = resumedUsers; break; } - + var button = _buttonState ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; - + if (!_timeout.IsRunning || _timeout.ElapsedMilliseconds > 15000) { _timeout.Reset(); - + if (ImGuiComponents.IconButton(button)) { if (UiShared.CtrlPressed()) @@ -192,7 +218,7 @@ public class CompactUi : Window, IDisposable { _ = _apiController.SendPairedClientPauseChange(entry.OtherUID, !entry.IsPaused); } - + _timeout.Start(); _buttonState = !_buttonState; } @@ -319,7 +345,6 @@ public class CompactUi : Window, IDisposable if (UiShared.CtrlPressed()) { _ = _apiController.SendPairedClientRemoval(entry.OtherUID); - _apiController.PairedClients.Remove(entry); } } UiShared.AttachToolTip("Hold CTRL and click to unpair permanently from " + entryUID); @@ -338,6 +363,13 @@ public class CompactUi : Window, IDisposable } } + private void DrawSyncshells() + { + UiShared.DrawWithID("addsyncshell", DrawAddSyncshell); + UiShared.DrawWithID("syncshells", DrawSyncshellList); + _transferPartHeight = ImGui.GetCursorPosY(); + } + private void DrawPairList() { UiShared.DrawWithID("addpair", DrawAddPair); @@ -363,6 +395,326 @@ public class CompactUi : Window, IDisposable ImGui.EndChild(); } + private void DrawAddSyncshell() + { + var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); + ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X); + ImGui.InputTextWithHint("##otheruid", "Syncshell ID", ref _syncShellToJoin, 20); + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) + { + if (_apiController.Groups.All(w => w.GID != _syncShellToJoin) && !string.IsNullOrEmpty(_syncShellToJoin)) + { + _errorGroupJoin = false; + _showModalEnterPassword = true; + ImGui.OpenPopup("Enter Syncshell Password"); + } + else + { + _lastCreatedGroup = null; + _errorGroupCreate = false; + _showModalCreateGroup = true; + ImGui.OpenPopup("Create Syncshell"); + } + } + UiShared.AttachToolTip(_syncShellToJoin.IsNullOrEmpty() ? "Create Syncshell" : "Join Syncshell" + _syncShellToJoin); + + if (ImGui.BeginPopupModal("Enter Syncshell Password", ref _showModalEnterPassword, ImGuiWindowFlags.AlwaysAutoResize)) + { + UiShared.TextWrapped("Before joining any Syncshells please be aware that you will be automatically paired with everyone in the Syncshell."); + ImGui.Separator(); + UiShared.TextWrapped("Enter the password for Syncshell " + _syncShellToJoin + ":"); + ImGui.InputTextWithHint("##password", _syncShellToJoin + " Password", ref _syncShellPassword, 255, ImGuiInputTextFlags.Password); + if (_errorGroupJoin) + { + UiShared.ColorTextWrapped("An error occured during joining of this Syncshell: you either have joined the maximum amount of Syncshells (6), it does not exist, the password you entered is wrong, you already joined the Syncshell, the Syncshell is full (100 users) or the Syncshell has closed invites.", + new Vector4(1, 0, 0, 1)); + } + if (ImGui.Button("Join " + _syncShellToJoin)) + { + var shell = _syncShellToJoin; + var pw = _syncShellPassword; + _errorGroupJoin = !_apiController.SendGroupJoin(shell, pw).Result; + if (!_errorGroupJoin) + { + _syncShellToJoin = string.Empty; + _showModalEnterPassword = false; + } + _syncShellPassword = string.Empty; + } + ImGui.EndPopup(); + } + + if (ImGui.BeginPopupModal("Create Syncshell", ref _showModalCreateGroup)) + { + ImGui.SetWindowSize(new(400, 200)); + UiShared.TextWrapped("Press the button below to create a new Syncshell."); + ImGui.SetNextItemWidth(200); + if (ImGui.Button("Create Syncshell")) + { + try + { + _lastCreatedGroup = _apiController.CreateGroup().Result; + } + catch + { + _errorGroupCreate = true; + } + } + + if (_lastCreatedGroup != null) + { + ImGui.Separator(); + _errorGroupCreate = false; + ImGui.TextUnformatted("Syncshell ID: " + _lastCreatedGroup.GID); + ImGui.TextUnformatted("Syncshell Password: " + _lastCreatedGroup.Password); + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) + { + ImGui.SetClipboardText(_lastCreatedGroup.Password); + } + UiShared.TextWrapped("You can change the Syncshell password later at any time."); + } + + if (_errorGroupCreate) + { + UiShared.ColorTextWrapped("You are already owner of the maximum amount of Syncshells (3) or joined the maximum amount of Syncshells (6). Relinquish ownership of your own Syncshells to someone else or leave existing Syncshells.", + new Vector4(1, 0, 0, 1)); + } + + ImGui.EndPopup(); + } + + + ImGuiHelpers.ScaledDummy(2); + } + + private void DrawSyncshellList() + { + var ySize = _transferPartHeight == 0 + ? 1 + : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _transferPartHeight - ImGui.GetCursorPosY(); + ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), false); + foreach (var entry in _apiController.Groups.ToList()) + { + UiShared.DrawWithID(entry.GID, () => DrawSyncshell(entry)); + } + ImGui.EndChild(); + } + + private void DrawSyncshell(GroupDto entry) + { + var name = entry.Alias ?? entry.GID; + var pairsInGroup = _apiController.GroupPairedClients.Where(p => p.GroupGID == entry.GID).ToList(); + if (ImGui.CollapsingHeader(name + " (" + (pairsInGroup.Count + 1) + " users)", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth)) + { + UiShared.DrawWithID(entry.GID + "settings", () => DrawSyncshellHeader(entry, name)); + pairsInGroup = pairsInGroup.OrderBy(p => p.UserUID == entry.OwnedBy ? 0 : 1).ThenBy(p => p.UserAlias ?? p.UserUID).ToList(); + foreach (var pair in pairsInGroup) + { + ImGui.Indent(20); + UiShared.DrawWithID(entry.GID + pair.UserUID, () => DrawSyncshellPairedClient(pair)); + ImGui.Unindent(20); + } + } + } + + private void DrawSyncshellHeader(GroupDto entry, string name) + { + bool invitesEnabled = entry.InvitesEnabled ?? true; + var lockedIcon = invitesEnabled ? FontAwesomeIcon.LockOpen : FontAwesomeIcon.Lock; + + ImGui.PushFont(UiBuilder.IconFont); + ImGui.Text(FontAwesomeIcon.Crown.ToIconString()); + ImGui.PopFont(); + ImGui.SameLine(); + UiShared.TextWrapped("Syncshell owner: " + entry.OwnedBy ?? string.Empty); + if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowCircleLeft)) + { + if (UiShared.CtrlPressed()) + { + _ = _apiController.SendLeaveGroup(entry.GID); + } + } + UiShared.AttachToolTip("Hold CTRL and click to leave this Syncshell"); + ImGui.SameLine(); + var pauseIcon = (entry.IsPaused ?? false) ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; + if (ImGuiComponents.IconButton(pauseIcon)) + { + _ = _apiController.SendPauseGroup(entry.GID, !entry.IsPaused ?? false); + } + UiShared.AttachToolTip(((entry.IsPaused ?? false) ? "Resume" : "Pause") + " pairing with all users in this Syncshell"); + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) + { + ImGui.SetClipboardText(entry.Alias ?? entry.GID); + } + UiShared.AttachToolTip("Copy Syncshell ID to Clipboard"); + if (entry.OwnedBy == _apiController.UID) + { + ImGui.SameLine(); + if (ImGuiComponents.IconButton(lockedIcon)) + { + _ = _apiController.SendGroupChangeInviteState(entry.GID, !entry.InvitesEnabled ?? true); + } + UiShared.AttachToolTip("Change Syncshell invite state, invites currently " + (invitesEnabled ? "open" : "closed")); + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Passport)) + { + ImGui.OpenPopup("Change Syncshell Password"); + _isPasswordValid = true; + _showModalChangePassword = true; + } + UiShared.AttachToolTip("Change Syncshell Password"); + + if (ImGui.BeginPopupModal("Change Syncshell Password", ref _showModalChangePassword, ImGuiWindowFlags.AlwaysAutoResize)) + { + UiShared.TextWrapped("Enter the new Syncshell password for Syncshell " + name + " here."); + UiShared.TextWrapped("This action is irreversible"); + ImGui.InputTextWithHint("##changepw", "New password for " + name, ref _newSyncShellPassword, 255); + if (ImGui.Button("Change password")) + { + var pw = _newSyncShellPassword; + _isPasswordValid = _apiController.ChangeGroupPassword(entry.GID, pw).Result; + _newSyncShellPassword = string.Empty; + if (_isPasswordValid) _showModalChangePassword = false; + } + + if (!_isPasswordValid) + { + UiShared.ColorTextWrapped("The selected password is too short. It must be at least 10 characters.", new Vector4(1, 0, 0, 1)); + } + + ImGui.EndPopup(); + } + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) + { + if (UiShared.CtrlPressed()) + { + _ = _apiController.SendDeleteGroup(entry.GID); + } + } + UiShared.AttachToolTip("Hold CTRL and click to delete this Syncshell. WARNING: this action is irreversible."); + } + else + { + ImGui.SameLine(); + ImGui.SameLine(); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.Text(lockedIcon.ToIconString()); + ImGui.PopFont(); + UiShared.AttachToolTip(invitesEnabled ? "Group is open for new joiners" : "Group is closed for new joiners"); + } + } + + private void DrawSyncshellPairedClient(GroupPairDto entry) + { + var plusButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); + var entryUID = string.IsNullOrEmpty(entry.UserAlias) ? entry.UserUID : entry.UserAlias; + var textSize = ImGui.CalcTextSize(entryUID); + var originalY = ImGui.GetCursorPosY(); + var buttonSizes = plusButtonSize.Y; + + var textPos = originalY + plusButtonSize.Y / 2 - textSize.Y / 2; + ImGui.SetCursorPosY(textPos); + if (entry.IsPaused ?? false) + { + ImGui.PushFont(UiBuilder.IconFont); + UiShared.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow); + ImGui.PopFont(); + + UiShared.AttachToolTip("Pairing status with " + entryUID + " is paused"); + } + else + { + ImGui.PushFont(UiBuilder.IconFont); + UiShared.ColorText(FontAwesomeIcon.Check.ToIconString(), ImGuiColors.ParsedGreen); + ImGui.PopFont(); + + UiShared.AttachToolTip("You are paired with " + entryUID); + } + + var textIsUid = true; + _showUidForEntry.TryGetValue(entry.UserUID, out var showUidInsteadOfName); + if (!showUidInsteadOfName && _configuration.GetCurrentServerUidComments().TryGetValue(entry.UserUID, out var playerText)) + { + if (string.IsNullOrEmpty(playerText)) + { + playerText = entryUID; + } + else + { + textIsUid = false; + } + } + else + { + playerText = entryUID; + } + + ImGui.SameLine(); + if (_editNickEntry != entry.UserUID) + { + ImGui.SetCursorPosY(textPos); + if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted(playerText); + if (textIsUid) ImGui.PopFont(); + UiShared.AttachToolTip("Left click to switch between UID display and nick" + Environment.NewLine + + "Right click to change nick for " + entryUID); + if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) + { + var prevState = textIsUid; + if (_showUidForEntry.ContainsKey(entry.UserUID)) + { + prevState = _showUidForEntry[entry.UserUID]; + } + + _showUidForEntry[entry.UserUID] = !prevState; + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configuration.SetCurrentServerUidComment(_editNickEntry, _editCharComment); + _configuration.Save(); + _editCharComment = _configuration.GetCurrentServerUidComments().ContainsKey(entry.UserUID) + ? _configuration.GetCurrentServerUidComments()[entry.UserUID] + : string.Empty; + _editNickEntry = entry.UserUID; + } + } + else + { + ImGui.SetCursorPosY(originalY); + + ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * 2); + if (ImGui.InputTextWithHint("", "Nick/Notes", ref _editCharComment, 255, ImGuiInputTextFlags.EnterReturnsTrue)) + { + _configuration.SetCurrentServerUidComment(entry.UserUID, _editCharComment); + _configuration.Save(); + _editNickEntry = string.Empty; + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _editNickEntry = string.Empty; + } + UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); + } + + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - plusButtonSize.X); + ImGui.SetCursorPosY(originalY); + if (!_apiController.PairedClients.Any(p => p.OtherUID == entry.UserUID)) + { + if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) + { + _ = _apiController.SendPairedClientAddition(entry.UserUID); + } + UiShared.AttachToolTip("Pair with " + entryUID + " individually"); + } + } + private IEnumerable GetFilteredUsers() { return _apiController.PairedClients.Where(p => diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs index ac04131..1562351 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs @@ -1,8 +1,10 @@ -using System.Linq; +using System.Collections.Generic; +using System.Linq; using System.Threading.Tasks; using MareSynchronos.API; using MareSynchronos.Utils; using MareSynchronos.WebAPI.Utils; +using Microsoft.AspNetCore.SignalR.Client; namespace MareSynchronos.WebAPI; @@ -80,6 +82,7 @@ public partial class ApiController if (dto.IsRemoved.GetValueOrDefault(false)) { GroupPairedClients.RemoveAll(g => g.GroupGID == dto.GroupGID && g.UserUID == dto.UserUID); + return; } var existingUser = GroupPairedClients.FirstOrDefault(f => f.GroupGID == dto.GroupGID && f.UserUID == dto.UserUID); @@ -93,7 +96,7 @@ public partial class ApiController existingUser.UserAlias = dto.UserAlias ?? existingUser.UserAlias; } - private void GroupChangedCallback(GroupDto dto) + private async Task GroupChangedCallback(GroupDto dto) { if (dto.IsDeleted.GetValueOrDefault(false)) { @@ -106,6 +109,7 @@ public partial class ApiController if (existingGroup == null) { Groups.Add(dto); + GroupPairedClients.AddRange(await _mareHub!.InvokeAsync>(Api.InvokeGroupGetUsersInGroup, dto.GID)); return; } diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs b/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs index 618c7bf..c54c2ac 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs @@ -1,4 +1,5 @@ using MareSynchronos.API; +using MareSynchronos.Utils; using Microsoft.AspNetCore.SignalR.Client; using System.Collections.Generic; using System.Threading.Tasks; @@ -8,57 +9,67 @@ public partial class ApiController { public async Task CreateGroup() { + if (!IsConnected || SecretKey == "-") return new GroupCreatedDto(); return await _mareHub!.InvokeAsync(Api.InvokeGroupCreate); } public async Task ChangeGroupPassword(string gid, string newpassword) { + if (!IsConnected || SecretKey == "-") return false; return await _mareHub!.InvokeAsync(Api.InvokeGroupChangePassword, gid, newpassword); } public async Task> GetGroups() { + if (!IsConnected || SecretKey == "-") return new List(); return await _mareHub!.InvokeAsync>(Api.InvokeGroupGetGroups); } public async Task> GetUsersInGroup(string gid) { + if (!IsConnected || SecretKey == "-") return new List(); return await _mareHub!.InvokeAsync>(Api.InvokeGroupGetUsersInGroup, gid); } - public async Task SendGroupJoin(string gid, string password) + public async Task SendGroupJoin(string gid, string password) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendGroupJoin, gid, password); + if (!IsConnected || SecretKey == "-") return false; + return await _mareHub!.InvokeAsync(Api.InvokeGroupJoin, gid, password); } public async Task SendGroupChangeInviteState(string gid, bool opened) { + if (!IsConnected || SecretKey == "-") return; await _mareHub!.SendAsync(Api.SendGroupChangeInviteState, gid, opened); } public async Task SendDeleteGroup(string gid) { + if (!IsConnected || SecretKey == "-") return; await _mareHub!.SendAsync(Api.SendGroupDelete, gid); } public async Task SendLeaveGroup(string gid) { + if (!IsConnected || SecretKey == "-") return; await _mareHub!.SendAsync(Api.SendGroupLeave, gid); } public async Task SendPauseGroup(string gid, bool isPaused) { + if (!IsConnected || SecretKey == "-") return; await _mareHub!.SendAsync(Api.SendGroupPause, gid, isPaused); } public async Task SendRemoveUserFromGroup(string gid, string uid) { + if (!IsConnected || SecretKey == "-") return; await _mareHub!.SendAsync(Api.SendGroupRemoveUser, gid, uid); } public async Task ChangeOwnerOfGroup(string gid, string uid) { + if (!IsConnected || SecretKey == "-") return; await _mareHub!.SendAsync(Api.SendGroupChangeOwner, gid, uid); } } \ No newline at end of file diff --git a/MareSynchronos/WebAPI/ApiController.cs b/MareSynchronos/WebAPI/ApiController.cs index f0e6481..e153559 100644 --- a/MareSynchronos/WebAPI/ApiController.cs +++ b/MareSynchronos/WebAPI/ApiController.cs @@ -118,6 +118,7 @@ public partial class ApiController : IDisposable .ToDictionary(k => k.Key, k => k.Value); public string UID => _connectionDto?.UID ?? string.Empty; + public string DisplayName => string.IsNullOrEmpty(_connectionDto?.Alias) ? (_connectionDto?.UID ?? string.Empty) : _connectionDto.Alias; private string ApiUri => _pluginConfiguration.ApiUri; public int OnlineUsers => SystemInfoDto.OnlineUsers; @@ -268,6 +269,7 @@ public partial class ApiController : IDisposable PairedClients = await _mareHub!.InvokeAsync>(Api.InvokeUserGetPairedClients, token); Groups = await GetGroups(); + GroupPairedClients.Clear(); foreach (var group in Groups) { GroupPairedClients.AddRange(await GetUsersInGroup(group.GID)); From fb7943cdcd7502277b0cbad55e8a45b776c560f9 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sat, 1 Oct 2022 18:20:01 +0200 Subject: [PATCH 06/17] preliminary finalized UI --- MareSynchronos/Configuration.cs | 2 +- MareSynchronos/Managers/PlayerManager.cs | 20 ++- MareSynchronos/MareSynchronos.csproj | 2 +- MareSynchronos/UI/CompactUI.cs | 179 ++++++++++++++++------- MareSynchronos/UI/UIShared.cs | 6 +- 5 files changed, 149 insertions(+), 60 deletions(-) diff --git a/MareSynchronos/Configuration.cs b/MareSynchronos/Configuration.cs index d5b51ad..8416d80 100644 --- a/MareSynchronos/Configuration.cs +++ b/MareSynchronos/Configuration.cs @@ -57,7 +57,7 @@ public class Configuration : IPluginConfiguration public Dictionary ClientSecret { get; set; } = new(); public Dictionary CustomServerList { get; set; } = new(); public int MaxLocalCacheInGiB { get; set; } = 20; - public bool ReverseUserSort { get; set; } = true; + public bool ReverseUserSort { get; set; } = false; public int TimeSpanBetweenScansInSeconds { get; set; } = 30; public bool FileScanPaused { get; set; } = false; diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index 913f35e..21e2879 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -235,14 +235,22 @@ public class PlayerManager : IDisposable Task.Run(async () => { - _periodicFileScanner.HaltScan("Character creation"); - foreach (var item in unprocessedObjects) + CharacterCacheDto? cacheDto = null; + try { - _dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, 10000, token); - } + _periodicFileScanner.HaltScan("Character creation"); + foreach (var item in unprocessedObjects) + { + _dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, 10000, token); + } - CharacterCacheDto? cacheDto = (await CreateFullCharacterCacheDto(token)); - _periodicFileScanner.ResumeScan("Character creation"); + cacheDto = (await CreateFullCharacterCacheDto(token)); + } + catch { } + finally + { + _periodicFileScanner.ResumeScan("Character creation"); + } if (cacheDto == null || token.IsCancellationRequested) return; #if DEBUG diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index 00c3f0c..4a8610c 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -3,7 +3,7 @@ - 0.4.22 + 0.5.0 https://github.com/Penumbra-Sync/client diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 8f8c37b..0880def 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -47,6 +47,8 @@ public class CompactUi : Window, IDisposable private bool _errorGroupJoin; private bool _errorGroupCreate = false; private GroupCreatedDto? _lastCreatedGroup = null; + private Dictionary ExpandedGroupState = new Dictionary(); + private bool showSyncShells = false; public CompactUi(WindowSystem windowSystem, UiShared uiShared, Configuration configuration, ApiController apiController) : base("###MareSynchronosMainUI") @@ -101,20 +103,52 @@ public class CompactUi : Window, IDisposable if (_apiController.ServerState is ServerState.Connected) { - ImGui.Separator(); - if (ImGui.BeginTabBar("maintabs")) + var hasShownSyncShells = showSyncShells; + + ImGui.PushFont(UiBuilder.IconFont); + if (!hasShownSyncShells) { - if (ImGui.BeginTabItem("Individuals")) - { - UiShared.DrawWithID("pairlist", DrawPairList); - ImGui.EndTabItem(); - } - if (ImGui.BeginTabItem("Syncshells")) - { - UiShared.DrawWithID("synchshells", DrawSyncshells); - ImGui.EndTabItem(); - } - ImGui.EndTabBar(); + ImGui.PushStyleColor(ImGuiCol.Button, ImGui.GetStyle().Colors[(int)ImGuiCol.ButtonHovered]); + } + if (ImGui.Button(FontAwesomeIcon.User.ToIconString(), new Vector2((UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X) / 2, 30))) + { + showSyncShells = false; + } + if (!hasShownSyncShells) + { + ImGui.PopStyleColor(); + } + ImGui.PopFont(); + UiShared.AttachToolTip("Individual pairs"); + + ImGui.SameLine(); + + ImGui.PushFont(UiBuilder.IconFont); + if (hasShownSyncShells) + { + ImGui.PushStyleColor(ImGuiCol.Button, ImGui.GetStyle().Colors[(int)ImGuiCol.ButtonHovered]); + } + if (ImGui.Button(FontAwesomeIcon.UserFriends.ToIconString(), new Vector2((UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X) / 2, 30))) + { + showSyncShells = true; + } + if (hasShownSyncShells) + { + ImGui.PopStyleColor(); + } + ImGui.PopFont(); + + UiShared.AttachToolTip("Syncshells"); + + ImGui.Separator(); + if (!hasShownSyncShells) + { + UiShared.DrawWithID("pairlist", DrawPairList); + } + else + { + UiShared.DrawWithID("syncshells", DrawSyncshells); + } ImGui.Separator(); UiShared.DrawWithID("transfers", DrawTransfers); @@ -132,7 +166,7 @@ public class CompactUi : Window, IDisposable { var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X); - ImGui.InputTextWithHint("##otheruid", "Other players UID", ref _pairToAdd, 10); + ImGui.InputTextWithHint("##otheruid", "Other players UID/GID", ref _pairToAdd, 10); ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { @@ -158,7 +192,7 @@ public class CompactUi : Window, IDisposable _configuration.ReverseUserSort = true; _configuration.Save(); } - UiShared.AttachToolTip("Sort by newest additions first"); + UiShared.AttachToolTip("Sort by name descending"); } else { @@ -167,7 +201,7 @@ public class CompactUi : Window, IDisposable _configuration.ReverseUserSort = false; _configuration.Save(); } - UiShared.AttachToolTip("Sort by oldest additions first"); + UiShared.AttachToolTip("Sort by name ascending"); } ImGui.SameLine(); @@ -366,7 +400,7 @@ public class CompactUi : Window, IDisposable private void DrawSyncshells() { UiShared.DrawWithID("addsyncshell", DrawAddSyncshell); - UiShared.DrawWithID("syncshells", DrawSyncshellList); + UiShared.DrawWithID("syncshelllist", DrawSyncshellList); _transferPartHeight = ImGui.GetCursorPosY(); } @@ -385,6 +419,7 @@ public class CompactUi : Window, IDisposable : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _transferPartHeight - ImGui.GetCursorPosY(); var users = GetFilteredUsers(); + users = users.OrderBy(u => _configuration.GetCurrentServerUidComments().ContainsKey(u.OtherUID) ? _configuration.GetCurrentServerUidComments()[u.OtherUID] : !string.IsNullOrEmpty(u.VanityUID) ? u.VanityUID : u.OtherUID); if (_configuration.ReverseUserSort) users = users.Reverse(); ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), false); @@ -399,7 +434,7 @@ public class CompactUi : Window, IDisposable { var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X); - ImGui.InputTextWithHint("##otheruid", "Syncshell ID", ref _syncShellToJoin, 20); + ImGui.InputTextWithHint("##syncshellid", "Syncshell GID/Alias", ref _syncShellToJoin, 20); ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { @@ -495,40 +530,62 @@ public class CompactUi : Window, IDisposable ? 1 : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _transferPartHeight - ImGui.GetCursorPosY(); ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), false); - foreach (var entry in _apiController.Groups.ToList()) + foreach (var entry in _apiController.Groups.OrderBy(g => g.Alias ?? g.GID).ToList()) { UiShared.DrawWithID(entry.GID, () => DrawSyncshell(entry)); } ImGui.EndChild(); } - private void DrawSyncshell(GroupDto entry) + private void DrawSyncshell(GroupDto group) { - var name = entry.Alias ?? entry.GID; - var pairsInGroup = _apiController.GroupPairedClients.Where(p => p.GroupGID == entry.GID).ToList(); - if (ImGui.CollapsingHeader(name + " (" + (pairsInGroup.Count + 1) + " users)", ImGuiTreeNodeFlags.DefaultOpen | ImGuiTreeNodeFlags.SpanAvailWidth)) + var name = group.Alias ?? group.GID; + var pairsInGroup = _apiController.GroupPairedClients.Where(p => p.GroupGID == group.GID).ToList(); + if (!ExpandedGroupState.TryGetValue(group.GID, out bool isExpanded)) { - UiShared.DrawWithID(entry.GID + "settings", () => DrawSyncshellHeader(entry, name)); - pairsInGroup = pairsInGroup.OrderBy(p => p.UserUID == entry.OwnedBy ? 0 : 1).ThenBy(p => p.UserAlias ?? p.UserUID).ToList(); + isExpanded = false; + ExpandedGroupState.Add(group.GID, isExpanded); + } + var icon = isExpanded ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight; + var collapseButton = UiShared.GetIconButtonSize(icon); + ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(0, 0, 0, 0)); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0, 0, 0, 0)); + if (ImGuiComponents.IconButton(icon)) + { + ExpandedGroupState[group.GID] = !ExpandedGroupState[group.GID]; + } + ImGui.PopStyleColor(2); + ImGui.SameLine(); + var pauseIcon = (group.IsPaused ?? false) ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; + if (ImGuiComponents.IconButton(pauseIcon)) + { + _ = _apiController.SendPauseGroup(group.GID, !group.IsPaused ?? false); + } + UiShared.AttachToolTip(((group.IsPaused ?? false) ? "Resume" : "Pause") + " pairing with all users in this Syncshell"); + ImGui.SameLine(); + ImGui.TextUnformatted(group.Alias ?? group.GID); + UiShared.AttachToolTip("Users: " + (pairsInGroup.Count + 1) + ", Owner: " + group.OwnedBy); + + ImGui.Indent(collapseButton.X + ImGui.GetStyle().ItemSpacing.X); + if (ExpandedGroupState[group.GID]) + { + UiShared.DrawWithID(group.GID + "settings", () => DrawSyncShellButtons(group, name)); + pairsInGroup = pairsInGroup.OrderBy(p => p.UserUID == group.OwnedBy ? 0 : 1).ThenBy(p => p.UserAlias ?? p.UserUID).ToList(); foreach (var pair in pairsInGroup) { - ImGui.Indent(20); - UiShared.DrawWithID(entry.GID + pair.UserUID, () => DrawSyncshellPairedClient(pair)); - ImGui.Unindent(20); + ImGui.Indent(ImGui.GetStyle().ItemSpacing.X / 2); + UiShared.DrawWithID(group.GID + pair.UserUID, () => DrawSyncshellPairedClient(pair, group.OwnedBy == _apiController.UID, group?.IsPaused ?? false)); + ImGui.Unindent(ImGui.GetStyle().ItemSpacing.X / 2); } } + ImGui.Unindent(collapseButton.X + ImGui.GetStyle().ItemSpacing.X); } - private void DrawSyncshellHeader(GroupDto entry, string name) + private void DrawSyncShellButtons(GroupDto entry, string name) { bool invitesEnabled = entry.InvitesEnabled ?? true; var lockedIcon = invitesEnabled ? FontAwesomeIcon.LockOpen : FontAwesomeIcon.Lock; - ImGui.PushFont(UiBuilder.IconFont); - ImGui.Text(FontAwesomeIcon.Crown.ToIconString()); - ImGui.PopFont(); - ImGui.SameLine(); - UiShared.TextWrapped("Syncshell owner: " + entry.OwnedBy ?? string.Empty); if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowCircleLeft)) { if (UiShared.CtrlPressed()) @@ -537,13 +594,7 @@ public class CompactUi : Window, IDisposable } } UiShared.AttachToolTip("Hold CTRL and click to leave this Syncshell"); - ImGui.SameLine(); - var pauseIcon = (entry.IsPaused ?? false) ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; - if (ImGuiComponents.IconButton(pauseIcon)) - { - _ = _apiController.SendPauseGroup(entry.GID, !entry.IsPaused ?? false); - } - UiShared.AttachToolTip(((entry.IsPaused ?? false) ? "Resume" : "Pause") + " pairing with all users in this Syncshell"); + ImGui.SameLine(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) { @@ -557,7 +608,7 @@ public class CompactUi : Window, IDisposable { _ = _apiController.SendGroupChangeInviteState(entry.GID, !entry.InvitesEnabled ?? true); } - UiShared.AttachToolTip("Change Syncshell invite state, invites currently " + (invitesEnabled ? "open" : "closed")); + UiShared.AttachToolTip("Change Syncshell joining permissions" + Environment.NewLine + "Syncshell is currently " + (invitesEnabled ? "open" : "closed") + " for people to join"); ImGui.SameLine(); if (ImGuiComponents.IconButton(FontAwesomeIcon.Passport)) { @@ -596,7 +647,8 @@ public class CompactUi : Window, IDisposable _ = _apiController.SendDeleteGroup(entry.GID); } } - UiShared.AttachToolTip("Hold CTRL and click to delete this Syncshell. WARNING: this action is irreversible."); + UiShared.AttachToolTip("Hold CTRL and click to delete this Syncshell. WARNING: this action is irreversible." + + Environment.NewLine + "Leaving an owned Syncshell will transfer the ownership to a random person in the Syncshell."); } else { @@ -605,13 +657,15 @@ public class CompactUi : Window, IDisposable ImGui.PushFont(UiBuilder.IconFont); ImGui.Text(lockedIcon.ToIconString()); ImGui.PopFont(); - UiShared.AttachToolTip(invitesEnabled ? "Group is open for new joiners" : "Group is closed for new joiners"); + UiShared.AttachToolTip(invitesEnabled ? "Syncshell is open for new joiners" : "Syncshell is closed for new joiners"); } } - private void DrawSyncshellPairedClient(GroupPairDto entry) + private void DrawSyncshellPairedClient(GroupPairDto entry, bool isOwner, bool isPausedByYou) { var plusButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); + var trashButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Trash); + var crownButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Crown); var entryUID = string.IsNullOrEmpty(entry.UserAlias) ? entry.UserUID : entry.UserAlias; var textSize = ImGui.CalcTextSize(entryUID); var originalY = ImGui.GetCursorPosY(); @@ -619,7 +673,7 @@ public class CompactUi : Window, IDisposable var textPos = originalY + plusButtonSize.Y / 2 - textSize.Y / 2; ImGui.SetCursorPosY(textPos); - if (entry.IsPaused ?? false) + if (isPausedByYou || (entry.IsPaused ?? false)) { ImGui.PushFont(UiBuilder.IconFont); UiShared.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow); @@ -703,10 +757,37 @@ public class CompactUi : Window, IDisposable UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); } - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - plusButtonSize.X); - ImGui.SetCursorPosY(originalY); - if (!_apiController.PairedClients.Any(p => p.OtherUID == entry.UserUID)) + bool addButtonShown = !_apiController.PairedClients.Any(p => p.OtherUID == entry.UserUID); + if (isOwner) { + ImGui.SetCursorPosY(originalY); + var subtractedWidth = addButtonShown ? (ImGui.GetStyle().ItemSpacing.X + plusButtonSize.X) : 0; + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - subtractedWidth - trashButtonSize.X - ImGui.GetStyle().ItemSpacing.X - crownButtonSize.X); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Crown)) + { + if (UiShared.CtrlPressed() && UiShared.ShiftPressed()) + { + _ = _apiController.ChangeOwnerOfGroup(entry.GroupGID, entry.UserUID); + } + } + UiShared.AttachToolTip("Hold CTRL and SHIFT and click to transfer ownership of this Syncshell to " + (entry.UserAlias ?? entry.UserUID) + Environment.NewLine + "WARNING: This action is irreversible."); + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) + { + if (UiShared.CtrlPressed()) + { + _ = _apiController.SendRemoveUserFromGroup(entry.GroupGID, entry.UserUID); + } + } + UiShared.AttachToolTip("Hold CTRL and click to remove user " + (entry.UserAlias ?? entry.UserUID) + " from Syncshell"); + } + + if (addButtonShown) + { + ImGui.SetCursorPosY(originalY); + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - plusButtonSize.X); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { _ = _apiController.SendPairedClientAddition(entry.UserUID); diff --git a/MareSynchronos/UI/UIShared.cs b/MareSynchronos/UI/UIShared.cs index 7534b34..8447028 100644 --- a/MareSynchronos/UI/UIShared.cs +++ b/MareSynchronos/UI/UIShared.cs @@ -39,8 +39,8 @@ public class UiShared : IDisposable public bool EditTrackerPosition { get; set; } public ImFontPtr UidFont { get; private set; } public bool UidFontBuilt { get; private set; } - public static bool CtrlPressed() => (GetKeyState(0xA2) & 0x8000) != 0 || (GetKeyState(0xA3) & 0x8000) != 0; + public static bool ShiftPressed() => (GetKeyState(0xA1) & 0x8000) != 0 || (GetKeyState(0xA0) & 0x8000) != 0; public ApiController ApiController => _apiController; @@ -88,8 +88,8 @@ public class UiShared : IDisposable } catch (Exception ex) { - Logger.Debug($"Font failed to load. {fontFile}"); - Logger.Debug(ex.ToString()); + Logger.Warn($"Font failed to load. {fontFile}"); + Logger.Warn(ex.ToString()); } } else From a2d884718a35b27a5019821a76010a1a513b23f6 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sun, 2 Oct 2022 01:25:02 +0200 Subject: [PATCH 07/17] remove boilerplate code, change to concurrent dict --- .../FileCache/PeriodicFileScanner.cs | 5 +++-- MareSynchronos/Managers/OnlinePlayerManager.cs | 18 ------------------ MareSynchronos/WebAPI/ApiController.cs | 4 ---- 3 files changed, 3 insertions(+), 24 deletions(-) diff --git a/MareSynchronos/FileCache/PeriodicFileScanner.cs b/MareSynchronos/FileCache/PeriodicFileScanner.cs index d692f45..ce9e649 100644 --- a/MareSynchronos/FileCache/PeriodicFileScanner.cs +++ b/MareSynchronos/FileCache/PeriodicFileScanner.cs @@ -1,5 +1,6 @@ using System; using System.Collections.Concurrent; +using System.Collections.Generic; using System.IO; using System.Linq; using System.Threading; @@ -203,13 +204,13 @@ public class PeriodicFileScanner : IDisposable Logger.Debug("Getting files from " + penumbraDir + " and " + _pluginConfiguration.CacheFolder); string[] ext = { ".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".scd", ".skp" }; - var scannedFiles = Directory.EnumerateFiles(penumbraDir, "*.*", SearchOption.AllDirectories) + var scannedFiles = new ConcurrentDictionary(Directory.EnumerateFiles(penumbraDir, "*.*", SearchOption.AllDirectories) .Select(s => s.ToLowerInvariant()) .Where(f => ext.Any(e => f.EndsWith(e)) && !f.Contains(@"\bg\") && !f.Contains(@"\bgcommon\") && !f.Contains(@"\ui\")) .Concat(Directory.EnumerateFiles(_pluginConfiguration.CacheFolder, "*.*", SearchOption.TopDirectoryOnly) .Where(f => new FileInfo(f).Name.Length == 40) .Select(s => s.ToLowerInvariant()).ToList()) - .ToDictionary(c => c, c => false); + .Select(c => new KeyValuePair(c, false))); TotalFiles = scannedFiles.Count; diff --git a/MareSynchronos/Managers/OnlinePlayerManager.cs b/MareSynchronos/Managers/OnlinePlayerManager.cs index 3b99e51..efbda2b 100644 --- a/MareSynchronos/Managers/OnlinePlayerManager.cs +++ b/MareSynchronos/Managers/OnlinePlayerManager.cs @@ -37,8 +37,6 @@ public class OnlinePlayerManager : IDisposable _fileDbManager = fileDbManager; _apiController.PairedClientOnline += ApiControllerOnPairedClientOnline; _apiController.PairedClientOffline += ApiControllerOnPairedClientOffline; - _apiController.PairedWithOther += ApiControllerOnPairedWithOther; - _apiController.UnpairedFromOther += ApiControllerOnUnpairedFromOther; _apiController.Connected += ApiControllerOnConnected; _apiController.Disconnected += ApiControllerOnDisconnected; _apiController.CharacterReceived += ApiControllerOnCharacterReceived; @@ -137,8 +135,6 @@ public class OnlinePlayerManager : IDisposable _apiController.PairedClientOnline -= ApiControllerOnPairedClientOnline; _apiController.PairedClientOffline -= ApiControllerOnPairedClientOffline; - _apiController.PairedWithOther -= ApiControllerOnPairedWithOther; - _apiController.UnpairedFromOther -= ApiControllerOnUnpairedFromOther; _apiController.Disconnected -= ApiControllerOnDisconnected; _apiController.Connected -= ApiControllerOnConnected; @@ -169,20 +165,6 @@ public class OnlinePlayerManager : IDisposable return; } - private void ApiControllerOnPairedWithOther(string charHash) - { - if (string.IsNullOrEmpty(charHash)) return; - Logger.Debug("Pairing with " + charHash); - AddPlayer(charHash); - } - - private void ApiControllerOnUnpairedFromOther(string? characterHash) - { - if (string.IsNullOrEmpty(characterHash)) return; - Logger.Debug("Unpairing from " + characterHash); - RemovePlayer(characterHash); - } - private void AddPlayer(string characterNameHash) { if (_onlineCachedPlayers.TryGetValue(characterNameHash, out var cachedPlayer)) diff --git a/MareSynchronos/WebAPI/ApiController.cs b/MareSynchronos/WebAPI/ApiController.cs index e153559..ba22b0f 100644 --- a/MareSynchronos/WebAPI/ApiController.cs +++ b/MareSynchronos/WebAPI/ApiController.cs @@ -80,10 +80,6 @@ public partial class ApiController : IDisposable public event SimpleStringDelegate? PairedClientOffline; public event SimpleStringDelegate? PairedClientOnline; - - public event SimpleStringDelegate? PairedWithOther; - - public event SimpleStringDelegate? UnpairedFromOther; public event VoidDelegate? DownloadStarted; public event VoidDelegate? DownloadFinished; From 8bad3fd91c6ce2d516eb40177d2462e6bdc34bdb Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sun, 2 Oct 2022 15:17:49 +0200 Subject: [PATCH 08/17] move groups into separate class, allow setting comments for groups, add new api methods --- MareAPI | 2 +- MareSynchronos/Configuration.cs | 18 + MareSynchronos/UI/CompactUI.cs | 439 ++-------------- MareSynchronos/UI/GroupPanel.cs | 495 ++++++++++++++++++ MareSynchronos/UI/UIShared.cs | 41 ++ .../WebAPI/ApiController.Functions.Groups.cs | 12 + 6 files changed, 597 insertions(+), 410 deletions(-) create mode 100644 MareSynchronos/UI/GroupPanel.cs diff --git a/MareAPI b/MareAPI index cfd0ba4..bab758f 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit cfd0ba4c234b89667b11b5eefa83c9fc55baca10 +Subproject commit bab758f1736e266a392a7a93a47357b03b7b3e01 diff --git a/MareSynchronos/Configuration.cs b/MareSynchronos/Configuration.cs index 8416d80..5ab1507 100644 --- a/MareSynchronos/Configuration.cs +++ b/MareSynchronos/Configuration.cs @@ -26,6 +26,23 @@ public static class ConfigurationExtensions : new Dictionary(); } + public static Dictionary GetCurrentServerGidComments(this Configuration configuration) + { + return configuration.GidServerComments.ContainsKey(configuration.ApiUri) + ? configuration.GidServerComments[configuration.ApiUri] + : new Dictionary(); + } + + public static void SetCurrentServerGidComment(this Configuration configuration, string gid, string comment) + { + if (!configuration.GidServerComments.ContainsKey(configuration.ApiUri)) + { + configuration.GidServerComments[configuration.ApiUri] = new Dictionary(); + } + + configuration.GidServerComments[configuration.ApiUri][gid] = comment; + } + public static void SetCurrentServerUidComment(this Configuration configuration, string uid, string comment) { if (!configuration.UidServerComments.ContainsKey(configuration.ApiUri)) @@ -66,6 +83,7 @@ public class Configuration : IPluginConfiguration public bool FullPause { get; set; } = false; public Dictionary> UidServerComments { get; set; } = new(); + public Dictionary> GidServerComments { get; set; } = new(); public Dictionary UidComments { get; set; } = new(); public int Version { get; set; } = 5; diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 0880def..897fd88 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -1,11 +1,9 @@ using System; using System.Collections.Generic; -using System.ComponentModel.DataAnnotations; using System.Diagnostics; using System.Linq; using System.Numerics; using System.Reflection; -using System.Threading.Tasks; using Dalamud.Interface; using Dalamud.Interface.Colors; using Dalamud.Interface.Components; @@ -22,33 +20,25 @@ public class CompactUi : Window, IDisposable { private readonly ApiController _apiController; private readonly Configuration _configuration; - private readonly Dictionary _showUidForEntry = new(); + public readonly Dictionary ShowUidForEntry = new(); private readonly UiShared _uiShared; private readonly WindowSystem _windowSystem; private string _characterOrCommentFilter = string.Empty; - private string _editCharComment = string.Empty; - private string _editNickEntry = string.Empty; + public string EditUserComment = string.Empty; + public string EditNickEntry = string.Empty; + private string _pairToAdd = string.Empty; - private string _syncShellPassword = string.Empty; - private string _syncShellToJoin = string.Empty; + private readonly Stopwatch _timeout = new(); private bool _buttonState; - private float _transferPartHeight = 0; + public float TransferPartHeight = 0; + public float _windowContentWidth = 0; - private float _windowContentWidth = 0; - private bool _showModalEnterPassword; - private bool _showModalCreateGroup; - private bool _showModalChangePassword; - private string _newSyncShellPassword = string.Empty; - private bool _isPasswordValid; - private bool _errorGroupJoin; - private bool _errorGroupCreate = false; - private GroupCreatedDto? _lastCreatedGroup = null; - private Dictionary ExpandedGroupState = new Dictionary(); private bool showSyncShells = false; + private GroupPanel groupPanel; public CompactUi(WindowSystem windowSystem, UiShared uiShared, Configuration configuration, ApiController apiController) : base("###MareSynchronosMainUI") @@ -78,6 +68,8 @@ public class CompactUi : Window, IDisposable _configuration = configuration; _apiController = apiController; + groupPanel = new(this, uiShared, configuration, apiController); + SizeConstraints = new WindowSizeConstraints() { MinimumSize = new Vector2(300, 400), @@ -147,19 +139,19 @@ public class CompactUi : Window, IDisposable } else { - UiShared.DrawWithID("syncshells", DrawSyncshells); + UiShared.DrawWithID("syncshells", groupPanel.DrawSyncshells); } ImGui.Separator(); UiShared.DrawWithID("transfers", DrawTransfers); - _transferPartHeight = ImGui.GetCursorPosY() - _transferPartHeight; + TransferPartHeight = ImGui.GetCursorPosY() - TransferPartHeight; } } public override void OnClose() { - _editNickEntry = string.Empty; - _editCharComment = string.Empty; + EditNickEntry = string.Empty; + EditUserComment = string.Empty; base.OnClose(); } private void DrawAddPair() @@ -306,7 +298,7 @@ public class CompactUi : Window, IDisposable } var textIsUid = true; - _showUidForEntry.TryGetValue(entry.OtherUID, out var showUidInsteadOfName); + ShowUidForEntry.TryGetValue(entry.OtherUID, out var showUidInsteadOfName); if (!showUidInsteadOfName && _configuration.GetCurrentServerUidComments().TryGetValue(entry.OtherUID, out var playerText)) { if (string.IsNullOrEmpty(playerText)) @@ -324,7 +316,7 @@ public class CompactUi : Window, IDisposable } ImGui.SameLine(); - if (_editNickEntry != entry.OtherUID) + if (EditNickEntry != entry.OtherUID) { ImGui.SetCursorPosY(textPos); if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont); @@ -335,22 +327,22 @@ public class CompactUi : Window, IDisposable if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) { var prevState = textIsUid; - if (_showUidForEntry.ContainsKey(entry.OtherUID)) + if (ShowUidForEntry.ContainsKey(entry.OtherUID)) { - prevState = _showUidForEntry[entry.OtherUID]; + prevState = ShowUidForEntry[entry.OtherUID]; } - _showUidForEntry[entry.OtherUID] = !prevState; + ShowUidForEntry[entry.OtherUID] = !prevState; } if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { - _configuration.SetCurrentServerUidComment(_editNickEntry, _editCharComment); + _configuration.SetCurrentServerUidComment(EditNickEntry, EditUserComment); _configuration.Save(); - _editCharComment = _configuration.GetCurrentServerUidComments().ContainsKey(entry.OtherUID) + EditUserComment = _configuration.GetCurrentServerUidComments().ContainsKey(entry.OtherUID) ? _configuration.GetCurrentServerUidComments()[entry.OtherUID] : string.Empty; - _editNickEntry = entry.OtherUID; + EditNickEntry = entry.OtherUID; } } else @@ -358,16 +350,16 @@ public class CompactUi : Window, IDisposable ImGui.SetCursorPosY(originalY); ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * 2); - if (ImGui.InputTextWithHint("", "Nick/Notes", ref _editCharComment, 255, ImGuiInputTextFlags.EnterReturnsTrue)) + if (ImGui.InputTextWithHint("", "Nick/Notes", ref EditUserComment, 255, ImGuiInputTextFlags.EnterReturnsTrue)) { - _configuration.SetCurrentServerUidComment(entry.OtherUID, _editCharComment); + _configuration.SetCurrentServerUidComment(entry.OtherUID, EditUserComment); _configuration.Save(); - _editNickEntry = string.Empty; + EditNickEntry = string.Empty; } if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) { - _editNickEntry = string.Empty; + EditNickEntry = string.Empty; } UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); } @@ -397,26 +389,19 @@ public class CompactUi : Window, IDisposable } } - private void DrawSyncshells() - { - UiShared.DrawWithID("addsyncshell", DrawAddSyncshell); - UiShared.DrawWithID("syncshelllist", DrawSyncshellList); - _transferPartHeight = ImGui.GetCursorPosY(); - } - private void DrawPairList() { UiShared.DrawWithID("addpair", DrawAddPair); UiShared.DrawWithID("pairs", DrawPairs); - _transferPartHeight = ImGui.GetCursorPosY(); + TransferPartHeight = ImGui.GetCursorPosY(); UiShared.DrawWithID("filter", DrawFilter); } private void DrawPairs() { - var ySize = _transferPartHeight == 0 + var ySize = TransferPartHeight == 0 ? 1 - : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _transferPartHeight - ImGui.GetCursorPosY(); + : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - TransferPartHeight - ImGui.GetCursorPosY(); var users = GetFilteredUsers(); users = users.OrderBy(u => _configuration.GetCurrentServerUidComments().ContainsKey(u.OtherUID) ? _configuration.GetCurrentServerUidComments()[u.OtherUID] : !string.IsNullOrEmpty(u.VanityUID) ? u.VanityUID : u.OtherUID); @@ -430,371 +415,7 @@ public class CompactUi : Window, IDisposable ImGui.EndChild(); } - private void DrawAddSyncshell() - { - var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); - ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X); - ImGui.InputTextWithHint("##syncshellid", "Syncshell GID/Alias", ref _syncShellToJoin, 20); - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) - { - if (_apiController.Groups.All(w => w.GID != _syncShellToJoin) && !string.IsNullOrEmpty(_syncShellToJoin)) - { - _errorGroupJoin = false; - _showModalEnterPassword = true; - ImGui.OpenPopup("Enter Syncshell Password"); - } - else - { - _lastCreatedGroup = null; - _errorGroupCreate = false; - _showModalCreateGroup = true; - ImGui.OpenPopup("Create Syncshell"); - } - } - UiShared.AttachToolTip(_syncShellToJoin.IsNullOrEmpty() ? "Create Syncshell" : "Join Syncshell" + _syncShellToJoin); - - if (ImGui.BeginPopupModal("Enter Syncshell Password", ref _showModalEnterPassword, ImGuiWindowFlags.AlwaysAutoResize)) - { - UiShared.TextWrapped("Before joining any Syncshells please be aware that you will be automatically paired with everyone in the Syncshell."); - ImGui.Separator(); - UiShared.TextWrapped("Enter the password for Syncshell " + _syncShellToJoin + ":"); - ImGui.InputTextWithHint("##password", _syncShellToJoin + " Password", ref _syncShellPassword, 255, ImGuiInputTextFlags.Password); - if (_errorGroupJoin) - { - UiShared.ColorTextWrapped("An error occured during joining of this Syncshell: you either have joined the maximum amount of Syncshells (6), it does not exist, the password you entered is wrong, you already joined the Syncshell, the Syncshell is full (100 users) or the Syncshell has closed invites.", - new Vector4(1, 0, 0, 1)); - } - if (ImGui.Button("Join " + _syncShellToJoin)) - { - var shell = _syncShellToJoin; - var pw = _syncShellPassword; - _errorGroupJoin = !_apiController.SendGroupJoin(shell, pw).Result; - if (!_errorGroupJoin) - { - _syncShellToJoin = string.Empty; - _showModalEnterPassword = false; - } - _syncShellPassword = string.Empty; - } - ImGui.EndPopup(); - } - - if (ImGui.BeginPopupModal("Create Syncshell", ref _showModalCreateGroup)) - { - ImGui.SetWindowSize(new(400, 200)); - UiShared.TextWrapped("Press the button below to create a new Syncshell."); - ImGui.SetNextItemWidth(200); - if (ImGui.Button("Create Syncshell")) - { - try - { - _lastCreatedGroup = _apiController.CreateGroup().Result; - } - catch - { - _errorGroupCreate = true; - } - } - - if (_lastCreatedGroup != null) - { - ImGui.Separator(); - _errorGroupCreate = false; - ImGui.TextUnformatted("Syncshell ID: " + _lastCreatedGroup.GID); - ImGui.TextUnformatted("Syncshell Password: " + _lastCreatedGroup.Password); - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) - { - ImGui.SetClipboardText(_lastCreatedGroup.Password); - } - UiShared.TextWrapped("You can change the Syncshell password later at any time."); - } - - if (_errorGroupCreate) - { - UiShared.ColorTextWrapped("You are already owner of the maximum amount of Syncshells (3) or joined the maximum amount of Syncshells (6). Relinquish ownership of your own Syncshells to someone else or leave existing Syncshells.", - new Vector4(1, 0, 0, 1)); - } - - ImGui.EndPopup(); - } - - - ImGuiHelpers.ScaledDummy(2); - } - - private void DrawSyncshellList() - { - var ySize = _transferPartHeight == 0 - ? 1 - : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _transferPartHeight - ImGui.GetCursorPosY(); - ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), false); - foreach (var entry in _apiController.Groups.OrderBy(g => g.Alias ?? g.GID).ToList()) - { - UiShared.DrawWithID(entry.GID, () => DrawSyncshell(entry)); - } - ImGui.EndChild(); - } - - private void DrawSyncshell(GroupDto group) - { - var name = group.Alias ?? group.GID; - var pairsInGroup = _apiController.GroupPairedClients.Where(p => p.GroupGID == group.GID).ToList(); - if (!ExpandedGroupState.TryGetValue(group.GID, out bool isExpanded)) - { - isExpanded = false; - ExpandedGroupState.Add(group.GID, isExpanded); - } - var icon = isExpanded ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight; - var collapseButton = UiShared.GetIconButtonSize(icon); - ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(0, 0, 0, 0)); - ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0, 0, 0, 0)); - if (ImGuiComponents.IconButton(icon)) - { - ExpandedGroupState[group.GID] = !ExpandedGroupState[group.GID]; - } - ImGui.PopStyleColor(2); - ImGui.SameLine(); - var pauseIcon = (group.IsPaused ?? false) ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; - if (ImGuiComponents.IconButton(pauseIcon)) - { - _ = _apiController.SendPauseGroup(group.GID, !group.IsPaused ?? false); - } - UiShared.AttachToolTip(((group.IsPaused ?? false) ? "Resume" : "Pause") + " pairing with all users in this Syncshell"); - ImGui.SameLine(); - ImGui.TextUnformatted(group.Alias ?? group.GID); - UiShared.AttachToolTip("Users: " + (pairsInGroup.Count + 1) + ", Owner: " + group.OwnedBy); - - ImGui.Indent(collapseButton.X + ImGui.GetStyle().ItemSpacing.X); - if (ExpandedGroupState[group.GID]) - { - UiShared.DrawWithID(group.GID + "settings", () => DrawSyncShellButtons(group, name)); - pairsInGroup = pairsInGroup.OrderBy(p => p.UserUID == group.OwnedBy ? 0 : 1).ThenBy(p => p.UserAlias ?? p.UserUID).ToList(); - foreach (var pair in pairsInGroup) - { - ImGui.Indent(ImGui.GetStyle().ItemSpacing.X / 2); - UiShared.DrawWithID(group.GID + pair.UserUID, () => DrawSyncshellPairedClient(pair, group.OwnedBy == _apiController.UID, group?.IsPaused ?? false)); - ImGui.Unindent(ImGui.GetStyle().ItemSpacing.X / 2); - } - } - ImGui.Unindent(collapseButton.X + ImGui.GetStyle().ItemSpacing.X); - } - - private void DrawSyncShellButtons(GroupDto entry, string name) - { - bool invitesEnabled = entry.InvitesEnabled ?? true; - var lockedIcon = invitesEnabled ? FontAwesomeIcon.LockOpen : FontAwesomeIcon.Lock; - - if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowCircleLeft)) - { - if (UiShared.CtrlPressed()) - { - _ = _apiController.SendLeaveGroup(entry.GID); - } - } - UiShared.AttachToolTip("Hold CTRL and click to leave this Syncshell"); - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) - { - ImGui.SetClipboardText(entry.Alias ?? entry.GID); - } - UiShared.AttachToolTip("Copy Syncshell ID to Clipboard"); - if (entry.OwnedBy == _apiController.UID) - { - ImGui.SameLine(); - if (ImGuiComponents.IconButton(lockedIcon)) - { - _ = _apiController.SendGroupChangeInviteState(entry.GID, !entry.InvitesEnabled ?? true); - } - UiShared.AttachToolTip("Change Syncshell joining permissions" + Environment.NewLine + "Syncshell is currently " + (invitesEnabled ? "open" : "closed") + " for people to join"); - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Passport)) - { - ImGui.OpenPopup("Change Syncshell Password"); - _isPasswordValid = true; - _showModalChangePassword = true; - } - UiShared.AttachToolTip("Change Syncshell Password"); - - if (ImGui.BeginPopupModal("Change Syncshell Password", ref _showModalChangePassword, ImGuiWindowFlags.AlwaysAutoResize)) - { - UiShared.TextWrapped("Enter the new Syncshell password for Syncshell " + name + " here."); - UiShared.TextWrapped("This action is irreversible"); - ImGui.InputTextWithHint("##changepw", "New password for " + name, ref _newSyncShellPassword, 255); - if (ImGui.Button("Change password")) - { - var pw = _newSyncShellPassword; - _isPasswordValid = _apiController.ChangeGroupPassword(entry.GID, pw).Result; - _newSyncShellPassword = string.Empty; - if (_isPasswordValid) _showModalChangePassword = false; - } - - if (!_isPasswordValid) - { - UiShared.ColorTextWrapped("The selected password is too short. It must be at least 10 characters.", new Vector4(1, 0, 0, 1)); - } - - ImGui.EndPopup(); - } - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) - { - if (UiShared.CtrlPressed()) - { - _ = _apiController.SendDeleteGroup(entry.GID); - } - } - UiShared.AttachToolTip("Hold CTRL and click to delete this Syncshell. WARNING: this action is irreversible." - + Environment.NewLine + "Leaving an owned Syncshell will transfer the ownership to a random person in the Syncshell."); - } - else - { - ImGui.SameLine(); - ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.Text(lockedIcon.ToIconString()); - ImGui.PopFont(); - UiShared.AttachToolTip(invitesEnabled ? "Syncshell is open for new joiners" : "Syncshell is closed for new joiners"); - } - } - - private void DrawSyncshellPairedClient(GroupPairDto entry, bool isOwner, bool isPausedByYou) - { - var plusButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); - var trashButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Trash); - var crownButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Crown); - var entryUID = string.IsNullOrEmpty(entry.UserAlias) ? entry.UserUID : entry.UserAlias; - var textSize = ImGui.CalcTextSize(entryUID); - var originalY = ImGui.GetCursorPosY(); - var buttonSizes = plusButtonSize.Y; - - var textPos = originalY + plusButtonSize.Y / 2 - textSize.Y / 2; - ImGui.SetCursorPosY(textPos); - if (isPausedByYou || (entry.IsPaused ?? false)) - { - ImGui.PushFont(UiBuilder.IconFont); - UiShared.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow); - ImGui.PopFont(); - - UiShared.AttachToolTip("Pairing status with " + entryUID + " is paused"); - } - else - { - ImGui.PushFont(UiBuilder.IconFont); - UiShared.ColorText(FontAwesomeIcon.Check.ToIconString(), ImGuiColors.ParsedGreen); - ImGui.PopFont(); - - UiShared.AttachToolTip("You are paired with " + entryUID); - } - - var textIsUid = true; - _showUidForEntry.TryGetValue(entry.UserUID, out var showUidInsteadOfName); - if (!showUidInsteadOfName && _configuration.GetCurrentServerUidComments().TryGetValue(entry.UserUID, out var playerText)) - { - if (string.IsNullOrEmpty(playerText)) - { - playerText = entryUID; - } - else - { - textIsUid = false; - } - } - else - { - playerText = entryUID; - } - - ImGui.SameLine(); - if (_editNickEntry != entry.UserUID) - { - ImGui.SetCursorPosY(textPos); - if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont); - ImGui.TextUnformatted(playerText); - if (textIsUid) ImGui.PopFont(); - UiShared.AttachToolTip("Left click to switch between UID display and nick" + Environment.NewLine + - "Right click to change nick for " + entryUID); - if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) - { - var prevState = textIsUid; - if (_showUidForEntry.ContainsKey(entry.UserUID)) - { - prevState = _showUidForEntry[entry.UserUID]; - } - - _showUidForEntry[entry.UserUID] = !prevState; - } - - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - _configuration.SetCurrentServerUidComment(_editNickEntry, _editCharComment); - _configuration.Save(); - _editCharComment = _configuration.GetCurrentServerUidComments().ContainsKey(entry.UserUID) - ? _configuration.GetCurrentServerUidComments()[entry.UserUID] - : string.Empty; - _editNickEntry = entry.UserUID; - } - } - else - { - ImGui.SetCursorPosY(originalY); - - ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * 2); - if (ImGui.InputTextWithHint("", "Nick/Notes", ref _editCharComment, 255, ImGuiInputTextFlags.EnterReturnsTrue)) - { - _configuration.SetCurrentServerUidComment(entry.UserUID, _editCharComment); - _configuration.Save(); - _editNickEntry = string.Empty; - } - - if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) - { - _editNickEntry = string.Empty; - } - UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); - } - - bool addButtonShown = !_apiController.PairedClients.Any(p => p.OtherUID == entry.UserUID); - if (isOwner) - { - ImGui.SetCursorPosY(originalY); - var subtractedWidth = addButtonShown ? (ImGui.GetStyle().ItemSpacing.X + plusButtonSize.X) : 0; - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - subtractedWidth - trashButtonSize.X - ImGui.GetStyle().ItemSpacing.X - crownButtonSize.X); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Crown)) - { - if (UiShared.CtrlPressed() && UiShared.ShiftPressed()) - { - _ = _apiController.ChangeOwnerOfGroup(entry.GroupGID, entry.UserUID); - } - } - UiShared.AttachToolTip("Hold CTRL and SHIFT and click to transfer ownership of this Syncshell to " + (entry.UserAlias ?? entry.UserUID) + Environment.NewLine + "WARNING: This action is irreversible."); - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) - { - if (UiShared.CtrlPressed()) - { - _ = _apiController.SendRemoveUserFromGroup(entry.GroupGID, entry.UserUID); - } - } - UiShared.AttachToolTip("Hold CTRL and click to remove user " + (entry.UserAlias ?? entry.UserUID) + " from Syncshell"); - } - - if (addButtonShown) - { - ImGui.SetCursorPosY(originalY); - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - plusButtonSize.X); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) - { - _ = _apiController.SendPairedClientAddition(entry.UserUID); - } - UiShared.AttachToolTip("Pair with " + entryUID + " individually"); - } - } + private IEnumerable GetFilteredUsers() { diff --git a/MareSynchronos/UI/GroupPanel.cs b/MareSynchronos/UI/GroupPanel.cs new file mode 100644 index 0000000..e6981e4 --- /dev/null +++ b/MareSynchronos/UI/GroupPanel.cs @@ -0,0 +1,495 @@ +using Dalamud.Interface.Colors; +using Dalamud.Interface.Components; +using Dalamud.Interface; +using Dalamud.Utility; +using ImGuiNET; +using MareSynchronos.API; +using MareSynchronos.WebAPI; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Numerics; + +namespace MareSynchronos.UI +{ + internal class GroupPanel + { + private readonly CompactUi _mainUi; + private UiShared _uiShared; + private Configuration _configuration; + private ApiController _apiController; + + private readonly Dictionary _showGidForEntry = new(); + private string _editGroupEntry = string.Empty; + private string _editGroupComment = string.Empty; + private string _syncShellPassword = string.Empty; + private string _syncShellToJoin = string.Empty; + + private bool _showModalEnterPassword; + private bool _showModalCreateGroup; + private bool _showModalChangePassword; + private string _newSyncShellPassword = string.Empty; + private bool _isPasswordValid; + private bool _errorGroupJoin; + private bool _errorGroupCreate = false; + private GroupCreatedDto? _lastCreatedGroup = null; + private Dictionary ExpandedGroupState = new Dictionary(); + + public GroupPanel(CompactUi mainUi, UiShared uiShared, Configuration configuration, ApiController apiController) + { + _mainUi = mainUi; + _uiShared = uiShared; + _configuration = configuration; + _apiController = apiController; + } + + public void DrawSyncshells() + { + UiShared.DrawWithID("addsyncshell", DrawAddSyncshell); + UiShared.DrawWithID("syncshelllist", DrawSyncshellList); + _mainUi.TransferPartHeight = ImGui.GetCursorPosY(); + } + + private void DrawAddSyncshell() + { + var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); + ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X); + ImGui.InputTextWithHint("##syncshellid", "Syncshell GID/Alias", ref _syncShellToJoin, 20); + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) + { + if (_apiController.Groups.All(w => w.GID != _syncShellToJoin) && !string.IsNullOrEmpty(_syncShellToJoin)) + { + _errorGroupJoin = false; + _showModalEnterPassword = true; + ImGui.OpenPopup("Enter Syncshell Password"); + } + else + { + _lastCreatedGroup = null; + _errorGroupCreate = false; + _showModalCreateGroup = true; + ImGui.OpenPopup("Create Syncshell"); + } + } + UiShared.AttachToolTip(_syncShellToJoin.IsNullOrEmpty() ? "Create Syncshell" : "Join Syncshell" + _syncShellToJoin); + + if (ImGui.BeginPopupModal("Enter Syncshell Password", ref _showModalEnterPassword, ImGuiWindowFlags.AlwaysAutoResize)) + { + UiShared.TextWrapped("Before joining any Syncshells please be aware that you will be automatically paired with everyone in the Syncshell."); + ImGui.Separator(); + UiShared.TextWrapped("Enter the password for Syncshell " + _syncShellToJoin + ":"); + ImGui.InputTextWithHint("##password", _syncShellToJoin + " Password", ref _syncShellPassword, 255, ImGuiInputTextFlags.Password); + if (_errorGroupJoin) + { + UiShared.ColorTextWrapped("An error occured during joining of this Syncshell: you either have joined the maximum amount of Syncshells (6), it does not exist, the password you entered is wrong, you already joined the Syncshell, the Syncshell is full (100 users) or the Syncshell has closed invites.", + new Vector4(1, 0, 0, 1)); + } + if (ImGui.Button("Join " + _syncShellToJoin)) + { + var shell = _syncShellToJoin; + var pw = _syncShellPassword; + _errorGroupJoin = !_apiController.SendGroupJoin(shell, pw).Result; + if (!_errorGroupJoin) + { + _syncShellToJoin = string.Empty; + _showModalEnterPassword = false; + } + _syncShellPassword = string.Empty; + } + ImGui.EndPopup(); + } + + if (ImGui.BeginPopupModal("Create Syncshell", ref _showModalCreateGroup)) + { + ImGui.SetWindowSize(new(400, 200)); + UiShared.TextWrapped("Press the button below to create a new Syncshell."); + ImGui.SetNextItemWidth(200); + if (ImGui.Button("Create Syncshell")) + { + try + { + _lastCreatedGroup = _apiController.CreateGroup().Result; + } + catch + { + _errorGroupCreate = true; + } + } + + if (_lastCreatedGroup != null) + { + ImGui.Separator(); + _errorGroupCreate = false; + ImGui.TextUnformatted("Syncshell ID: " + _lastCreatedGroup.GID); + ImGui.TextUnformatted("Syncshell Password: " + _lastCreatedGroup.Password); + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) + { + ImGui.SetClipboardText(_lastCreatedGroup.Password); + } + UiShared.TextWrapped("You can change the Syncshell password later at any time."); + } + + if (_errorGroupCreate) + { + UiShared.ColorTextWrapped("You are already owner of the maximum amount of Syncshells (3) or joined the maximum amount of Syncshells (6). Relinquish ownership of your own Syncshells to someone else or leave existing Syncshells.", + new Vector4(1, 0, 0, 1)); + } + + ImGui.EndPopup(); + } + + + ImGuiHelpers.ScaledDummy(2); + } + + private void DrawSyncshellList() + { + var ySize = _mainUi.TransferPartHeight == 0 + ? 1 + : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _mainUi.TransferPartHeight - ImGui.GetCursorPosY(); + ImGui.BeginChild("list", new Vector2(_mainUi._windowContentWidth, ySize), false); + foreach (var entry in _apiController.Groups.OrderBy(g => g.Alias ?? g.GID).ToList()) + { + UiShared.DrawWithID(entry.GID, () => DrawSyncshell(entry)); + } + ImGui.EndChild(); + } + + private void DrawSyncshell(GroupDto group) + { + var name = group.Alias ?? group.GID; + var pairsInGroup = _apiController.GroupPairedClients.Where(p => p.GroupGID == group.GID).ToList(); + if (!ExpandedGroupState.TryGetValue(group.GID, out bool isExpanded)) + { + isExpanded = false; + ExpandedGroupState.Add(group.GID, isExpanded); + } + var icon = isExpanded ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight; + var collapseButton = UiShared.GetIconButtonSize(icon); + ImGui.PushStyleColor(ImGuiCol.Button, new Vector4(0, 0, 0, 0)); + ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0, 0, 0, 0)); + if (ImGuiComponents.IconButton(icon)) + { + ExpandedGroupState[group.GID] = !ExpandedGroupState[group.GID]; + } + ImGui.PopStyleColor(2); + ImGui.SameLine(); + var pauseIcon = (group.IsPaused ?? false) ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; + if (ImGuiComponents.IconButton(pauseIcon)) + { + _ = _apiController.SendPauseGroup(group.GID, !group.IsPaused ?? false); + } + UiShared.AttachToolTip(((group.IsPaused ?? false) ? "Resume" : "Pause") + " pairing with all users in this Syncshell"); + ImGui.SameLine(); + + var groupName = group.Alias ?? group.GID; + var textIsGid = true; + _showGidForEntry.TryGetValue(group.GID, out var showGidInsteadOfName); + if (!showGidInsteadOfName && _configuration.GetCurrentServerGidComments().TryGetValue(group.GID, out var groupComment)) + { + if (!string.IsNullOrEmpty(groupComment)) + { + groupName = groupComment; + textIsGid = false; + } + } + + if (_editGroupEntry != group.GID) + { + if (textIsGid) ImGui.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted(groupName); + if (textIsGid) ImGui.PopFont(); + UiShared.AttachToolTip("Left click to switch between GID display and comment" + Environment.NewLine + + "Right click to change comment for " + groupName + Environment.NewLine + Environment.NewLine + + "Users: " + (pairsInGroup.Count + 1) + ", Owner: " + group.OwnedBy); + if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) + { + var prevState = textIsGid; + if (_showGidForEntry.ContainsKey(group.GID)) + { + prevState = _showGidForEntry[group.GID]; + } + + _showGidForEntry[group.GID] = !prevState; + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configuration.SetCurrentServerGidComment(_editGroupEntry, _editGroupComment); + _configuration.Save(); + _editGroupComment = _configuration.GetCurrentServerGidComments().ContainsKey(group.GID) + ? _configuration.GetCurrentServerGidComments()[group.GID] + : string.Empty; + _editGroupEntry = group.GID; + } + } + else + { + ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetCursorPosX()); + if (ImGui.InputTextWithHint("", "Comment/Notes", ref _editGroupComment, 255, ImGuiInputTextFlags.EnterReturnsTrue)) + { + _configuration.SetCurrentServerGidComment(group.GID, _editGroupComment); + _configuration.Save(); + _editGroupEntry = string.Empty; + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _editGroupEntry = string.Empty; + } + UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); + } + + ImGui.Indent(collapseButton.X + ImGui.GetStyle().ItemSpacing.X); + if (ExpandedGroupState[group.GID]) + { + UiShared.DrawWithID(group.GID + "settings", () => DrawSyncShellButtons(group, name)); + pairsInGroup = pairsInGroup.OrderBy(p => p.UserUID == group.OwnedBy ? 0 : 1).ThenBy(p => p.IsPinned ?? false).ThenBy(p => p.UserAlias ?? p.UserUID).ToList(); + foreach (var pair in pairsInGroup) + { + ImGui.Indent(ImGui.GetStyle().ItemSpacing.X / 2); + UiShared.DrawWithID(group.GID + pair.UserUID, () => DrawSyncshellPairedClient(pair, group.OwnedBy == _apiController.UID, group?.IsPaused ?? false)); + ImGui.Unindent(ImGui.GetStyle().ItemSpacing.X / 2); + } + } + ImGui.Unindent(collapseButton.X + ImGui.GetStyle().ItemSpacing.X); + } + + private void DrawSyncShellButtons(GroupDto entry, string name) + { + bool invitesEnabled = entry.InvitesEnabled ?? true; + var lockedIcon = invitesEnabled ? FontAwesomeIcon.LockOpen : FontAwesomeIcon.Lock; + + if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowCircleLeft)) + { + if (UiShared.CtrlPressed()) + { + _ = _apiController.SendLeaveGroup(entry.GID); + } + } + UiShared.AttachToolTip("Hold CTRL and click to leave this Syncshell" + (entry.OwnedBy != _apiController.UID ? string.Empty : Environment.NewLine + + "WARNING: This action is irreverisble" + Environment.NewLine + "Leaving an owned Syncshell will transfer the ownership to a random person in the Syncshell.")); + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) + { + ImGui.SetClipboardText(entry.Alias ?? entry.GID); + } + UiShared.AttachToolTip("Copy Syncshell ID to Clipboard"); + if (entry.OwnedBy == _apiController.UID) + { + ImGui.SameLine(); + if (ImGuiComponents.IconButton(lockedIcon)) + { + _ = _apiController.SendGroupChangeInviteState(entry.GID, !entry.InvitesEnabled ?? true); + } + UiShared.AttachToolTip("Change Syncshell joining permissions" + Environment.NewLine + "Syncshell is currently " + (invitesEnabled ? "open" : "closed") + " for people to join"); + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Passport)) + { + ImGui.OpenPopup("Change Syncshell Password"); + _isPasswordValid = true; + _showModalChangePassword = true; + } + UiShared.AttachToolTip("Change Syncshell Password"); + + if (ImGui.BeginPopupModal("Change Syncshell Password", ref _showModalChangePassword, ImGuiWindowFlags.AlwaysAutoResize)) + { + UiShared.TextWrapped("Enter the new Syncshell password for Syncshell " + name + " here."); + UiShared.TextWrapped("This action is irreversible"); + ImGui.InputTextWithHint("##changepw", "New password for " + name, ref _newSyncShellPassword, 255); + if (ImGui.Button("Change password")) + { + var pw = _newSyncShellPassword; + _isPasswordValid = _apiController.ChangeGroupPassword(entry.GID, pw).Result; + _newSyncShellPassword = string.Empty; + if (_isPasswordValid) _showModalChangePassword = false; + } + + if (!_isPasswordValid) + { + UiShared.ColorTextWrapped("The selected password is too short. It must be at least 10 characters.", new Vector4(1, 0, 0, 1)); + } + + ImGui.EndPopup(); + } + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Broom)) + { + if (UiShared.CtrlPressed()) + { + _ = _apiController.SendClearGroup(entry.GID); + } + } + UiShared.AttachToolTip("Hold CTRL and click to clear this Syncshell." + Environment.NewLine + "WARNING: this action is irreversible." + Environment.NewLine + + "Clearing the Syncshell will remove all not pinned users from it."); + + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) + { + if (UiShared.CtrlPressed() && UiShared.ShiftPressed()) + { + _ = _apiController.SendDeleteGroup(entry.GID); + } + } + UiShared.AttachToolTip("Hold CTRL and Shift and click to delete this Syncshell." + Environment.NewLine + "WARNING: this action is irreversible."); + } + else + { + ImGui.SameLine(); + ImGui.SameLine(); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.Text(lockedIcon.ToIconString()); + ImGui.PopFont(); + UiShared.AttachToolTip(invitesEnabled ? "Syncshell is open for new joiners" : "Syncshell is closed for new joiners"); + } + } + + private void DrawSyncshellPairedClient(GroupPairDto entry, bool isOwner, bool isPausedByYou) + { + var plusButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); + var trashButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Trash); + var crownButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Crown); + var entryUID = string.IsNullOrEmpty(entry.UserAlias) ? entry.UserUID : entry.UserAlias; + var textSize = ImGui.CalcTextSize(entryUID); + var originalY = ImGui.GetCursorPosY(); + var buttonSizes = plusButtonSize.Y; + + var textPos = originalY + plusButtonSize.Y / 2 - textSize.Y / 2; + ImGui.SetCursorPosY(textPos); + if (isPausedByYou || (entry.IsPaused ?? false)) + { + ImGui.PushFont(UiBuilder.IconFont); + UiShared.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow); + ImGui.PopFont(); + + UiShared.AttachToolTip("Pairing status with " + entryUID + " is paused"); + } + else + { + ImGui.PushFont(UiBuilder.IconFont); + UiShared.ColorText(FontAwesomeIcon.Check.ToIconString(), ImGuiColors.ParsedGreen); + ImGui.PopFont(); + + UiShared.AttachToolTip("You are paired with " + entryUID); + } + + if (entry.IsPinned ?? false) + { + ImGui.SameLine(); + ImGui.SetCursorPosY(textPos); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.TextUnformatted(FontAwesomeIcon.Thumbtack.ToIconString()); + ImGui.PopFont(); + } + + var textIsUid = true; + _mainUi.ShowUidForEntry.TryGetValue(entry.UserUID, out var showUidInsteadOfName); + if (!showUidInsteadOfName && _configuration.GetCurrentServerUidComments().TryGetValue(entry.UserUID, out var playerText)) + { + if (string.IsNullOrEmpty(playerText)) + { + playerText = entryUID; + } + else + { + textIsUid = false; + } + } + else + { + playerText = entryUID; + } + + ImGui.SameLine(); + if (_mainUi.EditNickEntry != entry.UserUID) + { + ImGui.SetCursorPosY(textPos); + if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont); + ImGui.TextUnformatted(playerText); + if (textIsUid) ImGui.PopFont(); + UiShared.AttachToolTip("Left click to switch between UID display and nick" + Environment.NewLine + + "Right click to change nick for " + entryUID); + if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) + { + var prevState = textIsUid; + if (_mainUi.ShowUidForEntry.ContainsKey(entry.UserUID)) + { + prevState = _mainUi.ShowUidForEntry[entry.UserUID]; + } + + _mainUi.ShowUidForEntry[entry.UserUID] = !prevState; + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _configuration.SetCurrentServerUidComment(_mainUi.EditNickEntry, _mainUi.EditUserComment); + _configuration.Save(); + _mainUi.EditUserComment = _configuration.GetCurrentServerUidComments().ContainsKey(entry.UserUID) + ? _configuration.GetCurrentServerUidComments()[entry.UserUID] + : string.Empty; + _mainUi.EditNickEntry = entry.UserUID; + } + } + else + { + ImGui.SetCursorPosY(originalY); + + ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * 2); + if (ImGui.InputTextWithHint("", "Nick/Notes", ref _mainUi.EditUserComment, 255, ImGuiInputTextFlags.EnterReturnsTrue)) + { + _configuration.SetCurrentServerUidComment(entry.UserUID, _mainUi.EditUserComment); + _configuration.Save(); + _mainUi.EditNickEntry = string.Empty; + } + + if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) + { + _mainUi.EditNickEntry = string.Empty; + } + UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); + } + + bool addButtonShown = !_apiController.PairedClients.Any(p => p.OtherUID == entry.UserUID); + if (isOwner) + { + ImGui.SetCursorPosY(originalY); + var subtractedWidth = addButtonShown ? (ImGui.GetStyle().ItemSpacing.X + plusButtonSize.X) : 0; + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - subtractedWidth - trashButtonSize.X - ImGui.GetStyle().ItemSpacing.X - crownButtonSize.X); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Crown)) + { + if (UiShared.CtrlPressed() && UiShared.ShiftPressed()) + { + _ = _apiController.ChangeOwnerOfGroup(entry.GroupGID, entry.UserUID); + } + } + UiShared.AttachToolTip("Hold CTRL and SHIFT and click to transfer ownership of this Syncshell to " + (entry.UserAlias ?? entry.UserUID) + Environment.NewLine + "WARNING: This action is irreversible."); + ImGui.SameLine(); + if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) + { + if (UiShared.CtrlPressed()) + { + _ = _apiController.SendRemoveUserFromGroup(entry.GroupGID, entry.UserUID); + } + } + UiShared.AttachToolTip("Hold CTRL and click to remove user " + (entry.UserAlias ?? entry.UserUID) + " from Syncshell"); + } + + if (addButtonShown) + { + ImGui.SetCursorPosY(originalY); + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - plusButtonSize.X); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) + { + _ = _apiController.SendPairedClientAddition(entry.UserUID); + } + UiShared.AttachToolTip("Pair with " + entryUID + " individually"); + } + } + } +} diff --git a/MareSynchronos/UI/UIShared.cs b/MareSynchronos/UI/UIShared.cs index 8447028..800324e 100644 --- a/MareSynchronos/UI/UIShared.cs +++ b/MareSynchronos/UI/UIShared.cs @@ -548,6 +548,47 @@ public class UiShared : IDisposable DrawHelpText("This allows you to stop the periodic scans of your Penumbra and Mare cache directories. Use this to move the Mare cache and Penumbra mod folders around. If you enable this permanently, run a Force rescan after adding mods to Penumbra."); } + private static Vector2 GetIconSize(FontAwesomeIcon icon) + { + ImGui.PushFont(UiBuilder.IconFont); + var iconSize = ImGui.CalcTextSize(icon.ToIconString()); + ImGui.PopFont(); + return iconSize; + } + + public static bool IconTextButton(FontAwesomeIcon icon, string text) + { + var buttonClicked = false; + + var iconSize = GetIconSize(icon); + var textSize = ImGui.CalcTextSize(text); + var padding = ImGui.GetStyle().FramePadding; + var spacing = ImGui.GetStyle().ItemSpacing; + + var buttonSizeX = iconSize.X + textSize.X + padding.X * 2 + spacing.X; + var buttonSizeY = (iconSize.Y > textSize.Y ? iconSize.Y : textSize.Y) + padding.Y * 2; + var buttonSize = new Vector2(buttonSizeX, buttonSizeY); + + if (ImGui.BeginChild(icon.ToIconString() + text, buttonSize)) + { + if (ImGui.Button("", buttonSize)) + { + buttonClicked = true; + } + + ImGui.SameLine(); + ImGui.SetCursorPosX(padding.X); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.Text(icon.ToIconString()); + ImGui.PopFont(); + ImGui.SameLine(); + ImGui.Text(text); + ImGui.EndChild(); + } + + return buttonClicked; + } + public void Dispose() { _pluginInterface.UiBuilder.BuildFonts -= BuildFont; diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs b/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs index c54c2ac..40b3f07 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs @@ -49,6 +49,18 @@ public partial class ApiController await _mareHub!.SendAsync(Api.SendGroupDelete, gid); } + public async Task SendChangeUserPinned(string gid, string uid, bool isPinned) + { + if (!IsConnected || SecretKey == "-") return; + await _mareHub!.SendAsync(Api.SendGroupChangePinned, gid, uid, isPinned); + } + + public async Task SendClearGroup(string gid) + { + if (!IsConnected || SecretKey == "-") return; + await _mareHub!.SendAsync(Api.SendGroupClear, gid); + } + public async Task SendLeaveGroup(string gid) { if (!IsConnected || SecretKey == "-") return; From fd23a977ac4479a8dca182fe9e4626b2eb74583d Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Sun, 2 Oct 2022 20:32:35 +0200 Subject: [PATCH 09/17] ui adjustments --- MareSynchronos/UI/GroupPanel.cs | 230 ++++++++++-------- MareSynchronos/UI/UIShared.cs | 2 +- .../ApiController.Functions.Callbacks.cs | 1 + 3 files changed, 130 insertions(+), 103 deletions(-) diff --git a/MareSynchronos/UI/GroupPanel.cs b/MareSynchronos/UI/GroupPanel.cs index e6981e4..d31a52f 100644 --- a/MareSynchronos/UI/GroupPanel.cs +++ b/MareSynchronos/UI/GroupPanel.cs @@ -242,17 +242,21 @@ namespace MareSynchronos.UI UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); } + UiShared.DrawWithID(group.GID + "settings", () => DrawSyncShellButtons(group, name)); + ImGui.Indent(collapseButton.X + ImGui.GetStyle().ItemSpacing.X); if (ExpandedGroupState[group.GID]) { - UiShared.DrawWithID(group.GID + "settings", () => DrawSyncShellButtons(group, name)); pairsInGroup = pairsInGroup.OrderBy(p => p.UserUID == group.OwnedBy ? 0 : 1).ThenBy(p => p.IsPinned ?? false).ThenBy(p => p.UserAlias ?? p.UserUID).ToList(); + ImGui.Indent(ImGui.GetStyle().ItemSpacing.X / 2); + + ImGui.Separator(); + foreach (var pair in pairsInGroup) { - ImGui.Indent(ImGui.GetStyle().ItemSpacing.X / 2); UiShared.DrawWithID(group.GID + pair.UserUID, () => DrawSyncshellPairedClient(pair, group.OwnedBy == _apiController.UID, group?.IsPaused ?? false)); - ImGui.Unindent(ImGui.GetStyle().ItemSpacing.X / 2); } + ImGui.Unindent(ImGui.GetStyle().ItemSpacing.X / 2); } ImGui.Unindent(collapseButton.X + ImGui.GetStyle().ItemSpacing.X); } @@ -261,98 +265,105 @@ namespace MareSynchronos.UI { bool invitesEnabled = entry.InvitesEnabled ?? true; var lockedIcon = invitesEnabled ? FontAwesomeIcon.LockOpen : FontAwesomeIcon.Lock; + var iconSize = UiShared.GetIconSize(lockedIcon); + var barbuttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Bars); - if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowCircleLeft)) - { - if (UiShared.CtrlPressed()) - { - _ = _apiController.SendLeaveGroup(entry.GID); - } - } - UiShared.AttachToolTip("Hold CTRL and click to leave this Syncshell" + (entry.OwnedBy != _apiController.UID ? string.Empty : Environment.NewLine - + "WARNING: This action is irreverisble" + Environment.NewLine + "Leaving an owned Syncshell will transfer the ownership to a random person in the Syncshell.")); - + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - barbuttonSize.X - iconSize.X - ImGui.GetStyle().ItemSpacing.X); + ImGui.PushFont(UiBuilder.IconFont); + ImGui.Text(lockedIcon.ToIconString()); + ImGui.PopFont(); + UiShared.AttachToolTip(invitesEnabled ? "Syncshell is open for new joiners" : "Syncshell is closed for new joiners"); ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Copy)) + if (ImGuiComponents.IconButton(FontAwesomeIcon.Bars)) { - ImGui.SetClipboardText(entry.Alias ?? entry.GID); + ImGui.OpenPopup("ShellPopup"); } - UiShared.AttachToolTip("Copy Syncshell ID to Clipboard"); - if (entry.OwnedBy == _apiController.UID) + + if (ImGui.BeginPopup("ShellPopup")) { - ImGui.SameLine(); - if (ImGuiComponents.IconButton(lockedIcon)) - { - _ = _apiController.SendGroupChangeInviteState(entry.GID, !entry.InvitesEnabled ?? true); - } - UiShared.AttachToolTip("Change Syncshell joining permissions" + Environment.NewLine + "Syncshell is currently " + (invitesEnabled ? "open" : "closed") + " for people to join"); - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Passport)) - { - ImGui.OpenPopup("Change Syncshell Password"); - _isPasswordValid = true; - _showModalChangePassword = true; - } - UiShared.AttachToolTip("Change Syncshell Password"); - - if (ImGui.BeginPopupModal("Change Syncshell Password", ref _showModalChangePassword, ImGuiWindowFlags.AlwaysAutoResize)) - { - UiShared.TextWrapped("Enter the new Syncshell password for Syncshell " + name + " here."); - UiShared.TextWrapped("This action is irreversible"); - ImGui.InputTextWithHint("##changepw", "New password for " + name, ref _newSyncShellPassword, 255); - if (ImGui.Button("Change password")) - { - var pw = _newSyncShellPassword; - _isPasswordValid = _apiController.ChangeGroupPassword(entry.GID, pw).Result; - _newSyncShellPassword = string.Empty; - if (_isPasswordValid) _showModalChangePassword = false; - } - - if (!_isPasswordValid) - { - UiShared.ColorTextWrapped("The selected password is too short. It must be at least 10 characters.", new Vector4(1, 0, 0, 1)); - } - - ImGui.EndPopup(); - } - - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Broom)) + if (UiShared.IconTextButton(FontAwesomeIcon.ArrowCircleLeft, "Leave Syncshell")) { if (UiShared.CtrlPressed()) { - _ = _apiController.SendClearGroup(entry.GID); + _ = _apiController.SendLeaveGroup(entry.GID); } } - UiShared.AttachToolTip("Hold CTRL and click to clear this Syncshell." + Environment.NewLine + "WARNING: this action is irreversible." + Environment.NewLine - + "Clearing the Syncshell will remove all not pinned users from it."); + UiShared.AttachToolTip("Hold CTRL and click to leave this Syncshell" + (entry.OwnedBy != _apiController.UID ? string.Empty : Environment.NewLine + + "WARNING: This action is irreverisble" + Environment.NewLine + "Leaving an owned Syncshell will transfer the ownership to a random person in the Syncshell.")); - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) + if (UiShared.IconTextButton(FontAwesomeIcon.Copy, "Copy ID")) { - if (UiShared.CtrlPressed() && UiShared.ShiftPressed()) - { - _ = _apiController.SendDeleteGroup(entry.GID); - } + ImGui.SetClipboardText(entry.Alias ?? entry.GID); } - UiShared.AttachToolTip("Hold CTRL and Shift and click to delete this Syncshell." + Environment.NewLine + "WARNING: this action is irreversible."); + UiShared.AttachToolTip("Copy Syncshell ID to Clipboard"); + + if (entry.OwnedBy == _apiController.UID) + { + ImGui.Separator(); + + if (UiShared.IconTextButton(lockedIcon, invitesEnabled ? "Lock Syncshell" : "Unlock Syncshell")) + { + _ = _apiController.SendGroupChangeInviteState(entry.GID, !entry.InvitesEnabled ?? true); + } + UiShared.AttachToolTip("Change Syncshell joining permissions" + Environment.NewLine + "Syncshell is currently " + (invitesEnabled ? "open" : "closed") + " for people to join"); + + if (UiShared.IconTextButton(FontAwesomeIcon.Passport, "Change Password")) + { + ImGui.OpenPopup("Change Syncshell Password"); + _isPasswordValid = true; + _showModalChangePassword = true; + } + UiShared.AttachToolTip("Change Syncshell Password"); + + if (UiShared.IconTextButton(FontAwesomeIcon.Broom, "Clear Syncshell")) + { + if (UiShared.CtrlPressed()) + { + _ = _apiController.SendClearGroup(entry.GID); + } + } + UiShared.AttachToolTip("Hold CTRL and click to clear this Syncshell." + Environment.NewLine + "WARNING: this action is irreversible." + Environment.NewLine + + "Clearing the Syncshell will remove all not pinned users from it."); + + if (UiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Syncshell")) + { + if (UiShared.CtrlPressed() && UiShared.ShiftPressed()) + { + _ = _apiController.SendDeleteGroup(entry.GID); + } + } + UiShared.AttachToolTip("Hold CTRL and Shift and click to delete this Syncshell." + Environment.NewLine + "WARNING: this action is irreversible."); + } + + ImGui.EndPopup(); } - else + + if (ImGui.BeginPopupModal("Change Syncshell Password", ref _showModalChangePassword, ImGuiWindowFlags.AlwaysAutoResize)) { - ImGui.SameLine(); - ImGui.SameLine(); - ImGui.PushFont(UiBuilder.IconFont); - ImGui.Text(lockedIcon.ToIconString()); - ImGui.PopFont(); - UiShared.AttachToolTip(invitesEnabled ? "Syncshell is open for new joiners" : "Syncshell is closed for new joiners"); + UiShared.TextWrapped("Enter the new Syncshell password for Syncshell " + name + " here."); + UiShared.TextWrapped("This action is irreversible"); + ImGui.InputTextWithHint("##changepw", "New password for " + name, ref _newSyncShellPassword, 255); + if (ImGui.Button("Change password")) + { + var pw = _newSyncShellPassword; + _isPasswordValid = _apiController.ChangeGroupPassword(entry.GID, pw).Result; + _newSyncShellPassword = string.Empty; + if (_isPasswordValid) _showModalChangePassword = false; + } + + if (!_isPasswordValid) + { + UiShared.ColorTextWrapped("The selected password is too short. It must be at least 10 characters.", new Vector4(1, 0, 0, 1)); + } + + ImGui.EndPopup(); } } private void DrawSyncshellPairedClient(GroupPairDto entry, bool isOwner, bool isPausedByYou) { var plusButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); - var trashButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Trash); - var crownButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Crown); + var barButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Bars); var entryUID = string.IsNullOrEmpty(entry.UserAlias) ? entry.UserUID : entry.UserAlias; var textSize = ImGui.CalcTextSize(entryUID); var originalY = ImGui.GetCursorPosY(); @@ -384,6 +395,7 @@ namespace MareSynchronos.UI ImGui.PushFont(UiBuilder.IconFont); ImGui.TextUnformatted(FontAwesomeIcon.Thumbtack.ToIconString()); ImGui.PopFont(); + UiShared.AttachToolTip("User is pinned in this Syncshell"); } var textIsUid = true; @@ -453,36 +465,12 @@ namespace MareSynchronos.UI UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); } - bool addButtonShown = !_apiController.PairedClients.Any(p => p.OtherUID == entry.UserUID); - if (isOwner) + bool plusButtonShown = !_apiController.PairedClients.Any(p => p.OtherUID == entry.UserUID); + + if (plusButtonShown) { ImGui.SetCursorPosY(originalY); - var subtractedWidth = addButtonShown ? (ImGui.GetStyle().ItemSpacing.X + plusButtonSize.X) : 0; - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - subtractedWidth - trashButtonSize.X - ImGui.GetStyle().ItemSpacing.X - crownButtonSize.X); - - if (ImGuiComponents.IconButton(FontAwesomeIcon.Crown)) - { - if (UiShared.CtrlPressed() && UiShared.ShiftPressed()) - { - _ = _apiController.ChangeOwnerOfGroup(entry.GroupGID, entry.UserUID); - } - } - UiShared.AttachToolTip("Hold CTRL and SHIFT and click to transfer ownership of this Syncshell to " + (entry.UserAlias ?? entry.UserUID) + Environment.NewLine + "WARNING: This action is irreversible."); - ImGui.SameLine(); - if (ImGuiComponents.IconButton(FontAwesomeIcon.Trash)) - { - if (UiShared.CtrlPressed()) - { - _ = _apiController.SendRemoveUserFromGroup(entry.GroupGID, entry.UserUID); - } - } - UiShared.AttachToolTip("Hold CTRL and click to remove user " + (entry.UserAlias ?? entry.UserUID) + " from Syncshell"); - } - - if (addButtonShown) - { - ImGui.SetCursorPosY(originalY); - ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - plusButtonSize.X); + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - plusButtonSize.X - (isOwner ? barButtonSize.X + ImGui.GetStyle().ItemSpacing.X : 0)); if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { @@ -490,6 +478,44 @@ namespace MareSynchronos.UI } UiShared.AttachToolTip("Pair with " + entryUID + " individually"); } + + if (isOwner) + { + ImGui.SetCursorPosY(originalY); + var subtractedWidth = plusButtonShown ? (plusButtonSize.X) : 0; + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - barButtonSize.X); + + if (ImGuiComponents.IconButton(FontAwesomeIcon.Bars)) + { + ImGui.OpenPopup("Popup"); + } + } + + if (ImGui.BeginPopup("Popup")) + { + if (UiShared.IconTextButton(FontAwesomeIcon.Thumbtack, "Pin user")) + { + _ = _apiController.SendChangeUserPinned(entry.GroupGID, entry.UserUID, !entry.IsPinned ?? false); + } + UiShared.AttachToolTip("Pin this user to the Syncshell. Pinned users will not be deleted in case of a manually initiated Syncshell clean"); + if (UiShared.IconTextButton(FontAwesomeIcon.Crown, "Transfer Ownership")) + { + if (UiShared.CtrlPressed() && UiShared.ShiftPressed()) + { + _ = _apiController.ChangeOwnerOfGroup(entry.GroupGID, entry.UserUID); + } + } + UiShared.AttachToolTip("Hold CTRL and SHIFT and click to transfer ownership of this Syncshell to " + (entry.UserAlias ?? entry.UserUID) + Environment.NewLine + "WARNING: This action is irreversible."); + if (UiShared.IconTextButton(FontAwesomeIcon.Trash, "Remove user")) + { + if (UiShared.CtrlPressed()) + { + _ = _apiController.SendRemoveUserFromGroup(entry.GroupGID, entry.UserUID); + } + } + UiShared.AttachToolTip("Hold CTRL and click to remove user " + (entry.UserAlias ?? entry.UserUID) + " from Syncshell"); + ImGui.EndPopup(); + } } } } diff --git a/MareSynchronos/UI/UIShared.cs b/MareSynchronos/UI/UIShared.cs index 800324e..2c556ca 100644 --- a/MareSynchronos/UI/UIShared.cs +++ b/MareSynchronos/UI/UIShared.cs @@ -548,7 +548,7 @@ public class UiShared : IDisposable DrawHelpText("This allows you to stop the periodic scans of your Penumbra and Mare cache directories. Use this to move the Mare cache and Penumbra mod folders around. If you enable this permanently, run a Force rescan after adding mods to Penumbra."); } - private static Vector2 GetIconSize(FontAwesomeIcon icon) + public static Vector2 GetIconSize(FontAwesomeIcon icon) { ImGui.PushFont(UiBuilder.IconFont); var iconSize = ImGui.CalcTextSize(icon.ToIconString()); diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs index 1562351..851e888 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs @@ -94,6 +94,7 @@ public partial class ApiController existingUser.IsPaused = dto.IsPaused ?? existingUser.IsPaused; existingUser.UserAlias = dto.UserAlias ?? existingUser.UserAlias; + existingUser.IsPinned = dto.IsPinned ?? existingUser.IsPinned; } private async Task GroupChangedCallback(GroupDto dto) From fcb0b17a5654ce89610fc896df60edfd2513ad2a Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Mon, 3 Oct 2022 00:20:49 +0200 Subject: [PATCH 10/17] ui adjustments for groups --- MareAPI | 2 +- MareSynchronos/UI/CompactUI.cs | 6 +-- MareSynchronos/UI/GroupPanel.cs | 63 +++++++++++++++----------- MareSynchronos/WebAPI/ApiController.cs | 2 +- 4 files changed, 42 insertions(+), 31 deletions(-) diff --git a/MareAPI b/MareAPI index bab758f..a2a4d07 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit bab758f1736e266a392a7a93a47357b03b7b3e01 +Subproject commit a2a4d07c046e0dc5ea5a871dda551f866760d5f6 diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 897fd88..7a02838 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -59,7 +59,7 @@ public class CompactUi : Window, IDisposable this.WindowName = "Mare Synchronos " + dateTime + "###MareSynchronosMainUI"; Toggle(); #else - this.WindowName = "Mare Synchronos " + Assembly.GetExecutingAssembly().GetName().Version; + this.WindowName = "Mare Synchronos " + Assembly.GetExecutingAssembly().GetName().Version + "###MareSynchronosMainUI"; #endif Logger.Verbose("Creating " + nameof(CompactUi)); @@ -72,8 +72,8 @@ public class CompactUi : Window, IDisposable SizeConstraints = new WindowSizeConstraints() { - MinimumSize = new Vector2(300, 400), - MaximumSize = new Vector2(300, 2000), + MinimumSize = new Vector2(350, 400), + MaximumSize = new Vector2(350, 2000), }; windowSystem.AddWindow(this); diff --git a/MareSynchronos/UI/GroupPanel.cs b/MareSynchronos/UI/GroupPanel.cs index d31a52f..0059ed9 100644 --- a/MareSynchronos/UI/GroupPanel.cs +++ b/MareSynchronos/UI/GroupPanel.cs @@ -113,6 +113,7 @@ namespace MareSynchronos.UI } catch { + _lastCreatedGroup = null; _errorGroupCreate = true; } } @@ -175,7 +176,7 @@ namespace MareSynchronos.UI ExpandedGroupState[group.GID] = !ExpandedGroupState[group.GID]; } ImGui.PopStyleColor(2); - ImGui.SameLine(); + ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + collapseButton.X); var pauseIcon = (group.IsPaused ?? false) ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause; if (ImGuiComponents.IconButton(pauseIcon)) { @@ -186,6 +187,16 @@ namespace MareSynchronos.UI var groupName = group.Alias ?? group.GID; var textIsGid = true; + + if (group.OwnedBy == _apiController.UID) + { + ImGui.PushFont(UiBuilder.IconFont); + ImGui.Text(FontAwesomeIcon.Crown.ToIconString()); + ImGui.PopFont(); + UiShared.AttachToolTip("You are the owner of Syncshell " + groupName); + ImGui.SameLine(); + } + _showGidForEntry.TryGetValue(group.GID, out var showGidInsteadOfName); if (!showGidInsteadOfName && _configuration.GetCurrentServerGidComments().TryGetValue(group.GID, out var groupComment)) { @@ -244,21 +255,21 @@ namespace MareSynchronos.UI UiShared.DrawWithID(group.GID + "settings", () => DrawSyncShellButtons(group, name)); - ImGui.Indent(collapseButton.X + ImGui.GetStyle().ItemSpacing.X); + ImGui.Indent(collapseButton.X); if (ExpandedGroupState[group.GID]) { pairsInGroup = pairsInGroup.OrderBy(p => p.UserUID == group.OwnedBy ? 0 : 1).ThenBy(p => p.IsPinned ?? false).ThenBy(p => p.UserAlias ?? p.UserUID).ToList(); ImGui.Indent(ImGui.GetStyle().ItemSpacing.X / 2); - ImGui.Separator(); - foreach (var pair in pairsInGroup) { UiShared.DrawWithID(group.GID + pair.UserUID, () => DrawSyncshellPairedClient(pair, group.OwnedBy == _apiController.UID, group?.IsPaused ?? false)); } + + ImGui.Separator(); ImGui.Unindent(ImGui.GetStyle().ItemSpacing.X / 2); } - ImGui.Unindent(collapseButton.X + ImGui.GetStyle().ItemSpacing.X); + ImGui.Unindent(collapseButton.X); } private void DrawSyncShellButtons(GroupDto entry, string name) @@ -315,6 +326,27 @@ namespace MareSynchronos.UI } UiShared.AttachToolTip("Change Syncshell Password"); + if (ImGui.BeginPopupModal("Change Syncshell Password", ref _showModalChangePassword, ImGuiWindowFlags.AlwaysAutoResize)) + { + UiShared.TextWrapped("Enter the new Syncshell password for Syncshell " + name + " here."); + UiShared.TextWrapped("This action is irreversible"); + ImGui.InputTextWithHint("##changepw", "New password for " + name, ref _newSyncShellPassword, 255); + if (ImGui.Button("Change password")) + { + var pw = _newSyncShellPassword; + _isPasswordValid = _apiController.ChangeGroupPassword(entry.GID, pw).Result; + _newSyncShellPassword = string.Empty; + if (_isPasswordValid) _showModalChangePassword = false; + } + + if (!_isPasswordValid) + { + UiShared.ColorTextWrapped("The selected password is too short. It must be at least 10 characters.", new Vector4(1, 0, 0, 1)); + } + + ImGui.EndPopup(); + } + if (UiShared.IconTextButton(FontAwesomeIcon.Broom, "Clear Syncshell")) { if (UiShared.CtrlPressed()) @@ -337,27 +369,6 @@ namespace MareSynchronos.UI ImGui.EndPopup(); } - - if (ImGui.BeginPopupModal("Change Syncshell Password", ref _showModalChangePassword, ImGuiWindowFlags.AlwaysAutoResize)) - { - UiShared.TextWrapped("Enter the new Syncshell password for Syncshell " + name + " here."); - UiShared.TextWrapped("This action is irreversible"); - ImGui.InputTextWithHint("##changepw", "New password for " + name, ref _newSyncShellPassword, 255); - if (ImGui.Button("Change password")) - { - var pw = _newSyncShellPassword; - _isPasswordValid = _apiController.ChangeGroupPassword(entry.GID, pw).Result; - _newSyncShellPassword = string.Empty; - if (_isPasswordValid) _showModalChangePassword = false; - } - - if (!_isPasswordValid) - { - UiShared.ColorTextWrapped("The selected password is too short. It must be at least 10 characters.", new Vector4(1, 0, 0, 1)); - } - - ImGui.EndPopup(); - } } private void DrawSyncshellPairedClient(GroupPairDto entry, bool isOwner, bool isPausedByYou) diff --git a/MareSynchronos/WebAPI/ApiController.cs b/MareSynchronos/WebAPI/ApiController.cs index ba22b0f..ac508cc 100644 --- a/MareSynchronos/WebAPI/ApiController.cs +++ b/MareSynchronos/WebAPI/ApiController.cs @@ -114,7 +114,7 @@ public partial class ApiController : IDisposable .ToDictionary(k => k.Key, k => k.Value); public string UID => _connectionDto?.UID ?? string.Empty; - public string DisplayName => string.IsNullOrEmpty(_connectionDto?.Alias) ? (_connectionDto?.UID ?? string.Empty) : _connectionDto.Alias; + public string DisplayName => _connectionDto?.UID ?? string.Empty; private string ApiUri => _pluginConfiguration.ApiUri; public int OnlineUsers => SystemInfoDto.OnlineUsers; From 3b9b260ab333a75f8bdfa8d2520d78bfd28bf038 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Mon, 3 Oct 2022 15:58:31 +0200 Subject: [PATCH 11/17] add analyzer and implement changes as well as api changes --- MareSynchronos/Managers/TransientResourceManager.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index 12668ab..660741a 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -110,7 +110,7 @@ public class TransientResourceManager : IDisposable && f.ResolvedPath.ToLowerInvariant() == filePath))) { Logger.Debug("Not adding " + replacedGamePath + ":" + filePath); - Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath + Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase) && string.Equals(f.ResolvedPath.ToLowerInvariant(), filePath, StringComparison.OrdinalIgnoreCase))).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath)); && f.ResolvedPath.ToLowerInvariant() == filePath)).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath)); } else From 260c4a48ee9143a374e446ec2cce848073e573db Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Mon, 3 Oct 2022 15:58:51 +0200 Subject: [PATCH 12/17] add analyzers and api --- .editorconfig | 102 ++++++++++++++++++ MareAPI | 2 +- MareSynchronos.sln | 5 + MareSynchronos/Configuration.cs | 38 +++---- .../Factories/CharacterDataFactory.cs | 12 +-- MareSynchronos/FileCache/FileCache.cs | 2 +- MareSynchronos/FileCache/FileDbManager.cs | 36 +++---- .../FileCache/PeriodicFileScanner.cs | 13 ++- MareSynchronos/Managers/CachedPlayer.cs | 12 +-- MareSynchronos/Managers/IpcManager.cs | 2 +- .../Managers/OnlinePlayerManager.cs | 6 +- MareSynchronos/Managers/PlayerManager.cs | 10 +- .../Managers/TransientResourceManager.cs | 26 ++--- MareSynchronos/MareSynchronos.csproj | 8 ++ MareSynchronos/Models/CharacterData.cs | 8 +- MareSynchronos/Models/FileReplacement.cs | 4 +- MareSynchronos/Models/PlayerRelatedObject.cs | 2 +- MareSynchronos/Plugin.cs | 2 +- MareSynchronos/UI/CompactUI.cs | 26 +++-- MareSynchronos/UI/GroupPanel.cs | 56 ++++++---- MareSynchronos/UI/IntroUI.cs | 14 +-- MareSynchronos/UI/SettingsUi.cs | 2 +- MareSynchronos/UI/UIShared.cs | 4 +- MareSynchronos/Utils/Crypto.cs | 8 +- .../Utils/DalamudLoggingProvider.cs | 26 +++++ MareSynchronos/Utils/DalamudUtil.cs | 6 +- MareSynchronos/Utils/Logger.cs | 24 +---- .../{Various.cs => VariousExtensions.cs} | 2 +- .../WebAPI/ApIController.Functions.Files.cs | 60 +++++------ .../WebAPI/ApIController.Functions.Users.cs | 20 ++-- .../WebAPI/ApiController.Functions.Admin.cs | 10 +- .../ApiController.Functions.Callbacks.cs | 24 ++--- .../WebAPI/ApiController.Functions.Groups.cs | 52 ++++----- MareSynchronos/WebAPI/ApiController.cs | 76 ++++++++----- 34 files changed, 435 insertions(+), 265 deletions(-) create mode 100644 .editorconfig create mode 100644 MareSynchronos/Utils/DalamudLoggingProvider.cs rename MareSynchronos/Utils/{Various.cs => VariousExtensions.cs} (96%) diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..7be38b9 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,102 @@ +[*.cs] + +# MA0046: Use EventHandler to declare events +dotnet_diagnostic.MA0046.severity = silent +csharp_indent_labels = one_less_than_current +csharp_using_directive_placement = outside_namespace:silent +csharp_prefer_simple_using_statement = true:suggestion +csharp_prefer_braces = true:silent +csharp_style_namespace_declarations = block_scoped:silent +csharp_style_prefer_method_group_conversion = true:silent +csharp_style_prefer_top_level_statements = true:silent +csharp_style_expression_bodied_methods = false:silent +csharp_style_expression_bodied_constructors = false:silent +csharp_style_expression_bodied_operators = false:silent +csharp_style_expression_bodied_properties = true:silent +csharp_style_expression_bodied_indexers = true:silent +csharp_style_expression_bodied_accessors = true:silent +csharp_style_expression_bodied_lambdas = true:silent +csharp_style_expression_bodied_local_functions = false:silent +csharp_style_throw_expression = true:suggestion +csharp_style_prefer_null_check_over_type_check = true:suggestion +csharp_prefer_simple_default_expression = true:suggestion +csharp_style_prefer_local_over_anonymous_function = true:suggestion +csharp_style_prefer_index_operator = true:suggestion +csharp_style_prefer_range_operator = true:suggestion +csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion +csharp_style_prefer_tuple_swap = true:suggestion +csharp_style_prefer_utf8_string_literals = true:suggestion +csharp_style_inlined_variable_declaration = true:suggestion +dotnet_diagnostic.MA0016.severity = suggestion +dotnet_diagnostic.MA0051.severity = suggestion + +# MA0009: Add regex evaluation timeout +dotnet_diagnostic.MA0009.severity = silent + +[*.{cs,vb}] +dotnet_style_operator_placement_when_wrapping = beginning_of_line +tab_width = 4 +indent_size = 4 +end_of_line = crlf +dotnet_style_coalesce_expression = true:suggestion +dotnet_style_null_propagation = true:suggestion +dotnet_style_prefer_is_null_check_over_reference_equality_method = true:suggestion +dotnet_style_prefer_auto_properties = true:silent +dotnet_style_object_initializer = true:suggestion +dotnet_style_collection_initializer = true:suggestion +dotnet_style_prefer_simplified_boolean_expressions = true:suggestion +dotnet_style_prefer_conditional_expression_over_assignment = true:silent +dotnet_style_prefer_conditional_expression_over_return = true:silent +dotnet_style_explicit_tuple_names = true:suggestion +dotnet_style_prefer_inferred_tuple_names = true:suggestion +dotnet_style_prefer_inferred_anonymous_type_member_names = true:suggestion +dotnet_style_prefer_compound_assignment = true:suggestion +dotnet_style_prefer_simplified_interpolation = true:suggestion +dotnet_style_namespace_match_folder = true:suggestion +[*.{cs,vb}] +#### Naming styles #### + +# Naming rules + +dotnet_naming_rule.interface_should_be_begins_with_i.severity = suggestion +dotnet_naming_rule.interface_should_be_begins_with_i.symbols = interface +dotnet_naming_rule.interface_should_be_begins_with_i.style = begins_with_i + +dotnet_naming_rule.types_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.types_should_be_pascal_case.symbols = types +dotnet_naming_rule.types_should_be_pascal_case.style = pascal_case + +dotnet_naming_rule.non_field_members_should_be_pascal_case.severity = suggestion +dotnet_naming_rule.non_field_members_should_be_pascal_case.symbols = non_field_members +dotnet_naming_rule.non_field_members_should_be_pascal_case.style = pascal_case + +# Symbol specifications + +dotnet_naming_symbols.interface.applicable_kinds = interface +dotnet_naming_symbols.interface.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.interface.required_modifiers = + +dotnet_naming_symbols.types.applicable_kinds = class, struct, interface, enum +dotnet_naming_symbols.types.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.types.required_modifiers = + +dotnet_naming_symbols.non_field_members.applicable_kinds = property, event, method +dotnet_naming_symbols.non_field_members.applicable_accessibilities = public, internal, private, protected, protected_internal, private_protected +dotnet_naming_symbols.non_field_members.required_modifiers = + +# Naming styles + +dotnet_naming_style.begins_with_i.required_prefix = I +dotnet_naming_style.begins_with_i.required_suffix = +dotnet_naming_style.begins_with_i.word_separator = +dotnet_naming_style.begins_with_i.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case + +dotnet_naming_style.pascal_case.required_prefix = +dotnet_naming_style.pascal_case.required_suffix = +dotnet_naming_style.pascal_case.word_separator = +dotnet_naming_style.pascal_case.capitalization = pascal_case diff --git a/MareAPI b/MareAPI index a2a4d07..2d5d9d9 160000 --- a/MareAPI +++ b/MareAPI @@ -1 +1 @@ -Subproject commit a2a4d07c046e0dc5ea5a871dda551f866760d5f6 +Subproject commit 2d5d9d9d1cc87eebcf89cddc182e2c47fe31a8b4 diff --git a/MareSynchronos.sln b/MareSynchronos.sln index d92ff59..f38743f 100644 --- a/MareSynchronos.sln +++ b/MareSynchronos.sln @@ -9,6 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos.API", "MareA EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumbra\Penumbra.GameData\Penumbra.GameData.csproj", "{89DD407C-B2B7-4BB3-BF26-C550BA1841F8}" EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{585B740D-BA2C-429B-9CF3-B2D223423748}" + ProjectSection(SolutionItems) = preProject + .editorconfig = .editorconfig + EndProjectSection +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU diff --git a/MareSynchronos/Configuration.cs b/MareSynchronos/Configuration.cs index 5ab1507..36e2ffd 100644 --- a/MareSynchronos/Configuration.cs +++ b/MareSynchronos/Configuration.cs @@ -23,21 +23,21 @@ public static class ConfigurationExtensions { return configuration.UidServerComments.ContainsKey(configuration.ApiUri) ? configuration.UidServerComments[configuration.ApiUri] - : new Dictionary(); + : new Dictionary(StringComparer.Ordinal); } public static Dictionary GetCurrentServerGidComments(this Configuration configuration) { return configuration.GidServerComments.ContainsKey(configuration.ApiUri) ? configuration.GidServerComments[configuration.ApiUri] - : new Dictionary(); + : new Dictionary(StringComparer.Ordinal); } public static void SetCurrentServerGidComment(this Configuration configuration, string gid, string comment) { if (!configuration.GidServerComments.ContainsKey(configuration.ApiUri)) { - configuration.GidServerComments[configuration.ApiUri] = new Dictionary(); + configuration.GidServerComments[configuration.ApiUri] = new Dictionary(StringComparer.Ordinal); } configuration.GidServerComments[configuration.ApiUri][gid] = comment; @@ -47,7 +47,7 @@ public static class ConfigurationExtensions { if (!configuration.UidServerComments.ContainsKey(configuration.ApiUri)) { - configuration.UidServerComments[configuration.ApiUri] = new Dictionary(); + configuration.UidServerComments[configuration.ApiUri] = new Dictionary(StringComparer.Ordinal); } configuration.UidServerComments[configuration.ApiUri][uid] = comment; @@ -71,8 +71,8 @@ public class Configuration : IPluginConfiguration } public string CacheFolder { get; set; } = string.Empty; - public Dictionary ClientSecret { get; set; } = new(); - public Dictionary CustomServerList { get; set; } = new(); + public Dictionary ClientSecret { get; set; } = new(StringComparer.Ordinal); + public Dictionary CustomServerList { get; set; } = new(StringComparer.Ordinal); public int MaxLocalCacheInGiB { get; set; } = 20; public bool ReverseUserSort { get; set; } = false; @@ -82,10 +82,10 @@ public class Configuration : IPluginConfiguration public bool InitialScanComplete { get; set; } = false; public bool FullPause { get; set; } = false; - public Dictionary> UidServerComments { get; set; } = new(); - public Dictionary> GidServerComments { get; set; } = new(); + public Dictionary> UidServerComments { get; set; } = new(StringComparer.Ordinal); + public Dictionary> GidServerComments { get; set; } = new(StringComparer.Ordinal); - public Dictionary UidComments { get; set; } = new(); + public Dictionary UidComments { get; set; } = new(StringComparer.Ordinal); public int Version { get; set; } = 5; public bool ShowTransferWindow { get; set; } = true; @@ -114,10 +114,10 @@ public class Configuration : IPluginConfiguration { Logger.Debug("Migrating Configuration from V0 to V1"); Version = 1; - ApiUri = ApiUri.Replace("https", "wss"); + ApiUri = ApiUri.Replace("https", "wss", StringComparison.Ordinal); foreach (var kvp in ClientSecret.ToList()) { - var newKey = kvp.Key.Replace("https", "wss"); + var newKey = kvp.Key.Replace("https", "wss", StringComparison.Ordinal); ClientSecret.Remove(kvp.Key); if (ClientSecret.ContainsKey(newKey)) { @@ -128,7 +128,7 @@ public class Configuration : IPluginConfiguration ClientSecret.Add(newKey, kvp.Value); } } - UidServerComments.Add(ApiUri, UidComments.ToDictionary(k => k.Key, k => k.Value)); + UidServerComments.Add(ApiUri, UidComments.ToDictionary(k => k.Key, k => k.Value, StringComparer.Ordinal)); UidComments.Clear(); Save(); } @@ -136,10 +136,10 @@ public class Configuration : IPluginConfiguration if (Version == 1) { Logger.Debug("Migrating Configuration from V1 to V2"); - ApiUri = ApiUri.Replace("5001", "5000"); + ApiUri = ApiUri.Replace("5001", "5000", StringComparison.Ordinal); foreach (var kvp in ClientSecret.ToList()) { - var newKey = kvp.Key.Replace("5001", "5000"); + var newKey = kvp.Key.Replace("5001", "5000", StringComparison.Ordinal); ClientSecret.Remove(kvp.Key); if (ClientSecret.ContainsKey(newKey)) { @@ -153,7 +153,7 @@ public class Configuration : IPluginConfiguration foreach (var kvp in UidServerComments.ToList()) { - var newKey = kvp.Key.Replace("5001", "5000"); + var newKey = kvp.Key.Replace("5001", "5000", StringComparison.Ordinal); UidServerComments.Remove(kvp.Key); UidServerComments.Add(newKey, kvp.Value); } @@ -177,10 +177,10 @@ public class Configuration : IPluginConfiguration { Logger.Debug("Migrating Configuration from V3 to V4"); - ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872"); + ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872", StringComparison.Ordinal); foreach (var kvp in ClientSecret.ToList()) { - var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872"); + var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872", StringComparison.Ordinal); ClientSecret.Remove(kvp.Key); if (ClientSecret.ContainsKey(newKey)) { @@ -194,7 +194,7 @@ public class Configuration : IPluginConfiguration foreach (var kvp in UidServerComments.ToList()) { - var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872"); + var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872", StringComparison.Ordinal); UidServerComments.Remove(kvp.Key); UidServerComments.Add(newKey, kvp.Value); } @@ -207,7 +207,7 @@ public class Configuration : IPluginConfiguration { Logger.Debug("Migrating Configuration from V4 to V5"); - ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6872", "wss://maresynchronos.com"); + ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6872", "wss://maresynchronos.com", StringComparison.Ordinal); ClientSecret.Remove("wss://v2202207178628194299.powersrv.de:6872"); UidServerComments.Remove("wss://v2202207178628194299.powersrv.de:6872"); diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index f7e767b..efab6a1 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -158,7 +158,7 @@ public class CharacterDataFactory if (cache.FileReplacements.ContainsKey(objectKind)) { - if (cache.FileReplacements[objectKind].Any(c => c.ResolvedPath.Contains(mtrlPath))) + if (cache.FileReplacements[objectKind].Any(c => c.ResolvedPath.Contains(mtrlPath, StringComparison.Ordinal))) { return; } @@ -196,7 +196,7 @@ public class CharacterDataFactory if (cache.FileReplacements.ContainsKey(objectKind)) { - if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(varPath))) + if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(varPath, StringComparer.Ordinal))) { return; } @@ -214,7 +214,7 @@ public class CharacterDataFactory if (cache.FileReplacements.ContainsKey(objectKind)) { - if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(texPath))) + if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(texPath, StringComparer.Ordinal))) { return; } @@ -225,7 +225,7 @@ public class CharacterDataFactory cache.AddFileReplacement(objectKind, texFileReplacement); - if (texPath.Contains("/--")) return; + if (texPath.Contains("/--", StringComparison.Ordinal)) return; var texDx11Replacement = CreateFileReplacement(texPath.Insert(texPath.LastIndexOf('/') + 1, "--"), doNotReverseResolve); @@ -322,11 +322,11 @@ public class CharacterDataFactory previousData.FileReplacements.Add(objectKind, new()); } - if (!previousData.FileReplacements[objectKind].Any(k => k.GamePaths.Any(p => item.GamePaths.Select(p => p.ToLowerInvariant()).Contains(p.ToLowerInvariant())))) + if (!previousData.FileReplacements[objectKind].Any(k => k.GamePaths.Any(p => item.GamePaths.Contains(p, StringComparer.OrdinalIgnoreCase)))) { var penumResolve = _ipcManager.PenumbraResolvePath(item.GamePaths.First()).ToLowerInvariant(); var gamePath = item.GamePaths.First().ToLowerInvariant(); - if (penumResolve == gamePath) + if (string.Equals(penumResolve, gamePath, StringComparison.Ordinal)) { Logger.Debug("PenumResolve was same as GamePath, not adding " + item); transientResourceManager.RemoveTransientResource(charaPointer, item); diff --git a/MareSynchronos/FileCache/FileCache.cs b/MareSynchronos/FileCache/FileCache.cs index 38aceb9..1bd3b53 100644 --- a/MareSynchronos/FileCache/FileCache.cs +++ b/MareSynchronos/FileCache/FileCache.cs @@ -22,7 +22,7 @@ public class FileCache public void SetResolvedFilePath(string filePath) { - ResolvedFilepath = filePath.ToLowerInvariant().Replace("\\\\", "\\"); + ResolvedFilepath = filePath.ToLowerInvariant().Replace("\\\\", "\\", System.StringComparison.Ordinal); } public string CsvEntry => $"{Hash}{FileCacheManager.CsvSplit}{PrefixedFilePath}{FileCacheManager.CsvSplit}{LastModifiedDateTicks.ToString(CultureInfo.InvariantCulture)}"; diff --git a/MareSynchronos/FileCache/FileDbManager.cs b/MareSynchronos/FileCache/FileDbManager.cs index 7d97516..dd71bd4 100644 --- a/MareSynchronos/FileCache/FileDbManager.cs +++ b/MareSynchronos/FileCache/FileDbManager.cs @@ -26,9 +26,9 @@ public class FileCacheManager : IDisposable private readonly Configuration _configuration; private readonly string CsvPath; private string CsvBakPath => CsvPath + ".bak"; - private readonly ConcurrentDictionary FileCaches = new(); + private readonly ConcurrentDictionary FileCaches = new(StringComparer.Ordinal); public const string CsvSplit = "|"; - private object _fileWriteLock = new object(); + private object _fileWriteLock = new(); public FileCacheManager(IpcManager ipcManager, Configuration configuration, string configDirectoryName) { @@ -64,7 +64,7 @@ public class FileCacheManager : IDisposable public void WriteOutFullCsv() { - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); foreach (var entry in FileCaches.OrderBy(f => f.Value.PrefixedFilePath)) { sb.AppendLine(entry.Value.CsvEntry); @@ -91,9 +91,9 @@ public class FileCacheManager : IDisposable public FileCache? GetFileCacheByHash(string hash) { - if (FileCaches.Any(f => f.Value.Hash == hash)) + if (FileCaches.Any(f => string.Equals(f.Value.Hash, hash, StringComparison.Ordinal))) { - return GetValidatedFileCache(FileCaches.FirstOrDefault(f => f.Value.Hash == hash).Value); + return GetValidatedFileCache(FileCaches.FirstOrDefault(f => string.Equals(f.Value.Hash, hash, StringComparison.Ordinal)).Value); } return null; @@ -107,7 +107,7 @@ public class FileCacheManager : IDisposable { return (FileState.RequireDeletion, fileCache); } - if (fi.LastWriteTimeUtc.Ticks.ToString() != fileCache.LastModifiedDateTicks) + if (!string.Equals(fi.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture), fileCache.LastModifiedDateTicks, StringComparison.Ordinal)) { return (FileState.RequireUpdate, fileCache); } @@ -117,8 +117,8 @@ public class FileCacheManager : IDisposable public FileCache? GetFileCacheByPath(string path) { - var cleanedPath = path.Replace("/", "\\").ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), ""); - var entry = FileCaches.FirstOrDefault(f => f.Value.ResolvedFilepath.EndsWith(cleanedPath)).Value; + var cleanedPath = path.Replace("/", "\\", StringComparison.Ordinal).ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), "", StringComparison.Ordinal); + var entry = FileCaches.FirstOrDefault(f => f.Value.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.Ordinal)).Value; if (entry == null) { @@ -137,9 +137,9 @@ public class FileCacheManager : IDisposable FileInfo fi = new(path); if (!fi.Exists) return null; var fullName = fi.FullName.ToLowerInvariant(); - if (!fullName.Contains(_configuration.CacheFolder.ToLowerInvariant())) return null; - string prefixedPath = fullName.Replace(_configuration.CacheFolder.ToLowerInvariant(), CachePrefix + "\\").Replace("\\\\", "\\"); - return CreateFileCacheEntity(fi, prefixedPath, fi.Name.ToUpper()); + if (!fullName.Contains(_configuration.CacheFolder.ToLowerInvariant(), StringComparison.Ordinal)) return null; + string prefixedPath = fullName.Replace(_configuration.CacheFolder.ToLowerInvariant(), CachePrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal); + return CreateFileCacheEntity(fi, prefixedPath, fi.Name.ToUpper(CultureInfo.InvariantCulture)); } public FileCache? CreateFileEntry(string path) @@ -148,8 +148,8 @@ public class FileCacheManager : IDisposable FileInfo fi = new(path); if (!fi.Exists) return null; var fullName = fi.FullName.ToLowerInvariant(); - if (!fullName.Contains(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant())) return null; - string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), PenumbraPrefix + "\\").Replace("\\\\", "\\"); + if (!fullName.Contains(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), StringComparison.Ordinal)) return null; + string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), PenumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal); return CreateFileCacheEntity(fi, prefixedPath); } @@ -187,7 +187,7 @@ public class FileCacheManager : IDisposable return null; } - if (file.LastWriteTimeUtc.Ticks.ToString() != fileCache.LastModifiedDateTicks) + if (!string.Equals(file.LastWriteTimeUtc.Ticks.ToString(), fileCache.LastModifiedDateTicks, StringComparison.Ordinal)) { UpdateHash(fileCache); } @@ -211,13 +211,13 @@ public class FileCacheManager : IDisposable private FileCache ReplacePathPrefixes(FileCache fileCache) { - if (fileCache.PrefixedFilePath.StartsWith(PenumbraPrefix)) + if (fileCache.PrefixedFilePath.StartsWith(PenumbraPrefix, StringComparison.OrdinalIgnoreCase)) { - fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(PenumbraPrefix, _ipcManager.PenumbraModDirectory())); + fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(PenumbraPrefix, _ipcManager.PenumbraModDirectory(), StringComparison.Ordinal)); } - else if (fileCache.PrefixedFilePath.StartsWith(CachePrefix)) + else if (fileCache.PrefixedFilePath.StartsWith(CachePrefix, StringComparison.OrdinalIgnoreCase)) { - fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(CachePrefix, _configuration.CacheFolder)); + fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(CachePrefix, _configuration.CacheFolder, StringComparison.Ordinal)); } return fileCache; diff --git a/MareSynchronos/FileCache/PeriodicFileScanner.cs b/MareSynchronos/FileCache/PeriodicFileScanner.cs index ce9e649..59d01ef 100644 --- a/MareSynchronos/FileCache/PeriodicFileScanner.cs +++ b/MareSynchronos/FileCache/PeriodicFileScanner.cs @@ -20,7 +20,7 @@ public class PeriodicFileScanner : IDisposable private readonly DalamudUtil _dalamudUtil; private CancellationTokenSource? _scanCancellationTokenSource; private Task? _fileScannerTask = null; - public ConcurrentDictionary haltScanLocks = new(); + public ConcurrentDictionary haltScanLocks = new(StringComparer.Ordinal); public PeriodicFileScanner(IpcManager ipcManager, Configuration pluginConfiguration, FileCacheManager fileDbManager, ApiController apiController, DalamudUtil dalamudUtil) { Logger.Verbose("Creating " + nameof(PeriodicFileScanner)); @@ -128,7 +128,7 @@ public class PeriodicFileScanner : IDisposable { while (haltScanLocks.Any(f => f.Value > 0)) { - await Task.Delay(TimeSpan.FromSeconds(1)); + await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } isForced |= RecalculateFileCacheSize(); @@ -144,7 +144,7 @@ public class PeriodicFileScanner : IDisposable _timeUntilNextScan = TimeSpan.FromSeconds(timeBetweenScans); while (_timeUntilNextScan.TotalSeconds >= 0) { - await Task.Delay(TimeSpan.FromSeconds(1), token); + await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false); _timeUntilNextScan -= TimeSpan.FromSeconds(1); } } @@ -206,11 +206,14 @@ public class PeriodicFileScanner : IDisposable var scannedFiles = new ConcurrentDictionary(Directory.EnumerateFiles(penumbraDir, "*.*", SearchOption.AllDirectories) .Select(s => s.ToLowerInvariant()) - .Where(f => ext.Any(e => f.EndsWith(e)) && !f.Contains(@"\bg\") && !f.Contains(@"\bgcommon\") && !f.Contains(@"\ui\")) + .Where(f => ext.Any(e => f.EndsWith(e, StringComparison.OrdinalIgnoreCase)) + && !f.Contains(@"\bg\", StringComparison.OrdinalIgnoreCase) + && !f.Contains(@"\bgcommon\", StringComparison.OrdinalIgnoreCase) + && !f.Contains(@"\ui\", StringComparison.OrdinalIgnoreCase)) .Concat(Directory.EnumerateFiles(_pluginConfiguration.CacheFolder, "*.*", SearchOption.TopDirectoryOnly) .Where(f => new FileInfo(f).Name.Length == 40) .Select(s => s.ToLowerInvariant()).ToList()) - .Select(c => new KeyValuePair(c, false))); + .Select(c => new KeyValuePair(c, false)), StringComparer.OrdinalIgnoreCase); TotalFiles = scannedFiles.Count; diff --git a/MareSynchronos/Managers/CachedPlayer.cs b/MareSynchronos/Managers/CachedPlayer.cs index b85d936..70318a4 100644 --- a/MareSynchronos/Managers/CachedPlayer.cs +++ b/MareSynchronos/Managers/CachedPlayer.cs @@ -71,7 +71,7 @@ public class CachedPlayer if (characterData.GetHashCode() == _cachedData.GetHashCode()) return; bool updateModdedPaths = false; - List charaDataToUpdate = new List(); + List charaDataToUpdate = new(); foreach (var objectKind in Enum.GetValues()) { _cachedData.FileReplacements.TryGetValue(objectKind, out var existingFileReplacements); @@ -108,7 +108,7 @@ public class CachedPlayer if (hasNewAndOldGlamourerData) { - bool glamourerDataDifferent = _cachedData.GlamourerData[objectKind] != characterData.GlamourerData[objectKind]; + bool glamourerDataDifferent = !string.Equals(_cachedData.GlamourerData[objectKind], characterData.GlamourerData[objectKind], StringComparison.Ordinal); if (glamourerDataDifferent) { Logger.Debug("Updating " + objectKind); @@ -159,7 +159,7 @@ public class CachedPlayer Logger.Debug("Downloading missing files for player " + PlayerName + ", kind: " + objectKind); if (toDownloadReplacements.Any()) { - await _apiController.DownloadFiles(downloadId, toDownloadReplacements, downloadToken); + await _apiController.DownloadFiles(downloadId, toDownloadReplacements, downloadToken).ConfigureAwait(false); _apiController.CancelDownload(downloadId); } if (downloadToken.IsCancellationRequested) @@ -168,7 +168,7 @@ public class CachedPlayer return; } - if ((TryCalculateModdedDictionary(out moddedPaths)).All(c => _apiController.ForbiddenTransfers.Any(f => f.Hash == c.Hash))) + if ((TryCalculateModdedDictionary(out moddedPaths)).All(c => _apiController.ForbiddenTransfers.Any(f => string.Equals(f.Hash, c.Hash, StringComparison.Ordinal)))) { break; } @@ -195,7 +195,7 @@ public class CachedPlayer private List TryCalculateModdedDictionary(out Dictionary moddedDictionary) { List missingFiles = new(); - moddedDictionary = new Dictionary(); + moddedDictionary = new Dictionary(StringComparer.Ordinal); try { foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => string.IsNullOrEmpty(v.FileSwapPath))).ToList()) @@ -454,7 +454,7 @@ public class CachedPlayer private void IpcManagerOnPenumbraRedrawEvent(IntPtr address, int idx) { var player = _dalamudUtil.GetCharacterFromObjectTableByIndex(idx); - if (player == null || player.Name.ToString() != PlayerName) return; + if (player == null || !string.Equals(player.Name.ToString(), PlayerName, StringComparison.OrdinalIgnoreCase)) return; if (!_penumbraRedrawEventTask?.IsCompleted ?? false) return; _penumbraRedrawEventTask = Task.Run(() => diff --git a/MareSynchronos/Managers/IpcManager.cs b/MareSynchronos/Managers/IpcManager.cs index e7c5882..cdd351c 100644 --- a/MareSynchronos/Managers/IpcManager.cs +++ b/MareSynchronos/Managers/IpcManager.cs @@ -158,7 +158,7 @@ public class IpcManager : IDisposable { try { - return _heelsGetApiVersion.InvokeFunc() == "1.0.1"; + return string.Equals(_heelsGetApiVersion.InvokeFunc(), "1.0.1", StringComparison.Ordinal); } catch { diff --git a/MareSynchronos/Managers/OnlinePlayerManager.cs b/MareSynchronos/Managers/OnlinePlayerManager.cs index efbda2b..20a683c 100644 --- a/MareSynchronos/Managers/OnlinePlayerManager.cs +++ b/MareSynchronos/Managers/OnlinePlayerManager.cs @@ -19,8 +19,8 @@ public class OnlinePlayerManager : IDisposable private readonly IpcManager _ipcManager; private readonly PlayerManager _playerManager; private readonly FileCacheManager _fileDbManager; - private readonly ConcurrentDictionary _onlineCachedPlayers = new(); - private readonly ConcurrentDictionary _temporaryStoredCharacterCache = new(); + private readonly ConcurrentDictionary _onlineCachedPlayers = new(StringComparer.Ordinal); + private readonly ConcurrentDictionary _temporaryStoredCharacterCache = new(StringComparer.Ordinal); private readonly ConcurrentDictionary _playerTokenDisposal = new(); private List OnlineVisiblePlayerHashes => _onlineCachedPlayers.Select(p => p.Value).Where(p => p.PlayerCharacter != IntPtr.Zero) @@ -226,7 +226,7 @@ public class OnlinePlayerManager : IDisposable Task.Run(async () => { await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData, - visiblePlayers); + visiblePlayers).ConfigureAwait(false); }); } } diff --git a/MareSynchronos/Managers/PlayerManager.cs b/MareSynchronos/Managers/PlayerManager.cs index 21e2879..accfd0c 100644 --- a/MareSynchronos/Managers/PlayerManager.cs +++ b/MareSynchronos/Managers/PlayerManager.cs @@ -34,7 +34,7 @@ public class PlayerManager : IDisposable private CancellationTokenSource? _playerChangedCts = new(); private CancellationTokenSource _transientUpdateCts = new(); - private List playerRelatedObjects = new List(); + private List playerRelatedObjects = new(); public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager, CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager, @@ -88,7 +88,7 @@ public class PlayerManager : IDisposable Task.Run(async () => { Logger.Debug("Delaying transient resource load update"); - await Task.Delay(750, token); + await Task.Delay(750, token).ConfigureAwait(false); if (obj.HasUnprocessedUpdate || token.IsCancellationRequested) return; Logger.Debug("Firing transient resource load update"); obj.HasTransientsUpdate = true; @@ -169,7 +169,7 @@ public class PlayerManager : IDisposable while (!PermanentDataCache.IsReady && !token.IsCancellationRequested) { Logger.Verbose("Waiting until cache is ready"); - await Task.Delay(50, token); + await Task.Delay(50, token).ConfigureAwait(false); } if (token.IsCancellationRequested) return null; @@ -215,7 +215,7 @@ public class PlayerManager : IDisposable var token = _playerChangedCts.Token; // fix for redraw from anamnesis - while ((!_dalamudUtil.IsPlayerPresent || _dalamudUtil.PlayerName == "--") && !token.IsCancellationRequested) + while ((!_dalamudUtil.IsPlayerPresent || string.Equals(_dalamudUtil.PlayerName, "--", StringComparison.Ordinal)) && !token.IsCancellationRequested) { Logger.Debug("Waiting Until Player is Present"); Thread.Sleep(100); @@ -244,7 +244,7 @@ public class PlayerManager : IDisposable _dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, 10000, token); } - cacheDto = (await CreateFullCharacterCacheDto(token)); + cacheDto = (await CreateFullCharacterCacheDto(token).ConfigureAwait(false)); } catch { } finally diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index 660741a..97d02be 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -82,7 +82,7 @@ public class TransientResourceManager : IDisposable private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath) { - if (!FileTypesToHandle.Any(type => gamePath.ToLowerInvariant().EndsWith(type))) + if (!FileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase))) { return; } @@ -93,25 +93,25 @@ public class TransientResourceManager : IDisposable if (!TransientResources.ContainsKey(gameObject)) { - TransientResources[gameObject] = new(); + TransientResources[gameObject] = new(StringComparer.OrdinalIgnoreCase); } - if (filePath.StartsWith("|")) + if (filePath.StartsWith("|", StringComparison.OrdinalIgnoreCase)) { filePath = filePath.Split("|")[2]; } - filePath = filePath.ToLowerInvariant().Replace("\\", "/"); + filePath = filePath.ToLowerInvariant().Replace("\\", "/", StringComparison.OrdinalIgnoreCase); - var replacedGamePath = gamePath.ToLowerInvariant().Replace("\\", "/"); + var replacedGamePath = gamePath.ToLowerInvariant().Replace("\\", "/", StringComparison.OrdinalIgnoreCase); if (TransientResources[gameObject].Contains(replacedGamePath) || - SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath - && f.ResolvedPath.ToLowerInvariant() == filePath))) + SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f.GamePaths.First(), replacedGamePath , StringComparison.OrdinalIgnoreCase) + && string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase)))) { Logger.Debug("Not adding " + replacedGamePath + ":" + filePath); - Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase) && string.Equals(f.ResolvedPath.ToLowerInvariant(), filePath, StringComparison.OrdinalIgnoreCase))).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath)); - && f.ResolvedPath.ToLowerInvariant() == filePath)).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath)); + Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase) + && string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase))).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath)); } else { @@ -125,7 +125,7 @@ public class TransientResourceManager : IDisposable { if (TransientResources.ContainsKey(gameObject)) { - TransientResources[gameObject].RemoveWhere(f => fileReplacement.GamePaths.Any(g => g.ToLowerInvariant() == f.ToLowerInvariant())); + TransientResources[gameObject].RemoveWhere(f => fileReplacement.GamePaths.Any(g => string.Equals(g, f, StringComparison.OrdinalIgnoreCase))); } } @@ -145,11 +145,11 @@ public class TransientResourceManager : IDisposable Logger.Debug("Persisting " + transientResources.Count + " transient resources"); foreach (var gamePath in transientResources) { - var existingResource = SemiTransientResources[objectKind].Any(f => f.GamePaths.First().ToLowerInvariant() == gamePath.ToLowerInvariant()); + var existingResource = SemiTransientResources[objectKind].Any(f => string.Equals(f.GamePaths.First(), gamePath, StringComparison.OrdinalIgnoreCase)); if (existingResource) { Logger.Debug("Semi Transient resource replaced: " + gamePath); - SemiTransientResources[objectKind].RemoveWhere(f => f.GamePaths.First().ToLowerInvariant() == gamePath.ToLowerInvariant()); + SemiTransientResources[objectKind].RemoveWhere(f => string.Equals(f.GamePaths.First(), gamePath, StringComparison.OrdinalIgnoreCase)); } try @@ -196,7 +196,7 @@ public class TransientResourceManager : IDisposable SemiTransientResources[objectKind] = new HashSet(); } - if (!SemiTransientResources[objectKind].Any(f => f.ResolvedPath.ToLowerInvariant() == item.ResolvedPath.ToLowerInvariant())) + if (!SemiTransientResources[objectKind].Any(f => string.Equals(f.ResolvedPath, item.ResolvedPath, StringComparison.OrdinalIgnoreCase))) { SemiTransientResources[objectKind].Add(item); } diff --git a/MareSynchronos/MareSynchronos.csproj b/MareSynchronos/MareSynchronos.csproj index 4a8610c..0cb2bd2 100644 --- a/MareSynchronos/MareSynchronos.csproj +++ b/MareSynchronos/MareSynchronos.csproj @@ -28,6 +28,10 @@ + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + @@ -86,4 +90,8 @@ + + + + diff --git a/MareSynchronos/Models/CharacterData.cs b/MareSynchronos/Models/CharacterData.cs index b0edfc4..0724ef8 100644 --- a/MareSynchronos/Models/CharacterData.cs +++ b/MareSynchronos/Models/CharacterData.cs @@ -31,10 +31,10 @@ public class CharacterData if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new List()); - var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => f.ResolvedPath == fileReplacement.ResolvedPath); + var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => string.Equals(f.ResolvedPath, fileReplacement.ResolvedPath, System.StringComparison.OrdinalIgnoreCase)); if (existingReplacement != null) { - existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e))); + existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e, System.StringComparer.OrdinalIgnoreCase))); } else { @@ -44,11 +44,11 @@ public class CharacterData public CharacterCacheDto ToCharacterCacheDto() { - var fileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement && !f.IsFileSwap).GroupBy(f => f.Hash).Select(g => + var fileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement && !f.IsFileSwap).GroupBy(f => f.Hash, System.StringComparer.OrdinalIgnoreCase).Select(g => { return new FileReplacementDto() { - GamePaths = g.SelectMany(f => f.GamePaths).Distinct().ToArray(), + GamePaths = g.SelectMany(f => f.GamePaths).Distinct(System.StringComparer.OrdinalIgnoreCase).ToArray(), Hash = g.First().Hash, }; }).ToList()); diff --git a/MareSynchronos/Models/FileReplacement.cs b/MareSynchronos/Models/FileReplacement.cs index a231afa..238bdc1 100644 --- a/MareSynchronos/Models/FileReplacement.cs +++ b/MareSynchronos/Models/FileReplacement.cs @@ -21,9 +21,9 @@ public class FileReplacement public List GamePaths { get; set; } = new(); - public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => p != ResolvedPath); + public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => !string.Equals(p, ResolvedPath, System.StringComparison.Ordinal)); - public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && GamePaths.First() != ResolvedPath; + public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && !string.Equals(GamePaths.First(), ResolvedPath, System.StringComparison.Ordinal); public string Hash { get; set; } = string.Empty; diff --git a/MareSynchronos/Models/PlayerRelatedObject.cs b/MareSynchronos/Models/PlayerRelatedObject.cs index d425891..50ad1b8 100644 --- a/MareSynchronos/Models/PlayerRelatedObject.cs +++ b/MareSynchronos/Models/PlayerRelatedObject.cs @@ -61,7 +61,7 @@ public class PlayerRelatedObject bool equip = CompareAndUpdateByteData(chara->EquipSlotData, chara->CustomizeData); bool drawObj = (IntPtr)chara->GameObject.DrawObject != DrawObjectAddress; var name = new Utf8String(chara->GameObject.Name).ToString(); - bool nameChange = (name != _name); + bool nameChange = (!string.Equals(name, _name, StringComparison.Ordinal)); if (addr || equip || drawObj || nameChange) { _name = name; diff --git a/MareSynchronos/Plugin.cs b/MareSynchronos/Plugin.cs index 5918753..c9aa278 100644 --- a/MareSynchronos/Plugin.cs +++ b/MareSynchronos/Plugin.cs @@ -175,7 +175,7 @@ public sealed class Plugin : IDalamudPlugin { while (!_dalamudUtil.IsPlayerPresent) { - await Task.Delay(100); + await Task.Delay(100).ConfigureAwait(false); } try diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 7a02838..6cc3eb9 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Globalization; using System.Linq; using System.Numerics; using System.Reflection; @@ -20,7 +21,7 @@ public class CompactUi : Window, IDisposable { private readonly ApiController _apiController; private readonly Configuration _configuration; - public readonly Dictionary ShowUidForEntry = new(); + public readonly Dictionary ShowUidForEntry = new(StringComparer.Ordinal); private readonly UiShared _uiShared; private readonly WindowSystem _windowSystem; private string _characterOrCommentFilter = string.Empty; @@ -162,7 +163,7 @@ public class CompactUi : Window, IDisposable ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { - if (_apiController.PairedClients.All(w => w.OtherUID != _pairToAdd)) + if (_apiController.PairedClients.All(w => !string.Equals(w.OtherUID, _pairToAdd, StringComparison.Ordinal))) { _ = _apiController.SendPairedClientAddition(_pairToAdd); _pairToAdd = string.Empty; @@ -316,7 +317,7 @@ public class CompactUi : Window, IDisposable } ImGui.SameLine(); - if (EditNickEntry != entry.OtherUID) + if (!string.Equals(EditNickEntry, entry.OtherUID, StringComparison.Ordinal)) { ImGui.SetCursorPosY(textPos); if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont); @@ -415,7 +416,7 @@ public class CompactUi : Window, IDisposable ImGui.EndChild(); } - + private IEnumerable GetFilteredUsers() { @@ -424,17 +425,23 @@ public class CompactUi : Window, IDisposable if (_characterOrCommentFilter.IsNullOrEmpty()) return true; _configuration.GetCurrentServerUidComments().TryGetValue(p.OtherUID, out var comment); var uid = p.VanityUID.IsNullOrEmpty() ? p.OtherUID : p.VanityUID; - return uid.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) || - (comment?.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) ?? false); + return uid.Contains(_characterOrCommentFilter, StringComparison.OrdinalIgnoreCase) || + (comment?.Contains(_characterOrCommentFilter, StringComparison.OrdinalIgnoreCase) ?? false); }); } private void DrawServerStatus() { var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Link); - var userCount = _apiController.OnlineUsers.ToString(); + var userCount = _apiController.OnlineUsers.ToString(CultureInfo.InvariantCulture); var userSize = ImGui.CalcTextSize(userCount); var textSize = ImGui.CalcTextSize("Users Online"); +#if DEBUG + string shardConnection = $"Connected shard: {_apiController.ServerInfo.ShardName}"; +#else + string shardConnection = string.Equals(_apiController.ServerInfo.ShardName, "Main", StringComparison.OrdinalIgnoreCase) ? string.Empty : $"Connected shard: {_apiController.ServerInfo.ShardName}"; +#endif + var shardTextSize = ImGui.CalcTextSize(shardConnection); if (_apiController.ServerState is ServerState.Connected) { @@ -444,6 +451,11 @@ public class CompactUi : Window, IDisposable ImGui.SameLine(); ImGui.AlignTextToFramePadding(); ImGui.Text("Users Online"); + ImGui.AlignTextToFramePadding(); + if (!string.IsNullOrEmpty(shardConnection)) + { + ImGui.TextUnformatted(shardConnection); + } } else { diff --git a/MareSynchronos/UI/GroupPanel.cs b/MareSynchronos/UI/GroupPanel.cs index 0059ed9..cceb985 100644 --- a/MareSynchronos/UI/GroupPanel.cs +++ b/MareSynchronos/UI/GroupPanel.cs @@ -19,7 +19,7 @@ namespace MareSynchronos.UI private Configuration _configuration; private ApiController _apiController; - private readonly Dictionary _showGidForEntry = new(); + private readonly Dictionary _showGidForEntry = new(StringComparer.Ordinal); private string _editGroupEntry = string.Empty; private string _editGroupComment = string.Empty; private string _syncShellPassword = string.Empty; @@ -33,7 +33,7 @@ namespace MareSynchronos.UI private bool _errorGroupJoin; private bool _errorGroupCreate = false; private GroupCreatedDto? _lastCreatedGroup = null; - private Dictionary ExpandedGroupState = new Dictionary(); + private readonly Dictionary ExpandedGroupState = new(StringComparer.Ordinal); public GroupPanel(CompactUi mainUi, UiShared uiShared, Configuration configuration, ApiController apiController) { @@ -56,23 +56,35 @@ namespace MareSynchronos.UI ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X); ImGui.InputTextWithHint("##syncshellid", "Syncshell GID/Alias", ref _syncShellToJoin, 20); ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); + + bool userCanJoinMoreGroups = _apiController.Groups.Count < _apiController.ServerInfo.MaxGroupsJoinedByUser; + bool userCanCreateMoreGroups = _apiController.Groups.Count(u => string.Equals(u.OwnedBy, _apiController.UID, StringComparison.Ordinal)) < _apiController.ServerInfo.MaxGroupsCreatedByUser; + if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { - if (_apiController.Groups.All(w => w.GID != _syncShellToJoin) && !string.IsNullOrEmpty(_syncShellToJoin)) + if (_apiController.Groups.All(w => !string.Equals(w.GID, _syncShellToJoin, StringComparison.Ordinal)) && !string.IsNullOrEmpty(_syncShellToJoin)) { - _errorGroupJoin = false; - _showModalEnterPassword = true; - ImGui.OpenPopup("Enter Syncshell Password"); + if (userCanJoinMoreGroups) + { + _errorGroupJoin = false; + _showModalEnterPassword = true; + ImGui.OpenPopup("Enter Syncshell Password"); + } } else { - _lastCreatedGroup = null; - _errorGroupCreate = false; - _showModalCreateGroup = true; - ImGui.OpenPopup("Create Syncshell"); + if (userCanCreateMoreGroups) + { + _lastCreatedGroup = null; + _errorGroupCreate = false; + _showModalCreateGroup = true; + ImGui.OpenPopup("Create Syncshell"); + } } } - UiShared.AttachToolTip(_syncShellToJoin.IsNullOrEmpty() ? "Create Syncshell" : "Join Syncshell" + _syncShellToJoin); + UiShared.AttachToolTip(_syncShellToJoin.IsNullOrEmpty() + ? (userCanCreateMoreGroups ? "Create Syncshell" : $"You cannot create more than {_apiController.ServerInfo.MaxGroupsCreatedByUser} Syncshells") + : (userCanJoinMoreGroups ? "Join Syncshell" + _syncShellToJoin : $"You cannot join more than {_apiController.ServerInfo.MaxGroupsJoinedByUser} Syncshells")); if (ImGui.BeginPopupModal("Enter Syncshell Password", ref _showModalEnterPassword, ImGuiWindowFlags.AlwaysAutoResize)) { @@ -82,7 +94,8 @@ namespace MareSynchronos.UI ImGui.InputTextWithHint("##password", _syncShellToJoin + " Password", ref _syncShellPassword, 255, ImGuiInputTextFlags.Password); if (_errorGroupJoin) { - UiShared.ColorTextWrapped("An error occured during joining of this Syncshell: you either have joined the maximum amount of Syncshells (6), it does not exist, the password you entered is wrong, you already joined the Syncshell, the Syncshell is full (100 users) or the Syncshell has closed invites.", + UiShared.ColorTextWrapped($"An error occured during joining of this Syncshell: you either have joined the maximum amount of Syncshells ({_apiController.ServerInfo.MaxGroupsJoinedByUser}), " + + $"it does not exist, the password you entered is wrong, you already joined the Syncshell, the Syncshell is full ({_apiController.ServerInfo.MaxGroupUserCount} users) or the Syncshell has closed invites.", new Vector4(1, 0, 0, 1)); } if (ImGui.Button("Join " + _syncShellToJoin)) @@ -141,7 +154,6 @@ namespace MareSynchronos.UI ImGui.EndPopup(); } - ImGuiHelpers.ScaledDummy(2); } @@ -161,7 +173,7 @@ namespace MareSynchronos.UI private void DrawSyncshell(GroupDto group) { var name = group.Alias ?? group.GID; - var pairsInGroup = _apiController.GroupPairedClients.Where(p => p.GroupGID == group.GID).ToList(); + var pairsInGroup = _apiController.GroupPairedClients.Where(p => string.Equals(p.GroupGID, group.GID, StringComparison.Ordinal)).ToList(); if (!ExpandedGroupState.TryGetValue(group.GID, out bool isExpanded)) { isExpanded = false; @@ -188,7 +200,7 @@ namespace MareSynchronos.UI var groupName = group.Alias ?? group.GID; var textIsGid = true; - if (group.OwnedBy == _apiController.UID) + if (string.Equals(group.OwnedBy, _apiController.UID, StringComparison.Ordinal)) { ImGui.PushFont(UiBuilder.IconFont); ImGui.Text(FontAwesomeIcon.Crown.ToIconString()); @@ -207,7 +219,7 @@ namespace MareSynchronos.UI } } - if (_editGroupEntry != group.GID) + if (!string.Equals(_editGroupEntry, group.GID, StringComparison.Ordinal)) { if (textIsGid) ImGui.PushFont(UiBuilder.MonoFont); ImGui.TextUnformatted(groupName); @@ -258,12 +270,12 @@ namespace MareSynchronos.UI ImGui.Indent(collapseButton.X); if (ExpandedGroupState[group.GID]) { - pairsInGroup = pairsInGroup.OrderBy(p => p.UserUID == group.OwnedBy ? 0 : 1).ThenBy(p => p.IsPinned ?? false).ThenBy(p => p.UserAlias ?? p.UserUID).ToList(); + pairsInGroup = pairsInGroup.OrderBy(p => string.Equals(p.UserUID, group.OwnedBy, StringComparison.Ordinal) ? 0 : 1).ThenBy(p => p.IsPinned ?? false).ThenBy(p => p.UserAlias ?? p.UserUID).ToList(); ImGui.Indent(ImGui.GetStyle().ItemSpacing.X / 2); ImGui.Separator(); foreach (var pair in pairsInGroup) { - UiShared.DrawWithID(group.GID + pair.UserUID, () => DrawSyncshellPairedClient(pair, group.OwnedBy == _apiController.UID, group?.IsPaused ?? false)); + UiShared.DrawWithID(group.GID + pair.UserUID, () => DrawSyncshellPairedClient(pair, string.Equals(group.OwnedBy, _apiController.UID, StringComparison.Ordinal), group?.IsPaused ?? false)); } ImGui.Separator(); @@ -299,7 +311,7 @@ namespace MareSynchronos.UI _ = _apiController.SendLeaveGroup(entry.GID); } } - UiShared.AttachToolTip("Hold CTRL and click to leave this Syncshell" + (entry.OwnedBy != _apiController.UID ? string.Empty : Environment.NewLine + UiShared.AttachToolTip("Hold CTRL and click to leave this Syncshell" + (!string.Equals(entry.OwnedBy, _apiController.UID, StringComparison.Ordinal) ? string.Empty : Environment.NewLine + "WARNING: This action is irreverisble" + Environment.NewLine + "Leaving an owned Syncshell will transfer the ownership to a random person in the Syncshell.")); if (UiShared.IconTextButton(FontAwesomeIcon.Copy, "Copy ID")) @@ -308,7 +320,7 @@ namespace MareSynchronos.UI } UiShared.AttachToolTip("Copy Syncshell ID to Clipboard"); - if (entry.OwnedBy == _apiController.UID) + if (string.Equals(entry.OwnedBy, _apiController.UID, StringComparison.Ordinal)) { ImGui.Separator(); @@ -428,7 +440,7 @@ namespace MareSynchronos.UI } ImGui.SameLine(); - if (_mainUi.EditNickEntry != entry.UserUID) + if (!string.Equals(_mainUi.EditNickEntry, entry.UserUID, StringComparison.Ordinal)) { ImGui.SetCursorPosY(textPos); if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont); @@ -476,7 +488,7 @@ namespace MareSynchronos.UI UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); } - bool plusButtonShown = !_apiController.PairedClients.Any(p => p.OtherUID == entry.UserUID); + bool plusButtonShown = !_apiController.PairedClients.Any(p => string.Equals(p.OtherUID, entry.UserUID, StringComparison.Ordinal)); if (plusButtonShown) { diff --git a/MareSynchronos/UI/IntroUI.cs b/MareSynchronos/UI/IntroUI.cs index 26239a3..3135309 100644 --- a/MareSynchronos/UI/IntroUI.cs +++ b/MareSynchronos/UI/IntroUI.cs @@ -37,12 +37,12 @@ internal class IntroUi : Window, IDisposable private Task _timeoutTask; private string _timeoutTime; - private Dictionary _languages = new() { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } }; + private Dictionary _languages = new(StringComparer.Ordinal) { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } }; private int _currentLanguage; - private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1.Trim() - && _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2.Trim() - && _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3.Trim(); + private bool DarkSoulsCaptchaValid => string.Equals(_darkSoulsCaptcha1.Item2, _enteredDarkSoulsCaptcha1.Trim() +, StringComparison.Ordinal) && string.Equals(_darkSoulsCaptcha2.Item2, _enteredDarkSoulsCaptcha2.Trim() +, StringComparison.Ordinal) && string.Equals(_darkSoulsCaptcha3.Item2, _enteredDarkSoulsCaptcha3.Trim(), StringComparison.Ordinal); public void Dispose() @@ -158,7 +158,7 @@ internal class IntroUi : Window, IDisposable { _timeoutTime = $"{i}s " + Strings.ToS.RemainingLabel; Logger.Debug(_timeoutTime); - await Task.Delay(TimeSpan.FromSeconds(1)); + await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false); } }); } @@ -286,11 +286,11 @@ internal class IntroUi : Window, IDisposable private Tuple GetCaptchaTuple() { - Random random = new Random(); + Random random = new(); var paragraphIdx = random.Next(TosParagraphs.Length); var splitParagraph = TosParagraphs[paragraphIdx].Split(".", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray(); var sentenceIdx = random.Next(splitParagraph.Length); - var splitSentence = splitParagraph[sentenceIdx].Split(" ").Select(c => c.Trim()).Select(c => c.Replace(".", "").Replace(",", "").Replace("'", "")).ToArray(); + var splitSentence = splitParagraph[sentenceIdx].Split(" ").Select(c => c.Trim()).Select(c => c.Replace(".", "", StringComparison.Ordinal).Replace(",", "", StringComparison.Ordinal).Replace("'", "", StringComparison.Ordinal)).ToArray(); var wordIdx = random.Next(splitSentence.Length); return new($"{Strings.ToS.ParagraphLabel} {paragraphIdx + 1}, {Strings.ToS.SentenceLabel} {sentenceIdx + 1}, {Strings.ToS.WordLabel} {wordIdx + 1}", splitSentence[wordIdx]); } diff --git a/MareSynchronos/UI/SettingsUi.cs b/MareSynchronos/UI/SettingsUi.cs index bfbf45d..0d72429 100644 --- a/MareSynchronos/UI/SettingsUi.cs +++ b/MareSynchronos/UI/SettingsUi.cs @@ -335,7 +335,7 @@ public class SettingsUi : Window, IDisposable }); } ImGui.SameLine(); - if (onlineUser.UID != _apiController.UID && _apiController.IsAdmin) + if (!string.Equals(onlineUser.UID, _apiController.UID, StringComparison.Ordinal) && _apiController.IsAdmin) { if (!onlineUser.IsModerator) { diff --git a/MareSynchronos/UI/UIShared.cs b/MareSynchronos/UI/UIShared.cs index 2c556ca..3867c1f 100644 --- a/MareSynchronos/UI/UIShared.cs +++ b/MareSynchronos/UI/UIShared.cs @@ -294,7 +294,7 @@ public class UiShared : IDisposable bool isSelected = _serverSelectionIndex == i; if (ImGui.Selectable(comboEntries[i], isSelected)) { - _pluginConfiguration.ApiUri = _apiController.ServerDictionary.Single(k => k.Value == comboEntries[i]).Key; + _pluginConfiguration.ApiUri = _apiController.ServerDictionary.Single(k => string.Equals(k.Value, comboEntries[i], StringComparison.Ordinal)).Key; _pluginConfiguration.Save(); _ = _apiController.CreateConnections(); } @@ -451,7 +451,7 @@ public class UiShared : IDisposable { if (!success) return; - _isPenumbraDirectory = path.ToLowerInvariant() == _ipcManager.PenumbraModDirectory()?.ToLowerInvariant(); + _isPenumbraDirectory = string.Equals(path.ToLowerInvariant(), _ipcManager.PenumbraModDirectory()?.ToLowerInvariant(), StringComparison.Ordinal); _isDirectoryWritable = IsDirectoryWritable(path); _cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => new FileInfo(f).Name.Length != 40); _cacheDirectoryIsValidPath = Regex.IsMatch(path, @"^(?:[a-zA-Z]:\\[\w\s\-\\]+?|\/(?:[\w\s\-\/])+?)$", RegexOptions.ECMAScript); diff --git a/MareSynchronos/Utils/Crypto.cs b/MareSynchronos/Utils/Crypto.cs index a4062c0..7a10b54 100644 --- a/MareSynchronos/Utils/Crypto.cs +++ b/MareSynchronos/Utils/Crypto.cs @@ -11,24 +11,24 @@ public class Crypto public static string GetFileHash(string filePath) { using SHA1CryptoServiceProvider cryptoProvider = new(); - return BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", ""); + return BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", "", StringComparison.Ordinal); } public static string GetHash(string stringToHash) { using SHA1CryptoServiceProvider cryptoProvider = new(); - return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", ""); + return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "", StringComparison.Ordinal); } public static string GetHash256(string stringToHash) { using SHA256CryptoServiceProvider cryptoProvider = new(); - return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", ""); + return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "", StringComparison.Ordinal); } public static string GetHash256(PlayerCharacter character) { using SHA256CryptoServiceProvider cryptoProvider = new(); - return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", ""); + return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", "", StringComparison.Ordinal); } } diff --git a/MareSynchronos/Utils/DalamudLoggingProvider.cs b/MareSynchronos/Utils/DalamudLoggingProvider.cs new file mode 100644 index 0000000..f4813f8 --- /dev/null +++ b/MareSynchronos/Utils/DalamudLoggingProvider.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Concurrent; +using Microsoft.Extensions.Logging; + +namespace MareSynchronos.Utils; + +[ProviderAlias("Dalamud")] +public class DalamudLoggingProvider : ILoggerProvider +{ + private readonly ConcurrentDictionary _loggers = + new(StringComparer.OrdinalIgnoreCase); + + public DalamudLoggingProvider() + { + } + + public ILogger CreateLogger(string categoryName) + { + return _loggers.GetOrAdd(categoryName, name => new Logger(categoryName)); + } + + public void Dispose() + { + _loggers.Clear(); + } +} diff --git a/MareSynchronos/Utils/DalamudUtil.cs b/MareSynchronos/Utils/DalamudUtil.cs index 4a9b979..fe23079 100644 --- a/MareSynchronos/Utils/DalamudUtil.cs +++ b/MareSynchronos/Utils/DalamudUtil.cs @@ -186,7 +186,7 @@ public class DalamudUtil : IDisposable { return _objectTable.Where(obj => obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player && - obj.Name.ToString() != PlayerName).Select(p => (PlayerCharacter)p).ToList(); + !string.Equals(obj.Name.ToString(), PlayerName, StringComparison.Ordinal)).Select(p => (PlayerCharacter)p).ToList(); } public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index) @@ -201,7 +201,7 @@ public class DalamudUtil : IDisposable foreach (var item in _objectTable) { if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue; - if (item.Name.ToString() == characterName) return (PlayerCharacter)item; + if (string.Equals(item.Name.ToString(), characterName, StringComparison.Ordinal)) return (PlayerCharacter)item; } return null; @@ -209,7 +209,7 @@ public class DalamudUtil : IDisposable public async Task RunOnFrameworkThread(Func func) { - return await _framework.RunOnFrameworkThread(func); + return await _framework.RunOnFrameworkThread(func).ConfigureAwait(false); } public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, int timeOut = 5000, CancellationToken? ct = null) diff --git a/MareSynchronos/Utils/Logger.cs b/MareSynchronos/Utils/Logger.cs index 4d90bac..f816a5f 100644 --- a/MareSynchronos/Utils/Logger.cs +++ b/MareSynchronos/Utils/Logger.cs @@ -1,5 +1,4 @@ using System; -using System.Collections.Concurrent; using System.Diagnostics; using Dalamud.Logging; using Dalamud.Utility; @@ -7,27 +6,6 @@ using Microsoft.Extensions.Logging; namespace MareSynchronos.Utils; -[ProviderAlias("Dalamud")] -public class DalamudLoggingProvider : ILoggerProvider -{ - private readonly ConcurrentDictionary _loggers = - new(StringComparer.OrdinalIgnoreCase); - - public DalamudLoggingProvider() - { - } - - public ILogger CreateLogger(string categoryName) - { - return _loggers.GetOrAdd(categoryName, name => new Logger(categoryName)); - } - - public void Dispose() - { - _loggers.Clear(); - } -} - internal class Logger : ILogger { private readonly string name; @@ -41,7 +19,7 @@ internal class Logger : ILogger public static void Debug(string debug, string stringToHighlight = "") { var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; - if (debug.Contains(stringToHighlight) && !stringToHighlight.IsNullOrEmpty()) + if (debug.Contains(stringToHighlight, StringComparison.Ordinal) && !stringToHighlight.IsNullOrEmpty()) { PluginLog.Warning($"[{caller}] {debug}"); } diff --git a/MareSynchronos/Utils/Various.cs b/MareSynchronos/Utils/VariousExtensions.cs similarity index 96% rename from MareSynchronos/Utils/Various.cs rename to MareSynchronos/Utils/VariousExtensions.cs index 98fb104..32fa32b 100644 --- a/MareSynchronos/Utils/Various.cs +++ b/MareSynchronos/Utils/VariousExtensions.cs @@ -18,7 +18,7 @@ public static class VariousExtensions if (attribute?.InformationalVersion != null) { var value = attribute.InformationalVersion; - var index = value.IndexOf(BuildVersionMetadataPrefix); + var index = value.IndexOf(BuildVersionMetadataPrefix, StringComparison.Ordinal); if (index > 0) { value = value[(index + BuildVersionMetadataPrefix.Length)..]; diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs index cd6b637..89727b4 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Files.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Files.cs @@ -33,7 +33,7 @@ public partial class ApiController public async Task DeleteAllMyFiles() { - await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles); + await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles).ConfigureAwait(false); } private async Task DownloadFile(int downloadId, string hash, Uri downloadUri, CancellationToken ct) @@ -44,7 +44,7 @@ public partial class ApiController { try { - CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = e.BytesReceived; + CurrentDownloads[downloadId].Single(f => string.Equals(f.Hash, hash, StringComparison.Ordinal)).Transferred = e.BytesReceived; } catch (Exception ex) { @@ -61,11 +61,11 @@ public partial class ApiController try { - await wc.DownloadFileTaskAsync(downloadUri, fileName); + await wc.DownloadFileTaskAsync(downloadUri, fileName).ConfigureAwait(false); } catch { } - CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = CurrentDownloads[downloadId].Single(f => f.Hash == hash).Total; + CurrentDownloads[downloadId].Single(f => string.Equals(f.Hash, hash, StringComparison.Ordinal)).Transferred = CurrentDownloads[downloadId].Single(f => string.Equals(f.Hash, hash, StringComparison.Ordinal)).Total; wc.DownloadProgressChanged -= progChanged; return fileName; @@ -78,7 +78,7 @@ public partial class ApiController DownloadStarted?.Invoke(); try { - await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct); + await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct).ConfigureAwait(false); } catch { @@ -94,8 +94,8 @@ public partial class ApiController { Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")"); - List downloadFileInfoFromService = new List(); - downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct)); + List downloadFileInfoFromService = new(); + downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct).ConfigureAwait(false)); Logger.Debug("Files with size 0 or less: " + string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash))); @@ -104,7 +104,7 @@ public partial class ApiController foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden)) { - if (ForbiddenTransfers.All(f => f.Hash != dto.Hash)) + if (ForbiddenTransfers.All(f => !string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal))) { ForbiddenTransfers.Add(new DownloadFileTransfer(dto)); } @@ -118,7 +118,7 @@ public partial class ApiController async (file, token) => { var hash = file.Hash; - var tempFile = await DownloadFile(currentDownloadId, file.Hash, file.DownloadUri, token); + var tempFile = await DownloadFile(currentDownloadId, file.Hash, file.DownloadUri, token).ConfigureAwait(false); if (token.IsCancellationRequested) { File.Delete(tempFile); @@ -128,16 +128,16 @@ public partial class ApiController return; } - var tempFileData = await File.ReadAllBytesAsync(tempFile, token); + var tempFileData = await File.ReadAllBytesAsync(tempFile, token).ConfigureAwait(false); var extractedFile = LZ4Codec.Unwrap(tempFileData); File.Delete(tempFile); var filePath = Path.Combine(_pluginConfiguration.CacheFolder, file.Hash); - await File.WriteAllBytesAsync(filePath, extractedFile, token); + await File.WriteAllBytesAsync(filePath, extractedFile, token).ConfigureAwait(false); var fi = new FileInfo(filePath); Func RandomDayFunc() { - DateTime start = new DateTime(1995, 1, 1); - Random gen = new Random(); + DateTime start = new(1995, 1, 1); + Random gen = new(); int range = (DateTime.Today - start).Days; return () => start.AddDays(gen.Next(range)); } @@ -155,7 +155,7 @@ public partial class ApiController Logger.Warn(ex.Message); Logger.Warn(ex.StackTrace); } - }); + }).ConfigureAwait(false); Logger.Debug("Download complete, removing " + currentDownloadId); CancelDownload(currentDownloadId); @@ -163,7 +163,7 @@ public partial class ApiController public async Task PushCharacterData(CharacterCacheDto character, List visibleCharacterIds) { - if (!IsConnected || SecretKey == "-") return; + if (!IsConnected || string.Equals(SecretKey, "-", StringComparison.Ordinal)) return; Logger.Debug("Sending Character data to service " + ApiUri); CancelUpload(); @@ -172,7 +172,7 @@ public partial class ApiController Logger.Verbose("New Token Created"); List unverifiedUploadHashes = new(); - foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct()).Distinct().ToList()) + foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct(StringComparer.Ordinal)).Distinct(StringComparer.Ordinal).ToList()) { if (!_verifiedUploadedHashes.Contains(item)) { @@ -183,7 +183,7 @@ public partial class ApiController if (unverifiedUploadHashes.Any()) { Logger.Debug("Verifying " + unverifiedUploadHashes.Count + " files"); - var filesToUpload = await _mareHub!.InvokeAsync>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken); + var filesToUpload = await _mareHub!.InvokeAsync>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken).ConfigureAwait(false); foreach (var file in filesToUpload.Where(f => !f.IsForbidden)) { @@ -203,7 +203,7 @@ public partial class ApiController foreach (var file in filesToUpload.Where(c => c.IsForbidden)) { - if (ForbiddenTransfers.All(f => f.Hash != file.Hash)) + if (ForbiddenTransfers.All(f => !string.Equals(f.Hash, file.Hash, StringComparison.Ordinal))) { ForbiddenTransfers.Add(new UploadFileTransfer(file) { @@ -217,9 +217,9 @@ public partial class ApiController foreach (var file in CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList()) { Logger.Debug("Compressing and uploading " + file); - var data = await GetCompressedFileData(file.Hash, uploadToken); - CurrentUploads.Single(e => e.Hash == data.Item1).Total = data.Item2.Length; - await UploadFile(data.Item2, file.Hash, uploadToken); + var data = await GetCompressedFileData(file.Hash, uploadToken).ConfigureAwait(false); + CurrentUploads.Single(e => string.Equals(e.Hash, data.Item1, StringComparison.Ordinal)).Total = data.Item2.Length; + await UploadFile(data.Item2, file.Hash, uploadToken).ConfigureAwait(false); if (!uploadToken.IsCancellationRequested) continue; Logger.Warn("Cancel in filesToUpload loop detected"); CurrentUploads.Clear(); @@ -233,12 +233,12 @@ public partial class ApiController } Logger.Debug("Upload tasks complete, waiting for server to confirm"); - var anyUploadsOpen = await _mareHub!.InvokeAsync(Api.InvokeFileIsUploadFinished, uploadToken); + var anyUploadsOpen = await _mareHub!.InvokeAsync(Api.InvokeFileIsUploadFinished, uploadToken).ConfigureAwait(false); Logger.Debug("Uploads open: " + anyUploadsOpen); while (anyUploadsOpen && !uploadToken.IsCancellationRequested) { - anyUploadsOpen = await _mareHub!.InvokeAsync(Api.InvokeFileIsUploadFinished, uploadToken); - await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken); + anyUploadsOpen = await _mareHub!.InvokeAsync(Api.InvokeFileIsUploadFinished, uploadToken).ConfigureAwait(false); + await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken).ConfigureAwait(false); Logger.Debug("Waiting for uploads to finish"); } @@ -257,7 +257,7 @@ public partial class ApiController if (!uploadToken.IsCancellationRequested) { Logger.Info("Pushing character data for " + character.GetHashCode() + " to " + string.Join(", ", visibleCharacterIds)); - StringBuilder sb = new StringBuilder(); + StringBuilder sb = new(); foreach (var item in character.FileReplacements) { sb.AppendLine($"FileReplacements for {item.Key}: {item.Value.Count}"); @@ -267,7 +267,7 @@ public partial class ApiController sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}"); } Logger.Debug("Chara data contained: " + Environment.NewLine + sb.ToString()); - await _mareHub!.InvokeAsync(Api.InvokeUserPushCharacterDataToVisibleClients, character, visibleCharacterIds, uploadToken); + await _mareHub!.InvokeAsync(Api.InvokeUserPushCharacterDataToVisibleClients, character, visibleCharacterIds, uploadToken).ConfigureAwait(false); } else { @@ -281,7 +281,7 @@ public partial class ApiController private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken) { var fileCache = _fileDbManager.GetFileCacheByHash(fileHash)!.ResolvedFilepath; - return (fileHash, LZ4Codec.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken), 0, + return (fileHash, LZ4Codec.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken).ConfigureAwait(false), 0, (int)new FileInfo(fileCache).Length)); } @@ -295,15 +295,15 @@ public partial class ApiController using var ms = new MemoryStream(compressedFile); var buffer = new byte[chunkSize]; int bytesRead; - while ((bytesRead = await ms.ReadAsync(buffer, 0, chunkSize, token)) > 0 && !token.IsCancellationRequested) + while ((bytesRead = await ms.ReadAsync(buffer, 0, chunkSize, token).ConfigureAwait(false)) > 0 && !token.IsCancellationRequested) { - CurrentUploads.Single(f => f.Hash == fileHash).Transferred += bytesRead; + CurrentUploads.Single(f => string.Equals(f.Hash, fileHash, StringComparison.Ordinal)).Transferred += bytesRead; token.ThrowIfCancellationRequested(); yield return bytesRead == chunkSize ? buffer.ToArray() : buffer.Take(bytesRead).ToArray(); } } - await _mareHub!.SendAsync(Api.SendFileUploadFileStreamAsync, fileHash, AsyncFileData(uploadToken), uploadToken); + await _mareHub!.SendAsync(Api.SendFileUploadFileStreamAsync, fileHash, AsyncFileData(uploadToken), uploadToken).ConfigureAwait(false); } public void CancelDownload(int downloadId) diff --git a/MareSynchronos/WebAPI/ApIController.Functions.Users.cs b/MareSynchronos/WebAPI/ApIController.Functions.Users.cs index 1e0223a..1b69cce 100644 --- a/MareSynchronos/WebAPI/ApIController.Functions.Users.cs +++ b/MareSynchronos/WebAPI/ApIController.Functions.Users.cs @@ -11,32 +11,32 @@ public partial class ApiController { _pluginConfiguration.ClientSecret.Remove(ApiUri); _pluginConfiguration.Save(); - await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles); - await _mareHub!.SendAsync(Api.SendUserDeleteAccount); - await CreateConnections(); + await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles).ConfigureAwait(false); + await _mareHub!.SendAsync(Api.SendUserDeleteAccount).ConfigureAwait(false); + await CreateConnections().ConfigureAwait(false); } public async Task> GetOnlineCharacters() { - return await _mareHub!.InvokeAsync>(Api.InvokeUserGetOnlineCharacters); + return await _mareHub!.InvokeAsync>(Api.InvokeUserGetOnlineCharacters).ConfigureAwait(false); } public async Task SendPairedClientAddition(string uid) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendUserPairedClientAddition, uid); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return; + await _mareHub!.SendAsync(Api.SendUserPairedClientAddition, uid).ConfigureAwait(false); } public async Task SendPairedClientPauseChange(string uid, bool paused) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendUserPairedClientPauseChange, uid, paused); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return; + await _mareHub!.SendAsync(Api.SendUserPairedClientPauseChange, uid, paused).ConfigureAwait(false); } public async Task SendPairedClientRemoval(string uid) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendUserPairedClientRemoval, uid); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return; + await _mareHub!.SendAsync(Api.SendUserPairedClientRemoval, uid).ConfigureAwait(false); } } diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs b/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs index c301460..c19df84 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Admin.cs @@ -10,27 +10,27 @@ public partial class ApiController { public async Task AddOrUpdateForbiddenFileEntry(ForbiddenFileDto forbiddenFile) { - await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile); + await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile).ConfigureAwait(false); } public async Task DeleteForbiddenFileEntry(ForbiddenFileDto forbiddenFile) { - await _mareHub!.SendAsync(Api.SendAdminDeleteForbiddenFile, forbiddenFile); + await _mareHub!.SendAsync(Api.SendAdminDeleteForbiddenFile, forbiddenFile).ConfigureAwait(false); } public async Task AddOrUpdateBannedUserEntry(BannedUserDto bannedUser) { - await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddBannedUser, bannedUser); + await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddBannedUser, bannedUser).ConfigureAwait(false); } public async Task DeleteBannedUserEntry(BannedUserDto bannedUser) { - await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser); + await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser).ConfigureAwait(false); } public async Task RefreshOnlineUsers() { - AdminOnlineUsers = await _mareHub!.InvokeAsync>(Api.InvokeAdminGetOnlineUsers); + AdminOnlineUsers = await _mareHub!.InvokeAsync>(Api.InvokeAdminGetOnlineUsers).ConfigureAwait(false); } public List AdminOnlineUsers { get; set; } = new List(); diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs index 851e888..04a8da9 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Callbacks.cs @@ -17,10 +17,10 @@ public partial class ApiController private void UpdateLocalClientPairsCallback(ClientPairDto dto) { - var entry = PairedClients.SingleOrDefault(e => e.OtherUID == dto.OtherUID); + var entry = PairedClients.SingleOrDefault(e => string.Equals(e.OtherUID, dto.OtherUID, System.StringComparison.Ordinal)); if (dto.IsRemoved) { - PairedClients.RemoveAll(p => p.OtherUID == dto.OtherUID); + PairedClients.RemoveAll(p => string.Equals(p.OtherUID, dto.OtherUID, System.StringComparison.Ordinal)); return; } if (entry == null) @@ -43,7 +43,7 @@ public partial class ApiController private void UpdateOrAddBannedUserCallback(BannedUserDto obj) { - var user = AdminBannedUsers.SingleOrDefault(b => b.CharacterHash == obj.CharacterHash); + var user = AdminBannedUsers.SingleOrDefault(b => string.Equals(b.CharacterHash, obj.CharacterHash, System.StringComparison.Ordinal)); if (user == null) { AdminBannedUsers.Add(obj); @@ -56,12 +56,12 @@ public partial class ApiController private void DeleteBannedUserCallback(BannedUserDto obj) { - AdminBannedUsers.RemoveAll(a => a.CharacterHash == obj.CharacterHash); + AdminBannedUsers.RemoveAll(a => string.Equals(a.CharacterHash, obj.CharacterHash, System.StringComparison.Ordinal)); } private void UpdateOrAddForbiddenFileCallback(ForbiddenFileDto obj) { - var user = AdminForbiddenFiles.SingleOrDefault(b => b.Hash == obj.Hash); + var user = AdminForbiddenFiles.SingleOrDefault(b => string.Equals(b.Hash, obj.Hash, System.StringComparison.Ordinal)); if (user == null) { AdminForbiddenFiles.Add(obj); @@ -74,18 +74,18 @@ public partial class ApiController private void DeleteForbiddenFileCallback(ForbiddenFileDto obj) { - AdminForbiddenFiles.RemoveAll(f => f.Hash == obj.Hash); + AdminForbiddenFiles.RemoveAll(f => string.Equals(f.Hash, obj.Hash, System.StringComparison.Ordinal)); } private void GroupPairChangedCallback(GroupPairDto dto) { if (dto.IsRemoved.GetValueOrDefault(false)) { - GroupPairedClients.RemoveAll(g => g.GroupGID == dto.GroupGID && g.UserUID == dto.UserUID); + GroupPairedClients.RemoveAll(g => string.Equals(g.GroupGID, dto.GroupGID, System.StringComparison.Ordinal) && string.Equals(g.UserUID, dto.UserUID, System.StringComparison.Ordinal)); return; } - var existingUser = GroupPairedClients.FirstOrDefault(f => f.GroupGID == dto.GroupGID && f.UserUID == dto.UserUID); + var existingUser = GroupPairedClients.FirstOrDefault(f => string.Equals(f.GroupGID, dto.GroupGID, System.StringComparison.Ordinal) && string.Equals(f.UserUID, dto.UserUID, System.StringComparison.Ordinal)); if (existingUser == null) { GroupPairedClients.Add(dto); @@ -101,16 +101,16 @@ public partial class ApiController { if (dto.IsDeleted.GetValueOrDefault(false)) { - Groups.RemoveAll(g => g.GID == dto.GID); - GroupPairedClients.RemoveAll(g => g.GroupGID == dto.GID); + Groups.RemoveAll(g => string.Equals(g.GID, dto.GID, System.StringComparison.Ordinal)); + GroupPairedClients.RemoveAll(g => string.Equals(g.GroupGID, dto.GID, System.StringComparison.Ordinal)); return; } - var existingGroup = Groups.FirstOrDefault(g => g.GID == dto.GID); + var existingGroup = Groups.FirstOrDefault(g => string.Equals(g.GID, dto.GID, System.StringComparison.Ordinal)); if (existingGroup == null) { Groups.Add(dto); - GroupPairedClients.AddRange(await _mareHub!.InvokeAsync>(Api.InvokeGroupGetUsersInGroup, dto.GID)); + GroupPairedClients.AddRange(await _mareHub!.InvokeAsync>(Api.InvokeGroupGetUsersInGroup, dto.GID).ConfigureAwait(false)); return; } diff --git a/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs b/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs index 40b3f07..0ba2e71 100644 --- a/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs +++ b/MareSynchronos/WebAPI/ApiController.Functions.Groups.cs @@ -9,79 +9,79 @@ public partial class ApiController { public async Task CreateGroup() { - if (!IsConnected || SecretKey == "-") return new GroupCreatedDto(); - return await _mareHub!.InvokeAsync(Api.InvokeGroupCreate); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return new GroupCreatedDto(); + return await _mareHub!.InvokeAsync(Api.InvokeGroupCreate).ConfigureAwait(false); } public async Task ChangeGroupPassword(string gid, string newpassword) { - if (!IsConnected || SecretKey == "-") return false; - return await _mareHub!.InvokeAsync(Api.InvokeGroupChangePassword, gid, newpassword); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return false; + return await _mareHub!.InvokeAsync(Api.InvokeGroupChangePassword, gid, newpassword).ConfigureAwait(false); } public async Task> GetGroups() { - if (!IsConnected || SecretKey == "-") return new List(); - return await _mareHub!.InvokeAsync>(Api.InvokeGroupGetGroups); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return new List(); + return await _mareHub!.InvokeAsync>(Api.InvokeGroupGetGroups).ConfigureAwait(false); } public async Task> GetUsersInGroup(string gid) { - if (!IsConnected || SecretKey == "-") return new List(); - return await _mareHub!.InvokeAsync>(Api.InvokeGroupGetUsersInGroup, gid); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return new List(); + return await _mareHub!.InvokeAsync>(Api.InvokeGroupGetUsersInGroup, gid).ConfigureAwait(false); } public async Task SendGroupJoin(string gid, string password) { - if (!IsConnected || SecretKey == "-") return false; - return await _mareHub!.InvokeAsync(Api.InvokeGroupJoin, gid, password); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return false; + return await _mareHub!.InvokeAsync(Api.InvokeGroupJoin, gid, password).ConfigureAwait(false); } public async Task SendGroupChangeInviteState(string gid, bool opened) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendGroupChangeInviteState, gid, opened); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return; + await _mareHub!.SendAsync(Api.SendGroupChangeInviteState, gid, opened).ConfigureAwait(false); } public async Task SendDeleteGroup(string gid) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendGroupDelete, gid); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return; + await _mareHub!.SendAsync(Api.SendGroupDelete, gid).ConfigureAwait(false); } public async Task SendChangeUserPinned(string gid, string uid, bool isPinned) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendGroupChangePinned, gid, uid, isPinned); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return; + await _mareHub!.SendAsync(Api.SendGroupChangePinned, gid, uid, isPinned).ConfigureAwait(false); } public async Task SendClearGroup(string gid) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendGroupClear, gid); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return; + await _mareHub!.SendAsync(Api.SendGroupClear, gid).ConfigureAwait(false); } public async Task SendLeaveGroup(string gid) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendGroupLeave, gid); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return; + await _mareHub!.SendAsync(Api.SendGroupLeave, gid).ConfigureAwait(false); } public async Task SendPauseGroup(string gid, bool isPaused) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendGroupPause, gid, isPaused); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return; + await _mareHub!.SendAsync(Api.SendGroupPause, gid, isPaused).ConfigureAwait(false); } public async Task SendRemoveUserFromGroup(string gid, string uid) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendGroupRemoveUser, gid, uid); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return; + await _mareHub!.SendAsync(Api.SendGroupRemoveUser, gid, uid).ConfigureAwait(false); } public async Task ChangeOwnerOfGroup(string gid, string uid) { - if (!IsConnected || SecretKey == "-") return; - await _mareHub!.SendAsync(Api.SendGroupChangeOwner, gid, uid); + if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return; + await _mareHub!.SendAsync(Api.SendGroupChangeOwner, gid, uid).ConfigureAwait(false); } } \ No newline at end of file diff --git a/MareSynchronos/WebAPI/ApiController.cs b/MareSynchronos/WebAPI/ApiController.cs index ac508cc..14c3bfc 100644 --- a/MareSynchronos/WebAPI/ApiController.cs +++ b/MareSynchronos/WebAPI/ApiController.cs @@ -33,8 +33,11 @@ public partial class ApiController : IDisposable private HubConnection? _mareHub; private CancellationTokenSource? _uploadCancellationTokenSource = new(); + private CancellationTokenSource? _healthCheckTokenSource = new(); private ConnectionDto? _connectionDto; + public ServerInfoDto ServerInfo => _connectionDto?.ServerInfo ?? new ServerInfoDto(); + public SystemInfoDto SystemInfoDto { get; private set; } = new(); public bool IsModerator => (_connectionDto?.IsAdmin ?? false) || (_connectionDto?.IsModerator ?? false); @@ -51,7 +54,7 @@ public partial class ApiController : IDisposable _dalamudUtil.LogIn += DalamudUtilOnLogIn; _dalamudUtil.LogOut += DalamudUtilOnLogOut; ServerState = ServerState.Offline; - _verifiedUploadedHashes = new(); + _verifiedUploadedHashes = new(StringComparer.Ordinal); if (_dalamudUtil.IsLoggedIn) { @@ -61,7 +64,7 @@ public partial class ApiController : IDisposable private void DalamudUtilOnLogOut() { - Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token)); + Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token).ConfigureAwait(false)); ServerState = ServerState.Offline; } @@ -108,10 +111,10 @@ public partial class ApiController : IDisposable public bool ServerAlive => ServerState is ServerState.Connected or ServerState.RateLimited or ServerState.Unauthorized or ServerState.Disconnected; - public Dictionary ServerDictionary => new Dictionary() + public Dictionary ServerDictionary => new Dictionary(StringComparer.Ordinal) { { MainServiceUri, MainServer } } .Concat(_pluginConfiguration.CustomServerList) - .ToDictionary(k => k.Key, k => k.Value); + .ToDictionary(k => k.Key, k => k.Value, StringComparer.Ordinal); public string UID => _connectionDto?.UID ?? string.Empty; public string DisplayName => _connectionDto?.UID ?? string.Empty; @@ -138,11 +141,11 @@ public partial class ApiController : IDisposable Logger.Info("Not recreating Connection, paused"); ServerState = ServerState.Disconnected; _connectionDto = null; - await StopConnection(_connectionCancellationTokenSource.Token); + await StopConnection(_connectionCancellationTokenSource.Token).ConfigureAwait(false); return; } - await StopConnection(_connectionCancellationTokenSource.Token); + await StopConnection(_connectionCancellationTokenSource.Token).ConfigureAwait(false); Logger.Info("Recreating Connection"); @@ -154,11 +157,11 @@ public partial class ApiController : IDisposable { if (string.IsNullOrEmpty(SecretKey)) { - await Task.Delay(TimeSpan.FromSeconds(2)); + await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false); continue; } - await StopConnection(token); + await StopConnection(token).ConfigureAwait(false); try { @@ -167,32 +170,32 @@ public partial class ApiController : IDisposable while (!_dalamudUtil.IsPlayerPresent && !token.IsCancellationRequested) { Logger.Debug("Player not loaded in yet, waiting"); - await Task.Delay(TimeSpan.FromSeconds(1), token); + await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false); } if (token.IsCancellationRequested) break; _mareHub = BuildHubConnection(Api.Path); - await _mareHub.StartAsync(token); + await _mareHub.StartAsync(token).ConfigureAwait(false); _mareHub.On(Api.OnUpdateSystemInfo, (dto) => SystemInfoDto = dto); _connectionDto = - await _mareHub.InvokeAsync(Api.InvokeHeartbeat, _dalamudUtil.PlayerNameHashed, token); + await _mareHub.InvokeAsync(Api.InvokeHeartbeat, _dalamudUtil.PlayerNameHashed, token).ConfigureAwait(false); ServerState = ServerState.Connected; if (_connectionDto.ServerVersion != Api.Version) { ServerState = ServerState.VersionMisMatch; - await StopConnection(token); + await StopConnection(token).ConfigureAwait(false); return; } if (ServerState is ServerState.Connected) // user is authorized && server is legit { - await InitializeData(token); + await InitializeData(token).ConfigureAwait(false); _mareHub.Closed += MareHubOnClosed; _mareHub.Reconnecting += MareHubOnReconnecting; @@ -206,7 +209,7 @@ public partial class ApiController : IDisposable Logger.Warn(ex.StackTrace ?? string.Empty); ServerState = ServerState.RateLimited; - await StopConnection(token); + await StopConnection(token).ConfigureAwait(false); return; } catch (HttpRequestException ex) @@ -218,14 +221,14 @@ public partial class ApiController : IDisposable if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized) { ServerState = ServerState.Unauthorized; - await StopConnection(token); + await StopConnection(token).ConfigureAwait(false); return; } else { ServerState = ServerState.Offline; Logger.Info("Failed to establish connection, retrying"); - await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token); + await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token).ConfigureAwait(false); } } catch (Exception ex) @@ -234,7 +237,7 @@ public partial class ApiController : IDisposable Logger.Warn(ex.Message); Logger.Warn(ex.StackTrace ?? string.Empty); Logger.Info("Failed to establish connection, retrying"); - await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token); + await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token).ConfigureAwait(false); } } } @@ -245,6 +248,21 @@ public partial class ApiController : IDisposable return Task.CompletedTask; } + private async Task ClientHealthCheck(CancellationToken ct) + { + while (!ct.IsCancellationRequested) + { + await Task.Delay(TimeSpan.FromSeconds(30), ct).ConfigureAwait(false); + if (ct.IsCancellationRequested) break; + var needsRestart = await _mareHub!.InvokeAsync(Api.InvokeCheckClientHealth, ct).ConfigureAwait(false); + Logger.Debug("Checked Client Health State, healthy: " + !needsRestart); + if (needsRestart) + { + _ = CreateConnections(); + } + } + } + private async Task InitializeData(CancellationToken token) { if (_mareHub == null) return; @@ -263,22 +281,22 @@ public partial class ApiController : IDisposable _mareHub.On(Api.OnGroupUserChange, GroupPairChangedCallback); PairedClients = - await _mareHub!.InvokeAsync>(Api.InvokeUserGetPairedClients, token); - Groups = await GetGroups(); + await _mareHub!.InvokeAsync>(Api.InvokeUserGetPairedClients, token).ConfigureAwait(false); + Groups = await GetGroups().ConfigureAwait(false); GroupPairedClients.Clear(); foreach (var group in Groups) { - GroupPairedClients.AddRange(await GetUsersInGroup(group.GID)); + GroupPairedClients.AddRange(await GetUsersInGroup(group.GID).ConfigureAwait(false)); } if (IsModerator) { AdminForbiddenFiles = await _mareHub.InvokeAsync>(Api.InvokeAdminGetForbiddenFiles, - token); + token).ConfigureAwait(false); AdminBannedUsers = await _mareHub.InvokeAsync>(Api.InvokeAdminGetBannedUsers, - token); + token).ConfigureAwait(false); _mareHub.On(Api.OnAdminUpdateOrAddBannedUser, UpdateOrAddBannedUserCallback); _mareHub.On(Api.OnAdminDeleteBannedUser, DeleteBannedUserCallback); @@ -288,6 +306,11 @@ public partial class ApiController : IDisposable DeleteForbiddenFileCallback); } + _healthCheckTokenSource?.Cancel(); + _healthCheckTokenSource?.Dispose(); + _healthCheckTokenSource = new CancellationTokenSource(); + _ = ClientHealthCheck(_healthCheckTokenSource.Token); + Connected?.Invoke(); } @@ -298,7 +321,7 @@ public partial class ApiController : IDisposable _dalamudUtil.LogIn -= DalamudUtilOnLogIn; _dalamudUtil.LogOut -= DalamudUtilOnLogOut; - Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token)); + Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token).ConfigureAwait(false)); _connectionCancellationTokenSource?.Cancel(); } @@ -332,6 +355,7 @@ public partial class ApiController : IDisposable private Task MareHubOnReconnecting(Exception? arg) { + _healthCheckTokenSource?.Cancel(); ServerState = ServerState.Disconnected; Logger.Warn("Connection closed... Reconnecting"); Logger.Warn(arg?.Message ?? string.Empty); @@ -350,8 +374,8 @@ public partial class ApiController : IDisposable _mareHub.Closed -= MareHubOnClosed; _mareHub.Reconnecting -= MareHubOnReconnecting; _mareHub.Reconnected -= MareHubOnReconnected; - await _mareHub.StopAsync(token); - await _mareHub.DisposeAsync(); + await _mareHub.StopAsync(token).ConfigureAwait(false); + await _mareHub.DisposeAsync().ConfigureAwait(false); CurrentUploads.Clear(); CurrentDownloads.Clear(); _uploadCancellationTokenSource?.Cancel(); @@ -363,7 +387,7 @@ public partial class ApiController : IDisposable { while (ServerState != ServerState.Offline) { - await Task.Delay(16); + await Task.Delay(16).ConfigureAwait(false); } } } From f68c52d0b002d83f47bed6212a75664babf2690e Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Mon, 3 Oct 2022 19:34:30 +0200 Subject: [PATCH 13/17] renaming of FileCache --- MareSynchronos/FileCache/FileCache.cs | 4 +-- MareSynchronos/FileCache/FileDbManager.cs | 30 +++++++++---------- .../FileCache/PeriodicFileScanner.cs | 4 +-- 3 files changed, 19 insertions(+), 19 deletions(-) diff --git a/MareSynchronos/FileCache/FileCache.cs b/MareSynchronos/FileCache/FileCache.cs index 1bd3b53..125173d 100644 --- a/MareSynchronos/FileCache/FileCache.cs +++ b/MareSynchronos/FileCache/FileCache.cs @@ -6,14 +6,14 @@ using System.Globalization; namespace MareSynchronos.FileCache; -public class FileCache +public class FileCacheEntity { public string ResolvedFilepath { get; private set; } = string.Empty; public string Hash { get; set; } public string PrefixedFilePath { get; init; } public string LastModifiedDateTicks { get; set; } - public FileCache(string hash, string path, string lastModifiedDateTicks) + public FileCacheEntity(string hash, string path, string lastModifiedDateTicks) { Hash = hash; PrefixedFilePath = path; diff --git a/MareSynchronos/FileCache/FileDbManager.cs b/MareSynchronos/FileCache/FileDbManager.cs index dd71bd4..b110675 100644 --- a/MareSynchronos/FileCache/FileDbManager.cs +++ b/MareSynchronos/FileCache/FileDbManager.cs @@ -26,7 +26,7 @@ public class FileCacheManager : IDisposable private readonly Configuration _configuration; private readonly string CsvPath; private string CsvBakPath => CsvPath + ".bak"; - private readonly ConcurrentDictionary FileCaches = new(StringComparer.Ordinal); + private readonly ConcurrentDictionary FileCaches = new(StringComparer.Ordinal); public const string CsvSplit = "|"; private object _fileWriteLock = new(); @@ -52,7 +52,7 @@ public class FileCacheManager : IDisposable var hash = splittedEntry[0]; var path = splittedEntry[1]; var time = splittedEntry[2]; - FileCaches[path] = new FileCache(hash, path, time); + FileCaches[path] = new FileCacheEntity(hash, path, time); } catch (Exception) { @@ -87,9 +87,9 @@ public class FileCacheManager : IDisposable } } - public List GetAllFileCaches() => FileCaches.Values.ToList(); + public List GetAllFileCaches() => FileCaches.Values.ToList(); - public FileCache? GetFileCacheByHash(string hash) + public FileCacheEntity? GetFileCacheByHash(string hash) { if (FileCaches.Any(f => string.Equals(f.Value.Hash, hash, StringComparison.Ordinal))) { @@ -99,7 +99,7 @@ public class FileCacheManager : IDisposable return null; } - public (FileState, FileCache) ValidateFileCacheEntity(FileCache fileCache) + public (FileState, FileCacheEntity) ValidateFileCacheEntity(FileCacheEntity fileCache) { fileCache = ReplacePathPrefixes(fileCache); FileInfo fi = new(fileCache.ResolvedFilepath); @@ -115,7 +115,7 @@ public class FileCacheManager : IDisposable return (FileState.Valid, fileCache); } - public FileCache? GetFileCacheByPath(string path) + public FileCacheEntity? GetFileCacheByPath(string path) { var cleanedPath = path.Replace("/", "\\", StringComparison.Ordinal).ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), "", StringComparison.Ordinal); var entry = FileCaches.FirstOrDefault(f => f.Value.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.Ordinal)).Value; @@ -131,7 +131,7 @@ public class FileCacheManager : IDisposable return validatedCacheEntry; } - public FileCache? CreateCacheEntry(string path) + public FileCacheEntity? CreateCacheEntry(string path) { Logger.Debug("Creating cache entry for " + path); FileInfo fi = new(path); @@ -142,7 +142,7 @@ public class FileCacheManager : IDisposable return CreateFileCacheEntity(fi, prefixedPath, fi.Name.ToUpper(CultureInfo.InvariantCulture)); } - public FileCache? CreateFileEntry(string path) + public FileCacheEntity? CreateFileEntry(string path) { Logger.Debug("Creating file entry for " + path); FileInfo fi = new(path); @@ -153,13 +153,13 @@ public class FileCacheManager : IDisposable return CreateFileCacheEntity(fi, prefixedPath); } - private FileCache? CreateFileCacheEntity(FileInfo fileInfo, string prefixedPath, string? hash = null) + private FileCacheEntity? CreateFileCacheEntity(FileInfo fileInfo, string prefixedPath, string? hash = null) { if (hash == null) { hash = Crypto.GetFileHash(fileInfo.FullName); } - var entity = new FileCache(hash, prefixedPath, fileInfo.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture)); + var entity = new FileCacheEntity(hash, prefixedPath, fileInfo.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture)); entity = ReplacePathPrefixes(entity); FileCaches[prefixedPath] = entity; lock (_fileWriteLock) @@ -171,14 +171,14 @@ public class FileCacheManager : IDisposable return result; } - private FileCache? GetValidatedFileCache(FileCache fileCache) + private FileCacheEntity? GetValidatedFileCache(FileCacheEntity fileCache) { var resulingFileCache = ReplacePathPrefixes(fileCache); resulingFileCache = Validate(resulingFileCache); return resulingFileCache; } - private FileCache? Validate(FileCache fileCache) + private FileCacheEntity? Validate(FileCacheEntity fileCache) { var file = new FileInfo(fileCache.ResolvedFilepath); if (!file.Exists) @@ -195,12 +195,12 @@ public class FileCacheManager : IDisposable return fileCache; } - public void RemoveHash(FileCache entity) + public void RemoveHash(FileCacheEntity entity) { FileCaches.Remove(entity.Hash, out _); } - public void UpdateHash(FileCache fileCache) + public void UpdateHash(FileCacheEntity fileCache) { Logger.Debug("Updating hash for " + fileCache.ResolvedFilepath); fileCache.Hash = Crypto.GetFileHash(fileCache.ResolvedFilepath); @@ -209,7 +209,7 @@ public class FileCacheManager : IDisposable FileCaches[fileCache.PrefixedFilePath] = fileCache; } - private FileCache ReplacePathPrefixes(FileCache fileCache) + private FileCacheEntity ReplacePathPrefixes(FileCacheEntity fileCache) { if (fileCache.PrefixedFilePath.StartsWith(PenumbraPrefix, StringComparison.OrdinalIgnoreCase)) { diff --git a/MareSynchronos/FileCache/PeriodicFileScanner.cs b/MareSynchronos/FileCache/PeriodicFileScanner.cs index 59d01ef..37a42d9 100644 --- a/MareSynchronos/FileCache/PeriodicFileScanner.cs +++ b/MareSynchronos/FileCache/PeriodicFileScanner.cs @@ -221,8 +221,8 @@ public class PeriodicFileScanner : IDisposable var cpuCount = (int)(Environment.ProcessorCount / 2.0f); Task[] dbTasks = Enumerable.Range(0, cpuCount).Select(c => Task.CompletedTask).ToArray(); - ConcurrentBag entitiesToRemove = new(); - ConcurrentBag entitiesToUpdate = new(); + ConcurrentBag entitiesToRemove = new(); + ConcurrentBag entitiesToUpdate = new(); try { foreach (var cache in _fileDbManager.GetAllFileCaches()) From e984304c1bc2c42c37c969116f2d7e20493642f8 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Tue, 4 Oct 2022 01:03:05 +0200 Subject: [PATCH 14/17] fix showing alias when empty --- MareSynchronos/UI/GroupPanel.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/MareSynchronos/UI/GroupPanel.cs b/MareSynchronos/UI/GroupPanel.cs index cceb985..0261b94 100644 --- a/MareSynchronos/UI/GroupPanel.cs +++ b/MareSynchronos/UI/GroupPanel.cs @@ -163,7 +163,7 @@ namespace MareSynchronos.UI ? 1 : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _mainUi.TransferPartHeight - ImGui.GetCursorPosY(); ImGui.BeginChild("list", new Vector2(_mainUi._windowContentWidth, ySize), false); - foreach (var entry in _apiController.Groups.OrderBy(g => g.Alias ?? g.GID).ToList()) + foreach (var entry in _apiController.Groups.OrderBy(g => string.IsNullOrEmpty(g.Alias) ? g.GID : g.Alias).ToList()) { UiShared.DrawWithID(entry.GID, () => DrawSyncshell(entry)); } @@ -197,7 +197,7 @@ namespace MareSynchronos.UI UiShared.AttachToolTip(((group.IsPaused ?? false) ? "Resume" : "Pause") + " pairing with all users in this Syncshell"); ImGui.SameLine(); - var groupName = group.Alias ?? group.GID; + var groupName = string.IsNullOrEmpty(group.Alias) ? group.GID : group.Alias; var textIsGid = true; if (string.Equals(group.OwnedBy, _apiController.UID, StringComparison.Ordinal)) @@ -316,7 +316,7 @@ namespace MareSynchronos.UI if (UiShared.IconTextButton(FontAwesomeIcon.Copy, "Copy ID")) { - ImGui.SetClipboardText(entry.Alias ?? entry.GID); + ImGui.SetClipboardText(string.IsNullOrEmpty(entry.Alias) ? entry.GID : entry.Alias); } UiShared.AttachToolTip("Copy Syncshell ID to Clipboard"); From 7f089f458ca4e189cb943d31e4b046dfea8e38b3 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Tue, 4 Oct 2022 01:18:41 +0200 Subject: [PATCH 15/17] center shard display, fix order of users in group --- MareSynchronos/UI/CompactUI.cs | 15 ++++++++------- MareSynchronos/UI/GroupPanel.cs | 2 +- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 6cc3eb9..9ccf93e 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -437,9 +437,9 @@ public class CompactUi : Window, IDisposable var userSize = ImGui.CalcTextSize(userCount); var textSize = ImGui.CalcTextSize("Users Online"); #if DEBUG - string shardConnection = $"Connected shard: {_apiController.ServerInfo.ShardName}"; + string shardConnection = $"Shard: {_apiController.ServerInfo.ShardName}"; #else - string shardConnection = string.Equals(_apiController.ServerInfo.ShardName, "Main", StringComparison.OrdinalIgnoreCase) ? string.Empty : $"Connected shard: {_apiController.ServerInfo.ShardName}"; + string shardConnection = string.Equals(_apiController.ServerInfo.ShardName, "Main", StringComparison.OrdinalIgnoreCase) ? string.Empty : $"Shard: {_apiController.ServerInfo.ShardName}"; #endif var shardTextSize = ImGui.CalcTextSize(shardConnection); @@ -451,11 +451,6 @@ public class CompactUi : Window, IDisposable ImGui.SameLine(); ImGui.AlignTextToFramePadding(); ImGui.Text("Users Online"); - ImGui.AlignTextToFramePadding(); - if (!string.IsNullOrEmpty(shardConnection)) - { - ImGui.TextUnformatted(shardConnection); - } } else { @@ -476,6 +471,12 @@ public class CompactUi : Window, IDisposable } ImGui.PopStyleColor(); UiShared.AttachToolTip(!_configuration.FullPause ? "Disconnect from " + _apiController.ServerDictionary[_configuration.ApiUri] : "Connect to " + _apiController.ServerDictionary[_configuration.ApiUri]); + + if (!string.IsNullOrEmpty(shardConnection)) + { + ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth()) / 2 - shardTextSize.X / 2); + ImGui.TextUnformatted(shardConnection); + } } private void DrawTransfers() diff --git a/MareSynchronos/UI/GroupPanel.cs b/MareSynchronos/UI/GroupPanel.cs index 0261b94..37565b8 100644 --- a/MareSynchronos/UI/GroupPanel.cs +++ b/MareSynchronos/UI/GroupPanel.cs @@ -270,7 +270,7 @@ namespace MareSynchronos.UI ImGui.Indent(collapseButton.X); if (ExpandedGroupState[group.GID]) { - pairsInGroup = pairsInGroup.OrderBy(p => string.Equals(p.UserUID, group.OwnedBy, StringComparison.Ordinal) ? 0 : 1).ThenBy(p => p.IsPinned ?? false).ThenBy(p => p.UserAlias ?? p.UserUID).ToList(); + pairsInGroup = pairsInGroup.OrderBy(p => string.Equals(p.UserUID, group.OwnedBy, StringComparison.Ordinal) ? 0 : 1).ThenBy(p => p.IsPinned ?? false ? 0 : 1).ThenBy(p => p.UserAlias ?? p.UserUID).ToList(); ImGui.Indent(ImGui.GetStyle().ItemSpacing.X / 2); ImGui.Separator(); foreach (var pair in pairsInGroup) From d6a434b2f01aa46260c318a0917338bb9da48464 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Tue, 4 Oct 2022 02:02:11 +0200 Subject: [PATCH 16/17] don't display shard if shardname is not set --- MareSynchronos/UI/CompactUI.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index 9ccf93e..f58ad2c 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -472,7 +472,7 @@ public class CompactUi : Window, IDisposable ImGui.PopStyleColor(); UiShared.AttachToolTip(!_configuration.FullPause ? "Disconnect from " + _apiController.ServerDictionary[_configuration.ApiUri] : "Connect to " + _apiController.ServerDictionary[_configuration.ApiUri]); - if (!string.IsNullOrEmpty(shardConnection)) + if (!string.IsNullOrEmpty(_apiController.ServerInfo.ShardName)) { ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth()) / 2 - shardTextSize.X / 2); ImGui.TextUnformatted(shardConnection); From 9df89adac8a954e9055e6ae05fc971e64558c579 Mon Sep 17 00:00:00 2001 From: Stanley Dimant Date: Tue, 4 Oct 2022 14:14:56 +0200 Subject: [PATCH 17/17] adjust some of the logging --- MareSynchronos/Factories/CharacterDataFactory.cs | 2 +- MareSynchronos/FileCache/FileDbManager.cs | 4 ++-- MareSynchronos/FileCache/PeriodicFileScanner.cs | 4 ++-- MareSynchronos/Managers/TransientResourceManager.cs | 2 +- MareSynchronos/UI/CompactUI.cs | 2 +- MareSynchronos/WebAPI/ApiController.cs | 1 + 6 files changed, 8 insertions(+), 7 deletions(-) diff --git a/MareSynchronos/Factories/CharacterDataFactory.cs b/MareSynchronos/Factories/CharacterDataFactory.cs index efab6a1..f2cadd0 100644 --- a/MareSynchronos/Factories/CharacterDataFactory.cs +++ b/MareSynchronos/Factories/CharacterDataFactory.cs @@ -328,7 +328,7 @@ public class CharacterDataFactory var gamePath = item.GamePaths.First().ToLowerInvariant(); if (string.Equals(penumResolve, gamePath, StringComparison.Ordinal)) { - Logger.Debug("PenumResolve was same as GamePath, not adding " + item); + Logger.Verbose("PenumResolve was same as GamePath, not adding " + item); transientResourceManager.RemoveTransientResource(charaPointer, item); } else diff --git a/MareSynchronos/FileCache/FileDbManager.cs b/MareSynchronos/FileCache/FileDbManager.cs index b110675..e2dee5c 100644 --- a/MareSynchronos/FileCache/FileDbManager.cs +++ b/MareSynchronos/FileCache/FileDbManager.cs @@ -133,7 +133,7 @@ public class FileCacheManager : IDisposable public FileCacheEntity? CreateCacheEntry(string path) { - Logger.Debug("Creating cache entry for " + path); + Logger.Verbose("Creating cache entry for " + path); FileInfo fi = new(path); if (!fi.Exists) return null; var fullName = fi.FullName.ToLowerInvariant(); @@ -144,7 +144,7 @@ public class FileCacheManager : IDisposable public FileCacheEntity? CreateFileEntry(string path) { - Logger.Debug("Creating file entry for " + path); + Logger.Verbose("Creating file entry for " + path); FileInfo fi = new(path); if (!fi.Exists) return null; var fullName = fi.FullName.ToLowerInvariant(); diff --git a/MareSynchronos/FileCache/PeriodicFileScanner.cs b/MareSynchronos/FileCache/PeriodicFileScanner.cs index 37a42d9..df737dc 100644 --- a/MareSynchronos/FileCache/PeriodicFileScanner.cs +++ b/MareSynchronos/FileCache/PeriodicFileScanner.cs @@ -283,7 +283,7 @@ public class PeriodicFileScanner : IDisposable _fileDbManager.WriteOutFullCsv(); } - Logger.Debug("Scanner validated existing db files"); + Logger.Verbose("Scanner validated existing db files"); if (ct.IsCancellationRequested) return; @@ -314,7 +314,7 @@ public class PeriodicFileScanner : IDisposable Task.WaitAll(dbTasks); - Logger.Debug("Scanner added new files to db"); + Logger.Verbose("Scanner added new files to db"); Logger.Debug("Scan complete"); TotalFiles = 0; diff --git a/MareSynchronos/Managers/TransientResourceManager.cs b/MareSynchronos/Managers/TransientResourceManager.cs index 97d02be..d82c61a 100644 --- a/MareSynchronos/Managers/TransientResourceManager.cs +++ b/MareSynchronos/Managers/TransientResourceManager.cs @@ -109,7 +109,7 @@ public class TransientResourceManager : IDisposable SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f.GamePaths.First(), replacedGamePath , StringComparison.OrdinalIgnoreCase) && string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase)))) { - Logger.Debug("Not adding " + replacedGamePath + ":" + filePath); + Logger.Verbose("Not adding " + replacedGamePath + ":" + filePath); Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase) && string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase))).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath)); } diff --git a/MareSynchronos/UI/CompactUI.cs b/MareSynchronos/UI/CompactUI.cs index f58ad2c..d0162c1 100644 --- a/MareSynchronos/UI/CompactUI.cs +++ b/MareSynchronos/UI/CompactUI.cs @@ -159,7 +159,7 @@ public class CompactUi : Window, IDisposable { var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X); - ImGui.InputTextWithHint("##otheruid", "Other players UID/GID", ref _pairToAdd, 10); + ImGui.InputTextWithHint("##otheruid", "Other players UID/Alias", ref _pairToAdd, 20); ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X); if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) { diff --git a/MareSynchronos/WebAPI/ApiController.cs b/MareSynchronos/WebAPI/ApiController.cs index 14c3bfc..a84923f 100644 --- a/MareSynchronos/WebAPI/ApiController.cs +++ b/MareSynchronos/WebAPI/ApiController.cs @@ -355,6 +355,7 @@ public partial class ApiController : IDisposable private Task MareHubOnReconnecting(Exception? arg) { + _connectionDto = null; _healthCheckTokenSource?.Cancel(); ServerState = ServerState.Disconnected; Logger.Warn("Connection closed... Reconnecting");