102
.editorconfig
Normal file
102
.editorconfig
Normal file
@@ -0,0 +1,102 @@
|
||||
[*.cs]
|
||||
|
||||
# MA0046: Use EventHandler<T> 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
|
||||
2
MareAPI
2
MareAPI
Submodule MareAPI updated: 9dc1e901aa...2d5d9d9d1c
@@ -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
|
||||
|
||||
@@ -7,10 +7,10 @@ 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)
|
||||
{
|
||||
return configuration.AcceptedAgreement && configuration.InitialScanComplete
|
||||
@@ -23,23 +23,40 @@ namespace MareSynchronos
|
||||
{
|
||||
return configuration.UidServerComments.ContainsKey(configuration.ApiUri)
|
||||
? configuration.UidServerComments[configuration.ApiUri]
|
||||
: new Dictionary<string, string>();
|
||||
: new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
public static Dictionary<string, string> GetCurrentServerGidComments(this Configuration configuration)
|
||||
{
|
||||
return configuration.GidServerComments.ContainsKey(configuration.ApiUri)
|
||||
? configuration.GidServerComments[configuration.ApiUri]
|
||||
: new Dictionary<string, string>(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<string, string>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
configuration.GidServerComments[configuration.ApiUri][gid] = comment;
|
||||
}
|
||||
|
||||
public static void SetCurrentServerUidComment(this Configuration configuration, string uid, string comment)
|
||||
{
|
||||
if (!configuration.UidServerComments.ContainsKey(configuration.ApiUri))
|
||||
{
|
||||
configuration.UidServerComments[configuration.ApiUri] = new Dictionary<string, string>();
|
||||
configuration.UidServerComments[configuration.ApiUri] = new Dictionary<string, string>(StringComparer.Ordinal);
|
||||
}
|
||||
|
||||
configuration.UidServerComments[configuration.ApiUri][uid] = comment;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
[Serializable]
|
||||
public class Configuration : IPluginConfiguration
|
||||
{
|
||||
private string _apiUri = string.Empty;
|
||||
private int _maxParallelScan = 10;
|
||||
[NonSerialized]
|
||||
@@ -54,10 +71,10 @@ namespace MareSynchronos
|
||||
}
|
||||
|
||||
public string CacheFolder { get; set; } = string.Empty;
|
||||
public Dictionary<string, string> ClientSecret { get; set; } = new();
|
||||
public Dictionary<string, string> CustomServerList { get; set; } = new();
|
||||
public Dictionary<string, string> ClientSecret { get; set; } = new(StringComparer.Ordinal);
|
||||
public Dictionary<string, string> CustomServerList { get; set; } = new(StringComparer.Ordinal);
|
||||
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;
|
||||
@@ -65,9 +82,10 @@ namespace MareSynchronos
|
||||
public bool InitialScanComplete { get; set; } = false;
|
||||
|
||||
public bool FullPause { get; set; } = false;
|
||||
public Dictionary<string, Dictionary<string, string>> UidServerComments { get; set; } = new();
|
||||
public Dictionary<string, Dictionary<string, string>> UidServerComments { get; set; } = new(StringComparer.Ordinal);
|
||||
public Dictionary<string, Dictionary<string, string>> GidServerComments { get; set; } = new(StringComparer.Ordinal);
|
||||
|
||||
public Dictionary<string, string> UidComments { get; set; } = new();
|
||||
public Dictionary<string, string> UidComments { get; set; } = new(StringComparer.Ordinal);
|
||||
public int Version { get; set; } = 5;
|
||||
|
||||
public bool ShowTransferWindow { get; set; } = true;
|
||||
@@ -96,10 +114,10 @@ namespace MareSynchronos
|
||||
{
|
||||
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))
|
||||
{
|
||||
@@ -110,7 +128,7 @@ namespace MareSynchronos
|
||||
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();
|
||||
}
|
||||
@@ -118,10 +136,10 @@ namespace MareSynchronos
|
||||
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))
|
||||
{
|
||||
@@ -135,7 +153,7 @@ namespace MareSynchronos
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -159,10 +177,10 @@ namespace MareSynchronos
|
||||
{
|
||||
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))
|
||||
{
|
||||
@@ -176,7 +194,7 @@ namespace MareSynchronos
|
||||
|
||||
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);
|
||||
}
|
||||
@@ -189,7 +207,7 @@ namespace MareSynchronos
|
||||
{
|
||||
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");
|
||||
|
||||
@@ -197,5 +215,4 @@ namespace MareSynchronos
|
||||
Save();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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,13 +322,13 @@ 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);
|
||||
Logger.Verbose("PenumResolve was same as GamePath, not adding " + item);
|
||||
transientResourceManager.RemoveTransientResource(charaPointer, item);
|
||||
}
|
||||
else
|
||||
|
||||
@@ -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;
|
||||
@@ -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)}";
|
||||
|
||||
@@ -26,9 +26,9 @@ public class FileCacheManager : IDisposable
|
||||
private readonly Configuration _configuration;
|
||||
private readonly string CsvPath;
|
||||
private string CsvBakPath => CsvPath + ".bak";
|
||||
private readonly ConcurrentDictionary<string, FileCache> FileCaches = new();
|
||||
private readonly ConcurrentDictionary<string, FileCacheEntity> 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)
|
||||
{
|
||||
@@ -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)
|
||||
{
|
||||
@@ -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);
|
||||
@@ -87,19 +87,19 @@ public class FileCacheManager : IDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public List<FileCache> GetAllFileCaches() => FileCaches.Values.ToList();
|
||||
public List<FileCacheEntity> GetAllFileCaches() => FileCaches.Values.ToList();
|
||||
|
||||
public FileCache? GetFileCacheByHash(string hash)
|
||||
public FileCacheEntity? 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;
|
||||
}
|
||||
|
||||
public (FileState, FileCache) ValidateFileCacheEntity(FileCache fileCache)
|
||||
public (FileState, FileCacheEntity) ValidateFileCacheEntity(FileCacheEntity fileCache)
|
||||
{
|
||||
fileCache = ReplacePathPrefixes(fileCache);
|
||||
FileInfo fi = new(fileCache.ResolvedFilepath);
|
||||
@@ -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);
|
||||
}
|
||||
@@ -115,10 +115,10 @@ public class FileCacheManager : IDisposable
|
||||
return (FileState.Valid, fileCache);
|
||||
}
|
||||
|
||||
public FileCache? GetFileCacheByPath(string path)
|
||||
public FileCacheEntity? 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)
|
||||
{
|
||||
@@ -131,35 +131,35 @@ public class FileCacheManager : IDisposable
|
||||
return validatedCacheEntry;
|
||||
}
|
||||
|
||||
public FileCache? CreateCacheEntry(string path)
|
||||
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();
|
||||
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)
|
||||
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();
|
||||
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);
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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,15 +209,15 @@ public class FileCacheManager : IDisposable
|
||||
FileCaches[fileCache.PrefixedFilePath] = fileCache;
|
||||
}
|
||||
|
||||
private FileCache ReplacePathPrefixes(FileCache fileCache)
|
||||
private FileCacheEntity ReplacePathPrefixes(FileCacheEntity 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;
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.IO;
|
||||
using System.Linq;
|
||||
using System.Threading;
|
||||
@@ -19,7 +20,7 @@ public class PeriodicFileScanner : IDisposable
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
private CancellationTokenSource? _scanCancellationTokenSource;
|
||||
private Task? _fileScannerTask = null;
|
||||
public ConcurrentDictionary<string, int> haltScanLocks = new();
|
||||
public ConcurrentDictionary<string, int> haltScanLocks = new(StringComparer.Ordinal);
|
||||
public PeriodicFileScanner(IpcManager ipcManager, Configuration pluginConfiguration, FileCacheManager fileDbManager, ApiController apiController, DalamudUtil dalamudUtil)
|
||||
{
|
||||
Logger.Verbose("Creating " + nameof(PeriodicFileScanner));
|
||||
@@ -127,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();
|
||||
@@ -143,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);
|
||||
}
|
||||
}
|
||||
@@ -203,13 +204,16 @@ 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<string, bool>(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())
|
||||
.ToDictionary(c => c, c => false);
|
||||
.Select(c => new KeyValuePair<string, bool>(c, false)), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
TotalFiles = scannedFiles.Count;
|
||||
|
||||
@@ -217,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<FileCache> entitiesToRemove = new();
|
||||
ConcurrentBag<FileCache> entitiesToUpdate = new();
|
||||
ConcurrentBag<FileCacheEntity> entitiesToRemove = new();
|
||||
ConcurrentBag<FileCacheEntity> entitiesToUpdate = new();
|
||||
try
|
||||
{
|
||||
foreach (var cache in _fileDbManager.GetAllFileCaches())
|
||||
@@ -280,7 +284,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;
|
||||
|
||||
@@ -311,7 +315,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;
|
||||
|
||||
@@ -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
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct WeaponDrawObject
|
||||
{
|
||||
[FieldOffset(0x00)] public RenderModel* RenderModel;
|
||||
}
|
||||
}
|
||||
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct HumanExt
|
||||
{
|
||||
[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
|
||||
{
|
||||
[StructLayout(LayoutKind.Explicit)]
|
||||
public unsafe struct CharaExt
|
||||
{
|
||||
[FieldOffset(0x0)] public Character Character;
|
||||
[FieldOffset(0x650)] public Character* Mount;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
using CheapLoc;
|
||||
|
||||
namespace MareSynchronos.Localization
|
||||
namespace MareSynchronos.Localization;
|
||||
|
||||
public static class Strings
|
||||
{
|
||||
public static class Strings
|
||||
{
|
||||
public class ToSStrings
|
||||
{
|
||||
public readonly string LanguageLabel = Loc.Localize("LanguageLabel", "Language");
|
||||
@@ -64,5 +64,4 @@ namespace MareSynchronos.Localization
|
||||
}
|
||||
|
||||
public static ToSStrings ToS { get; set; } = new();
|
||||
}
|
||||
}
|
||||
@@ -71,7 +71,7 @@ public class CachedPlayer
|
||||
if (characterData.GetHashCode() == _cachedData.GetHashCode()) return;
|
||||
|
||||
bool updateModdedPaths = false;
|
||||
List<ObjectKind> charaDataToUpdate = new List<ObjectKind>();
|
||||
List<ObjectKind> charaDataToUpdate = new();
|
||||
foreach (var objectKind in Enum.GetValues<ObjectKind>())
|
||||
{
|
||||
_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<FileReplacementDto> TryCalculateModdedDictionary(out Dictionary<string, string> moddedDictionary)
|
||||
{
|
||||
List<FileReplacementDto> missingFiles = new();
|
||||
moddedDictionary = new Dictionary<string, string>();
|
||||
moddedDictionary = new Dictionary<string, string>(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(() =>
|
||||
|
||||
@@ -8,13 +8,13 @@ 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<int> _glamourerApiVersion;
|
||||
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyAll;
|
||||
private readonly ICallGateSubscriber<GameObject?, string>? _glamourerGetAllCustomization;
|
||||
@@ -158,7 +158,7 @@ namespace MareSynchronos.Managers
|
||||
{
|
||||
try
|
||||
{
|
||||
return _heelsGetApiVersion.InvokeFunc() == "1.0.1";
|
||||
return string.Equals(_heelsGetApiVersion.InvokeFunc(), "1.0.1", StringComparison.Ordinal);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -397,5 +397,4 @@ namespace MareSynchronos.Managers
|
||||
PenumbraDisposed?.Invoke();
|
||||
actionQueue.Clear();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,8 +19,8 @@ public class OnlinePlayerManager : IDisposable
|
||||
private readonly IpcManager _ipcManager;
|
||||
private readonly PlayerManager _playerManager;
|
||||
private readonly FileCacheManager _fileDbManager;
|
||||
private readonly ConcurrentDictionary<string, CachedPlayer> _onlineCachedPlayers = new();
|
||||
private readonly ConcurrentDictionary<string, CharacterCacheDto> _temporaryStoredCharacterCache = new();
|
||||
private readonly ConcurrentDictionary<string, CachedPlayer> _onlineCachedPlayers = new(StringComparer.Ordinal);
|
||||
private readonly ConcurrentDictionary<string, CharacterCacheDto> _temporaryStoredCharacterCache = new(StringComparer.Ordinal);
|
||||
private readonly ConcurrentDictionary<CachedPlayer, CancellationTokenSource> _playerTokenDisposal = new();
|
||||
|
||||
private List<string> OnlineVisiblePlayerHashes => _onlineCachedPlayers.Select(p => p.Value).Where(p => p.PlayerCharacter != IntPtr.Zero)
|
||||
@@ -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))
|
||||
@@ -244,7 +226,7 @@ public class OnlinePlayerManager : IDisposable
|
||||
Task.Run(async () =>
|
||||
{
|
||||
await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData,
|
||||
visiblePlayers);
|
||||
visiblePlayers).ConfigureAwait(false);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,12 +14,12 @@ using MareSynchronos.FileCache;
|
||||
using Newtonsoft.Json;
|
||||
#endif
|
||||
|
||||
namespace MareSynchronos.Managers
|
||||
{
|
||||
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
|
||||
namespace MareSynchronos.Managers;
|
||||
|
||||
public class PlayerManager : IDisposable
|
||||
{
|
||||
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
|
||||
|
||||
public class PlayerManager : IDisposable
|
||||
{
|
||||
private readonly ApiController _apiController;
|
||||
private readonly CharacterDataFactory _characterDataFactory;
|
||||
private readonly DalamudUtil _dalamudUtil;
|
||||
@@ -34,7 +34,7 @@ namespace MareSynchronos.Managers
|
||||
private CancellationTokenSource? _playerChangedCts = new();
|
||||
private CancellationTokenSource _transientUpdateCts = new();
|
||||
|
||||
private List<PlayerRelatedObject> playerRelatedObjects = new List<PlayerRelatedObject>();
|
||||
private List<PlayerRelatedObject> playerRelatedObjects = new();
|
||||
|
||||
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
|
||||
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager,
|
||||
@@ -88,7 +88,7 @@ namespace MareSynchronos.Managers
|
||||
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 @@ namespace MareSynchronos.Managers
|
||||
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 @@ namespace MareSynchronos.Managers
|
||||
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);
|
||||
@@ -234,6 +234,9 @@ namespace MareSynchronos.Managers
|
||||
}
|
||||
|
||||
Task.Run(async () =>
|
||||
{
|
||||
CharacterCacheDto? cacheDto = null;
|
||||
try
|
||||
{
|
||||
_periodicFileScanner.HaltScan("Character creation");
|
||||
foreach (var item in unprocessedObjects)
|
||||
@@ -241,8 +244,13 @@ namespace MareSynchronos.Managers
|
||||
_dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, 10000, token);
|
||||
}
|
||||
|
||||
CharacterCacheDto? cacheDto = (await CreateFullCharacterCacheDto(token));
|
||||
cacheDto = (await CreateFullCharacterCacheDto(token).ConfigureAwait(false));
|
||||
}
|
||||
catch { }
|
||||
finally
|
||||
{
|
||||
_periodicFileScanner.ResumeScan("Character creation");
|
||||
}
|
||||
if (cacheDto == null || token.IsCancellationRequested) return;
|
||||
|
||||
#if DEBUG
|
||||
@@ -267,5 +275,4 @@ namespace MareSynchronos.Managers
|
||||
}
|
||||
}, token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,12 +8,12 @@ using System.Linq;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.Managers
|
||||
{
|
||||
public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
|
||||
namespace MareSynchronos.Managers;
|
||||
|
||||
public class TransientResourceManager : IDisposable
|
||||
{
|
||||
public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
|
||||
|
||||
public class TransientResourceManager : IDisposable
|
||||
{
|
||||
private readonly IpcManager manager;
|
||||
private readonly DalamudUtil dalamudUtil;
|
||||
|
||||
@@ -82,7 +82,7 @@ namespace MareSynchronos.Managers
|
||||
|
||||
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 @@ namespace MareSynchronos.Managers
|
||||
|
||||
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 => f.GamePaths.First().ToLowerInvariant() == replacedGamePath
|
||||
&& f.ResolvedPath.ToLowerInvariant() == filePath)).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath));
|
||||
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));
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -125,7 +125,7 @@ namespace MareSynchronos.Managers
|
||||
{
|
||||
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 @@ namespace MareSynchronos.Managers
|
||||
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,10 +196,9 @@ namespace MareSynchronos.Managers
|
||||
SemiTransientResources[objectKind] = new HashSet<FileReplacement>();
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
<PropertyGroup>
|
||||
<Authors></Authors>
|
||||
<Company></Company>
|
||||
<Version>0.4.24</Version>
|
||||
<Version>0.5.0</Version>
|
||||
<Description></Description>
|
||||
<Copyright></Copyright>
|
||||
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>
|
||||
@@ -28,6 +28,10 @@
|
||||
<ItemGroup>
|
||||
<PackageReference Include="DalamudPackager" Version="2.1.8" />
|
||||
<PackageReference Include="lz4net" Version="1.0.15.93" />
|
||||
<PackageReference Include="Meziantou.Analyzer" Version="1.0.733">
|
||||
<PrivateAssets>all</PrivateAssets>
|
||||
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||
</PackageReference>
|
||||
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.8" />
|
||||
</ItemGroup>
|
||||
|
||||
@@ -86,4 +90,8 @@
|
||||
<EmbeddedResource Include="Localization\fr.json" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
<None Include="..\.editorconfig" Link=".editorconfig" />
|
||||
</ItemGroup>
|
||||
|
||||
</Project>
|
||||
|
||||
@@ -6,11 +6,11 @@ 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<ObjectKind, List<FileReplacement>> FileReplacements { get; set; } = new();
|
||||
|
||||
@@ -31,10 +31,10 @@ namespace MareSynchronos.Models
|
||||
|
||||
if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new List<FileReplacement>());
|
||||
|
||||
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 @@ namespace MareSynchronos.Models
|
||||
|
||||
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());
|
||||
@@ -84,5 +84,4 @@ namespace MareSynchronos.Models
|
||||
}
|
||||
return stringBuilder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,10 +6,10 @@ 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)
|
||||
@@ -21,9 +21,9 @@ namespace MareSynchronos.Models
|
||||
|
||||
public List<string> 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;
|
||||
|
||||
@@ -56,5 +56,4 @@ namespace MareSynchronos.Models
|
||||
builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}");
|
||||
return builder.ToString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,10 +5,10 @@ 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<IntPtr> getAddress;
|
||||
|
||||
public unsafe Character* Character => (Character*)Address;
|
||||
@@ -61,7 +61,7 @@ namespace MareSynchronos.Models
|
||||
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;
|
||||
@@ -132,5 +132,4 @@ namespace MareSynchronos.Models
|
||||
|
||||
return hasChanges;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,12 @@ using MareSynchronos.UI;
|
||||
using MareSynchronos.Utils;
|
||||
using Dalamud.Game.ClientState.Conditions;
|
||||
using MareSynchronos.FileCache;
|
||||
using Dalamud.Logging;
|
||||
|
||||
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;
|
||||
@@ -174,7 +175,7 @@ namespace MareSynchronos
|
||||
{
|
||||
while (!_dalamudUtil.IsPlayerPresent)
|
||||
{
|
||||
await Task.Delay(100);
|
||||
await Task.Delay(100).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
try
|
||||
@@ -235,5 +236,4 @@ namespace MareSynchronos
|
||||
else
|
||||
_introUi.Toggle();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
@@ -14,26 +15,31 @@ 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<string, bool> _showUidForEntry = new();
|
||||
public readonly Dictionary<string, bool> ShowUidForEntry = new(StringComparer.Ordinal);
|
||||
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 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 showSyncShells = false;
|
||||
private GroupPanel groupPanel;
|
||||
|
||||
public CompactUi(WindowSystem windowSystem,
|
||||
UiShared uiShared, Configuration configuration, ApiController apiController) : base("###MareSynchronosMainUI")
|
||||
@@ -54,7 +60,7 @@ namespace MareSynchronos.UI
|
||||
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));
|
||||
|
||||
@@ -63,10 +69,12 @@ namespace MareSynchronos.UI
|
||||
_configuration = configuration;
|
||||
_apiController = apiController;
|
||||
|
||||
groupPanel = new(this, uiShared, configuration, apiController);
|
||||
|
||||
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);
|
||||
@@ -88,29 +96,74 @@ namespace MareSynchronos.UI
|
||||
|
||||
if (_apiController.ServerState is ServerState.Connected)
|
||||
{
|
||||
var hasShownSyncShells = showSyncShells;
|
||||
|
||||
ImGui.PushFont(UiBuilder.IconFont);
|
||||
if (!hasShownSyncShells)
|
||||
{
|
||||
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", 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()
|
||||
{
|
||||
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/Alias", ref _pairToAdd, 20);
|
||||
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;
|
||||
@@ -132,7 +185,7 @@ namespace MareSynchronos.UI
|
||||
_configuration.ReverseUserSort = true;
|
||||
_configuration.Save();
|
||||
}
|
||||
UiShared.AttachToolTip("Sort by newest additions first");
|
||||
UiShared.AttachToolTip("Sort by name descending");
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -141,7 +194,7 @@ namespace MareSynchronos.UI
|
||||
_configuration.ReverseUserSort = false;
|
||||
_configuration.Save();
|
||||
}
|
||||
UiShared.AttachToolTip("Sort by oldest additions first");
|
||||
UiShared.AttachToolTip("Sort by name ascending");
|
||||
}
|
||||
ImGui.SameLine();
|
||||
|
||||
@@ -246,7 +299,7 @@ namespace MareSynchronos.UI
|
||||
}
|
||||
|
||||
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))
|
||||
@@ -264,7 +317,7 @@ namespace MareSynchronos.UI
|
||||
}
|
||||
|
||||
ImGui.SameLine();
|
||||
if (_editNickEntry != entry.OtherUID)
|
||||
if (!string.Equals(EditNickEntry, entry.OtherUID, StringComparison.Ordinal))
|
||||
{
|
||||
ImGui.SetCursorPosY(textPos);
|
||||
if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont);
|
||||
@@ -275,22 +328,22 @@ namespace MareSynchronos.UI
|
||||
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
|
||||
@@ -298,16 +351,16 @@ namespace MareSynchronos.UI
|
||||
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");
|
||||
}
|
||||
@@ -319,7 +372,6 @@ namespace MareSynchronos.UI
|
||||
if (UiShared.CtrlPressed())
|
||||
{
|
||||
_ = _apiController.SendPairedClientRemoval(entry.OtherUID);
|
||||
_apiController.PairedClients.Remove(entry);
|
||||
}
|
||||
}
|
||||
UiShared.AttachToolTip("Hold CTRL and click to unpair permanently from " + entryUID);
|
||||
@@ -342,17 +394,18 @@ namespace MareSynchronos.UI
|
||||
{
|
||||
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);
|
||||
if (_configuration.ReverseUserSort) users = users.Reverse();
|
||||
|
||||
ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), false);
|
||||
@@ -363,6 +416,8 @@ namespace MareSynchronos.UI
|
||||
ImGui.EndChild();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private IEnumerable<ClientPairDto> GetFilteredUsers()
|
||||
{
|
||||
return _apiController.PairedClients.Where(p =>
|
||||
@@ -370,17 +425,23 @@ namespace MareSynchronos.UI
|
||||
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 = $"Shard: {_apiController.ServerInfo.ShardName}";
|
||||
#else
|
||||
string shardConnection = string.Equals(_apiController.ServerInfo.ShardName, "Main", StringComparison.OrdinalIgnoreCase) ? string.Empty : $"Shard: {_apiController.ServerInfo.ShardName}";
|
||||
#endif
|
||||
var shardTextSize = ImGui.CalcTextSize(shardConnection);
|
||||
|
||||
if (_apiController.ServerState is ServerState.Connected)
|
||||
{
|
||||
@@ -410,6 +471,12 @@ namespace MareSynchronos.UI
|
||||
}
|
||||
ImGui.PopStyleColor();
|
||||
UiShared.AttachToolTip(!_configuration.FullPause ? "Disconnect from " + _apiController.ServerDictionary[_configuration.ApiUri] : "Connect to " + _apiController.ServerDictionary[_configuration.ApiUri]);
|
||||
|
||||
if (!string.IsNullOrEmpty(_apiController.ServerInfo.ShardName))
|
||||
{
|
||||
ImGui.SetCursorPosX((ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth()) / 2 - shardTextSize.X / 2);
|
||||
ImGui.TextUnformatted(shardConnection);
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawTransfers()
|
||||
@@ -559,5 +626,4 @@ namespace MareSynchronos.UI
|
||||
_ => string.Empty
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
544
MareSynchronos/UI/GroupPanel.cs
Normal file
544
MareSynchronos/UI/GroupPanel.cs
Normal file
@@ -0,0 +1,544 @@
|
||||
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<string, bool> _showGidForEntry = new(StringComparer.Ordinal);
|
||||
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 readonly Dictionary<string, bool> ExpandedGroupState = new(StringComparer.Ordinal);
|
||||
|
||||
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);
|
||||
|
||||
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 => !string.Equals(w.GID, _syncShellToJoin, StringComparison.Ordinal)) && !string.IsNullOrEmpty(_syncShellToJoin))
|
||||
{
|
||||
if (userCanJoinMoreGroups)
|
||||
{
|
||||
_errorGroupJoin = false;
|
||||
_showModalEnterPassword = true;
|
||||
ImGui.OpenPopup("Enter Syncshell Password");
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (userCanCreateMoreGroups)
|
||||
{
|
||||
_lastCreatedGroup = null;
|
||||
_errorGroupCreate = false;
|
||||
_showModalCreateGroup = true;
|
||||
ImGui.OpenPopup("Create Syncshell");
|
||||
}
|
||||
}
|
||||
}
|
||||
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))
|
||||
{
|
||||
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 ({_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))
|
||||
{
|
||||
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
|
||||
{
|
||||
_lastCreatedGroup = null;
|
||||
_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 => string.IsNullOrEmpty(g.Alias) ? g.GID : g.Alias).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 => string.Equals(p.GroupGID, group.GID, StringComparison.Ordinal)).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(ImGui.GetWindowContentRegionMin().X + collapseButton.X);
|
||||
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 = string.IsNullOrEmpty(group.Alias) ? group.GID : group.Alias;
|
||||
var textIsGid = true;
|
||||
|
||||
if (string.Equals(group.OwnedBy, _apiController.UID, StringComparison.Ordinal))
|
||||
{
|
||||
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))
|
||||
{
|
||||
if (!string.IsNullOrEmpty(groupComment))
|
||||
{
|
||||
groupName = groupComment;
|
||||
textIsGid = false;
|
||||
}
|
||||
}
|
||||
|
||||
if (!string.Equals(_editGroupEntry, group.GID, StringComparison.Ordinal))
|
||||
{
|
||||
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");
|
||||
}
|
||||
|
||||
UiShared.DrawWithID(group.GID + "settings", () => DrawSyncShellButtons(group, name));
|
||||
|
||||
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 ? 0 : 1).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, string.Equals(group.OwnedBy, _apiController.UID, StringComparison.Ordinal), group?.IsPaused ?? false));
|
||||
}
|
||||
|
||||
ImGui.Separator();
|
||||
ImGui.Unindent(ImGui.GetStyle().ItemSpacing.X / 2);
|
||||
}
|
||||
ImGui.Unindent(collapseButton.X);
|
||||
}
|
||||
|
||||
private void DrawSyncShellButtons(GroupDto entry, string name)
|
||||
{
|
||||
bool invitesEnabled = entry.InvitesEnabled ?? true;
|
||||
var lockedIcon = invitesEnabled ? FontAwesomeIcon.LockOpen : FontAwesomeIcon.Lock;
|
||||
var iconSize = UiShared.GetIconSize(lockedIcon);
|
||||
var barbuttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Bars);
|
||||
|
||||
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.Bars))
|
||||
{
|
||||
ImGui.OpenPopup("ShellPopup");
|
||||
}
|
||||
|
||||
if (ImGui.BeginPopup("ShellPopup"))
|
||||
{
|
||||
if (UiShared.IconTextButton(FontAwesomeIcon.ArrowCircleLeft, "Leave Syncshell"))
|
||||
{
|
||||
if (UiShared.CtrlPressed())
|
||||
{
|
||||
_ = _apiController.SendLeaveGroup(entry.GID);
|
||||
}
|
||||
}
|
||||
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"))
|
||||
{
|
||||
ImGui.SetClipboardText(string.IsNullOrEmpty(entry.Alias) ? entry.GID : entry.Alias);
|
||||
}
|
||||
UiShared.AttachToolTip("Copy Syncshell ID to Clipboard");
|
||||
|
||||
if (string.Equals(entry.OwnedBy, _apiController.UID, StringComparison.Ordinal))
|
||||
{
|
||||
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 (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())
|
||||
{
|
||||
_ = _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();
|
||||
}
|
||||
}
|
||||
|
||||
private void DrawSyncshellPairedClient(GroupPairDto entry, bool isOwner, bool isPausedByYou)
|
||||
{
|
||||
var plusButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus);
|
||||
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();
|
||||
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();
|
||||
UiShared.AttachToolTip("User is pinned in this Syncshell");
|
||||
}
|
||||
|
||||
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 (!string.Equals(_mainUi.EditNickEntry, entry.UserUID, StringComparison.Ordinal))
|
||||
{
|
||||
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 plusButtonShown = !_apiController.PairedClients.Any(p => string.Equals(p.OtherUID, entry.UserUID, StringComparison.Ordinal));
|
||||
|
||||
if (plusButtonShown)
|
||||
{
|
||||
ImGui.SetCursorPosY(originalY);
|
||||
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - plusButtonSize.X - (isOwner ? barButtonSize.X + ImGui.GetStyle().ItemSpacing.X : 0));
|
||||
|
||||
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
|
||||
{
|
||||
_ = _apiController.SendPairedClientAddition(entry.UserUID);
|
||||
}
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -12,10 +12,10 @@ 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;
|
||||
@@ -37,12 +37,12 @@ namespace MareSynchronos.UI
|
||||
private Task _timeoutTask;
|
||||
private string _timeoutTime;
|
||||
|
||||
private Dictionary<string, string> _languages = new() { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } };
|
||||
private Dictionary<string, string> _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 @@ namespace MareSynchronos.UI
|
||||
{
|
||||
_timeoutTime = $"{i}s " + Strings.ToS.RemainingLabel;
|
||||
Logger.Debug(_timeoutTime);
|
||||
await Task.Delay(TimeSpan.FromSeconds(1));
|
||||
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -286,13 +286,12 @@ namespace MareSynchronos.UI
|
||||
|
||||
private Tuple<string, string> 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]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -14,11 +14,11 @@ 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;
|
||||
@@ -335,7 +335,7 @@ namespace MareSynchronos.UI
|
||||
});
|
||||
}
|
||||
ImGui.SameLine();
|
||||
if (onlineUser.UID != _apiController.UID && _apiController.IsAdmin)
|
||||
if (!string.Equals(onlineUser.UID, _apiController.UID, StringComparison.Ordinal) && _apiController.IsAdmin)
|
||||
{
|
||||
if (!onlineUser.IsModerator)
|
||||
{
|
||||
@@ -609,5 +609,4 @@ namespace MareSynchronos.UI
|
||||
_uiShared.EditTrackerPosition = false;
|
||||
base.OnClose();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,10 +18,10 @@ 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);
|
||||
|
||||
@@ -39,8 +39,8 @@ namespace MareSynchronos.UI
|
||||
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 @@ namespace MareSynchronos.UI
|
||||
}
|
||||
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
|
||||
@@ -294,7 +294,7 @@ namespace MareSynchronos.UI
|
||||
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 @@ namespace MareSynchronos.UI
|
||||
{
|
||||
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);
|
||||
@@ -548,9 +548,49 @@ namespace MareSynchronos.UI
|
||||
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 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
26
MareSynchronos/Utils/DalamudLoggingProvider.cs
Normal file
26
MareSynchronos/Utils/DalamudLoggingProvider.cs
Normal file
@@ -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<string, Logger> _loggers =
|
||||
new(StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
public DalamudLoggingProvider()
|
||||
{
|
||||
}
|
||||
|
||||
public ILogger CreateLogger(string categoryName)
|
||||
{
|
||||
return _loggers.GetOrAdd(categoryName, name => new Logger(categoryName));
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
_loggers.Clear();
|
||||
}
|
||||
}
|
||||
@@ -11,19 +11,19 @@ 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);
|
||||
|
||||
public delegate void LogIn();
|
||||
public delegate void LogOut();
|
||||
public delegate void ClassJobChanged();
|
||||
|
||||
public delegate void FrameworkUpdate();
|
||||
public delegate void VoidDelegate();
|
||||
|
||||
public class DalamudUtil : IDisposable
|
||||
{
|
||||
private readonly ClientState _clientState;
|
||||
private readonly ObjectTable _objectTable;
|
||||
private readonly Framework _framework;
|
||||
@@ -186,7 +186,7 @@ namespace MareSynchronos.Utils
|
||||
{
|
||||
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 @@ namespace MareSynchronos.Utils
|
||||
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 @@ namespace MareSynchronos.Utils
|
||||
|
||||
public async Task<T> RunOnFrameworkThread<T>(Func<T> 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)
|
||||
@@ -238,5 +238,4 @@ namespace MareSynchronos.Utils
|
||||
_clientState.Logout -= ClientStateOnLogout;
|
||||
_framework.Update -= FrameworkOnUpdate;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,35 +1,13 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics;
|
||||
using Dalamud.Logging;
|
||||
using Dalamud.Utility;
|
||||
using Microsoft.Extensions.Logging;
|
||||
|
||||
namespace MareSynchronos.Utils
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
internal class Logger : ILogger
|
||||
{
|
||||
[ProviderAlias("Dalamud")]
|
||||
public class DalamudLoggingProvider : ILoggerProvider
|
||||
{
|
||||
private readonly ConcurrentDictionary<string, Logger> _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;
|
||||
|
||||
public static void Info(string info)
|
||||
@@ -41,7 +19,7 @@ namespace MareSynchronos.Utils
|
||||
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}");
|
||||
}
|
||||
@@ -108,5 +86,4 @@ namespace MareSynchronos.Utils
|
||||
}
|
||||
|
||||
public IDisposable BeginScope<TState>(TState state) => default!;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.Utils
|
||||
{
|
||||
public static class VariousExtensions
|
||||
{
|
||||
public static DateTime GetLinkerTime(Assembly assembly)
|
||||
{
|
||||
const string BuildVersionMetadataPrefix = "+build";
|
||||
|
||||
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
||||
if (attribute?.InformationalVersion != null)
|
||||
{
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
}
|
||||
31
MareSynchronos/Utils/VariousExtensions.cs
Normal file
31
MareSynchronos/Utils/VariousExtensions.cs
Normal file
@@ -0,0 +1,31 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.Utils;
|
||||
|
||||
public static class VariousExtensions
|
||||
{
|
||||
public static DateTime GetLinkerTime(Assembly assembly)
|
||||
{
|
||||
const string BuildVersionMetadataPrefix = "+build";
|
||||
|
||||
var attribute = assembly.GetCustomAttribute<AssemblyInformationalVersionAttribute>();
|
||||
if (attribute?.InformationalVersion != null)
|
||||
{
|
||||
var value = attribute.InformationalVersion;
|
||||
var index = value.IndexOf(BuildVersionMetadataPrefix, StringComparison.Ordinal);
|
||||
if (index > 0)
|
||||
{
|
||||
value = value[(index + BuildVersionMetadataPrefix.Length)..];
|
||||
return DateTime.ParseExact(value, "yyyy-MM-ddTHH:mm:ss:fffZ", CultureInfo.InvariantCulture);
|
||||
}
|
||||
}
|
||||
|
||||
return default;
|
||||
}
|
||||
}
|
||||
@@ -13,10 +13,10 @@ 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<string> _verifiedUploadedHashes;
|
||||
|
||||
private int _downloadId = 0;
|
||||
@@ -33,7 +33,7 @@ namespace MareSynchronos.WebAPI
|
||||
|
||||
public async Task DeleteAllMyFiles()
|
||||
{
|
||||
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles);
|
||||
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
private async Task<string> DownloadFile(int downloadId, string hash, Uri downloadUri, CancellationToken ct)
|
||||
@@ -44,7 +44,7 @@ namespace MareSynchronos.WebAPI
|
||||
{
|
||||
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 @@ namespace MareSynchronos.WebAPI
|
||||
|
||||
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 @@ namespace MareSynchronos.WebAPI
|
||||
DownloadStarted?.Invoke();
|
||||
try
|
||||
{
|
||||
await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct);
|
||||
await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct).ConfigureAwait(false);
|
||||
}
|
||||
catch
|
||||
{
|
||||
@@ -94,8 +94,8 @@ namespace MareSynchronos.WebAPI
|
||||
{
|
||||
Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")");
|
||||
|
||||
List<DownloadFileDto> downloadFileInfoFromService = new List<DownloadFileDto>();
|
||||
downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync<List<DownloadFileDto>>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct));
|
||||
List<DownloadFileDto> downloadFileInfoFromService = new();
|
||||
downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync<List<DownloadFileDto>>(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 @@ namespace MareSynchronos.WebAPI
|
||||
|
||||
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 @@ namespace MareSynchronos.WebAPI
|
||||
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 @@ namespace MareSynchronos.WebAPI
|
||||
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<DateTime> 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 @@ namespace MareSynchronos.WebAPI
|
||||
Logger.Warn(ex.Message);
|
||||
Logger.Warn(ex.StackTrace);
|
||||
}
|
||||
});
|
||||
}).ConfigureAwait(false);
|
||||
|
||||
Logger.Debug("Download complete, removing " + currentDownloadId);
|
||||
CancelDownload(currentDownloadId);
|
||||
@@ -163,7 +163,7 @@ namespace MareSynchronos.WebAPI
|
||||
|
||||
public async Task PushCharacterData(CharacterCacheDto character, List<string> 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 @@ namespace MareSynchronos.WebAPI
|
||||
Logger.Verbose("New Token Created");
|
||||
|
||||
List<string> 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 @@ namespace MareSynchronos.WebAPI
|
||||
if (unverifiedUploadHashes.Any())
|
||||
{
|
||||
Logger.Debug("Verifying " + unverifiedUploadHashes.Count + " files");
|
||||
var filesToUpload = await _mareHub!.InvokeAsync<List<UploadFileDto>>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken);
|
||||
var filesToUpload = await _mareHub!.InvokeAsync<List<UploadFileDto>>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken).ConfigureAwait(false);
|
||||
|
||||
foreach (var file in filesToUpload.Where(f => !f.IsForbidden))
|
||||
{
|
||||
@@ -203,7 +203,7 @@ namespace MareSynchronos.WebAPI
|
||||
|
||||
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 @@ namespace MareSynchronos.WebAPI
|
||||
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 @@ namespace MareSynchronos.WebAPI
|
||||
}
|
||||
|
||||
Logger.Debug("Upload tasks complete, waiting for server to confirm");
|
||||
var anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
|
||||
var anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken).ConfigureAwait(false);
|
||||
Logger.Debug("Uploads open: " + anyUploadsOpen);
|
||||
while (anyUploadsOpen && !uploadToken.IsCancellationRequested)
|
||||
{
|
||||
anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
|
||||
await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken);
|
||||
anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(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 @@ namespace MareSynchronos.WebAPI
|
||||
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 @@ namespace MareSynchronos.WebAPI
|
||||
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 @@ namespace MareSynchronos.WebAPI
|
||||
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 @@ namespace MareSynchronos.WebAPI
|
||||
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)
|
||||
@@ -313,6 +313,5 @@ namespace MareSynchronos.WebAPI
|
||||
CurrentDownloads.TryRemove(downloadId, out _);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
@@ -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()
|
||||
{
|
||||
_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<List<string>> GetOnlineCharacters()
|
||||
{
|
||||
return await _mareHub!.InvokeAsync<List<string>>(Api.InvokeUserGetOnlineCharacters);
|
||||
return await _mareHub!.InvokeAsync<List<string>>(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);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -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<CharacterReceivedEventArgs>? 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<int, List<DownloadFileTransfer>> CurrentDownloads { get; } = new();
|
||||
|
||||
public List<FileTransfer> CurrentUploads { get; } = new();
|
||||
|
||||
public List<FileTransfer> ForbiddenTransfers { get; } = new();
|
||||
|
||||
public List<BannedUserDto> AdminBannedUsers { get; private set; } = new();
|
||||
|
||||
public List<ForbiddenFileDto> 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<ClientPairDto> 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<string, string> ServerDictionary => new Dictionary<string, string>()
|
||||
{ { 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<SystemInfoDto>(Api.OnUpdateSystemInfo, (dto) => SystemInfoDto = dto);
|
||||
|
||||
_connectionDto =
|
||||
await _mareHub.InvokeAsync<ConnectionDto>(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<ClientPairDto, string>(Api.OnUserUpdateClientPairs,
|
||||
UpdateLocalClientPairsCallback);
|
||||
_mareHub.On<CharacterCacheDto, string>(Api.OnUserReceiveCharacterData,
|
||||
ReceiveCharacterDataCallback);
|
||||
_mareHub.On<string>(Api.OnUserRemoveOnlinePairedPlayer,
|
||||
(s) => PairedClientOffline?.Invoke(s));
|
||||
_mareHub.On<string>(Api.OnUserAddOnlinePairedPlayer,
|
||||
(s) => PairedClientOnline?.Invoke(s));
|
||||
_mareHub.On(Api.OnAdminForcedReconnect, UserForcedReconnectCallback);
|
||||
|
||||
PairedClients =
|
||||
await _mareHub!.InvokeAsync<List<ClientPairDto>>(Api.InvokeUserGetPairedClients, token);
|
||||
|
||||
if (IsModerator)
|
||||
{
|
||||
AdminForbiddenFiles =
|
||||
await _mareHub.InvokeAsync<List<ForbiddenFileDto>>(Api.InvokeAdminGetForbiddenFiles,
|
||||
token);
|
||||
AdminBannedUsers =
|
||||
await _mareHub.InvokeAsync<List<BannedUserDto>>(Api.InvokeAdminGetBannedUsers,
|
||||
token);
|
||||
_mareHub.On<BannedUserDto>(Api.OnAdminUpdateOrAddBannedUser,
|
||||
UpdateOrAddBannedUserCallback);
|
||||
_mareHub.On<BannedUserDto>(Api.OnAdminDeleteBannedUser, DeleteBannedUserCallback);
|
||||
_mareHub.On<ForbiddenFileDto>(Api.OnAdminUpdateOrAddForbiddenFile,
|
||||
UpdateOrAddForbiddenFileCallback);
|
||||
_mareHub.On<ForbiddenFileDto>(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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,35 +1,36 @@
|
||||
using System.Collections.Generic;
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
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)
|
||||
{
|
||||
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<List<OnlineUserDto>>(Api.InvokeAdminGetOnlineUsers);
|
||||
AdminOnlineUsers = await _mareHub!.InvokeAsync<List<OnlineUserDto>>(Api.InvokeAdminGetOnlineUsers).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public List<OnlineUserDto> AdminOnlineUsers { get; set; } = new List<OnlineUserDto>();
|
||||
@@ -43,5 +44,4 @@ namespace MareSynchronos.WebAPI
|
||||
{
|
||||
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,25 +1,26 @@
|
||||
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
|
||||
namespace MareSynchronos.WebAPI;
|
||||
|
||||
public partial class ApiController
|
||||
{
|
||||
public partial class ApiController
|
||||
{
|
||||
private void UserForcedReconnectCallback()
|
||||
{
|
||||
_ = CreateConnections();
|
||||
}
|
||||
|
||||
private void UpdateLocalClientPairsCallback(ClientPairDto dto, string characterIdentifier)
|
||||
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);
|
||||
UnpairedFromOther?.Invoke(characterIdentifier);
|
||||
PairedClients.RemoveAll(p => string.Equals(p.OtherUID, dto.OtherUID, System.StringComparison.Ordinal));
|
||||
return;
|
||||
}
|
||||
if (entry == null)
|
||||
@@ -28,20 +29,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)
|
||||
@@ -53,7 +43,7 @@ namespace MareSynchronos.WebAPI
|
||||
|
||||
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);
|
||||
@@ -66,12 +56,12 @@ namespace MareSynchronos.WebAPI
|
||||
|
||||
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);
|
||||
@@ -84,7 +74,48 @@ namespace MareSynchronos.WebAPI
|
||||
|
||||
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 => string.Equals(g.GroupGID, dto.GroupGID, System.StringComparison.Ordinal) && string.Equals(g.UserUID, dto.UserUID, System.StringComparison.Ordinal));
|
||||
return;
|
||||
}
|
||||
|
||||
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);
|
||||
return;
|
||||
}
|
||||
|
||||
existingUser.IsPaused = dto.IsPaused ?? existingUser.IsPaused;
|
||||
existingUser.UserAlias = dto.UserAlias ?? existingUser.UserAlias;
|
||||
existingUser.IsPinned = dto.IsPinned ?? existingUser.IsPinned;
|
||||
}
|
||||
|
||||
private async Task GroupChangedCallback(GroupDto dto)
|
||||
{
|
||||
if (dto.IsDeleted.GetValueOrDefault(false))
|
||||
{
|
||||
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 => string.Equals(g.GID, dto.GID, System.StringComparison.Ordinal));
|
||||
if (existingGroup == null)
|
||||
{
|
||||
Groups.Add(dto);
|
||||
GroupPairedClients.AddRange(await _mareHub!.InvokeAsync<List<GroupPairDto>>(Api.InvokeGroupGetUsersInGroup, dto.GID).ConfigureAwait(false));
|
||||
return;
|
||||
}
|
||||
|
||||
existingGroup.OwnedBy = dto.OwnedBy ?? existingGroup.OwnedBy;
|
||||
existingGroup.InvitesEnabled = dto.InvitesEnabled ?? existingGroup.InvitesEnabled;
|
||||
existingGroup.IsPaused = dto.IsPaused ?? existingGroup.IsPaused;
|
||||
}
|
||||
}
|
||||
|
||||
87
MareSynchronos/WebAPI/ApiController.Functions.Groups.cs
Normal file
87
MareSynchronos/WebAPI/ApiController.Functions.Groups.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using MareSynchronos.API;
|
||||
using MareSynchronos.Utils;
|
||||
using Microsoft.AspNetCore.SignalR.Client;
|
||||
using System.Collections.Generic;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace MareSynchronos.WebAPI;
|
||||
public partial class ApiController
|
||||
{
|
||||
public async Task<GroupCreatedDto> CreateGroup()
|
||||
{
|
||||
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return new GroupCreatedDto();
|
||||
return await _mareHub!.InvokeAsync<GroupCreatedDto>(Api.InvokeGroupCreate).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> ChangeGroupPassword(string gid, string newpassword)
|
||||
{
|
||||
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return false;
|
||||
return await _mareHub!.InvokeAsync<bool>(Api.InvokeGroupChangePassword, gid, newpassword).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<List<GroupDto>> GetGroups()
|
||||
{
|
||||
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return new List<GroupDto>();
|
||||
return await _mareHub!.InvokeAsync<List<GroupDto>>(Api.InvokeGroupGetGroups).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<List<GroupPairDto>> GetUsersInGroup(string gid)
|
||||
{
|
||||
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return new List<GroupPairDto>();
|
||||
return await _mareHub!.InvokeAsync<List<GroupPairDto>>(Api.InvokeGroupGetUsersInGroup, gid).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task<bool> SendGroupJoin(string gid, string password)
|
||||
{
|
||||
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return false;
|
||||
return await _mareHub!.InvokeAsync<bool>(Api.InvokeGroupJoin, gid, password).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SendGroupChangeInviteState(string gid, bool 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 || 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 || 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 || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
|
||||
await _mareHub!.SendAsync(Api.SendGroupClear, gid).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
public async Task SendLeaveGroup(string 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 || 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 || 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 || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
|
||||
await _mareHub!.SendAsync(Api.SendGroupChangeOwner, gid, uid).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
395
MareSynchronos/WebAPI/ApiController.cs
Normal file
395
MareSynchronos/WebAPI/ApiController.cs
Normal file
@@ -0,0 +1,395 @@
|
||||
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 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);
|
||||
|
||||
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(StringComparer.Ordinal);
|
||||
|
||||
if (_dalamudUtil.IsLoggedIn)
|
||||
{
|
||||
DalamudUtilOnLogIn();
|
||||
}
|
||||
}
|
||||
|
||||
private void DalamudUtilOnLogOut()
|
||||
{
|
||||
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token).ConfigureAwait(false));
|
||||
ServerState = ServerState.Offline;
|
||||
}
|
||||
|
||||
private void DalamudUtilOnLogIn()
|
||||
{
|
||||
Task.Run(CreateConnections);
|
||||
}
|
||||
|
||||
|
||||
public event EventHandler<CharacterReceivedEventArgs>? CharacterReceived;
|
||||
|
||||
public event VoidDelegate? Connected;
|
||||
|
||||
public event VoidDelegate? Disconnected;
|
||||
|
||||
public event SimpleStringDelegate? PairedClientOffline;
|
||||
|
||||
public event SimpleStringDelegate? PairedClientOnline;
|
||||
public event VoidDelegate? DownloadStarted;
|
||||
public event VoidDelegate? DownloadFinished;
|
||||
|
||||
public ConcurrentDictionary<int, List<DownloadFileTransfer>> CurrentDownloads { get; } = new();
|
||||
|
||||
public List<FileTransfer> CurrentUploads { get; } = new();
|
||||
|
||||
public List<FileTransfer> ForbiddenTransfers { get; } = new();
|
||||
|
||||
public List<BannedUserDto> AdminBannedUsers { get; private set; } = new();
|
||||
|
||||
public List<ForbiddenFileDto> 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<ClientPairDto> PairedClients { get; set; } = new();
|
||||
public List<GroupPairDto> GroupPairedClients { get; set; } = new();
|
||||
public List<GroupDto> 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<string, string> ServerDictionary => new Dictionary<string, string>(StringComparer.Ordinal)
|
||||
{ { MainServiceUri, MainServer } }
|
||||
.Concat(_pluginConfiguration.CustomServerList)
|
||||
.ToDictionary(k => k.Key, k => k.Value, StringComparer.Ordinal);
|
||||
|
||||
public string UID => _connectionDto?.UID ?? string.Empty;
|
||||
public string DisplayName => _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).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
await StopConnection(_connectionCancellationTokenSource.Token).ConfigureAwait(false);
|
||||
|
||||
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)).ConfigureAwait(false);
|
||||
continue;
|
||||
}
|
||||
|
||||
await StopConnection(token).ConfigureAwait(false);
|
||||
|
||||
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).ConfigureAwait(false);
|
||||
}
|
||||
|
||||
if (token.IsCancellationRequested) break;
|
||||
|
||||
_mareHub = BuildHubConnection(Api.Path);
|
||||
|
||||
await _mareHub.StartAsync(token).ConfigureAwait(false);
|
||||
|
||||
_mareHub.On<SystemInfoDto>(Api.OnUpdateSystemInfo, (dto) => SystemInfoDto = dto);
|
||||
|
||||
_connectionDto =
|
||||
await _mareHub.InvokeAsync<ConnectionDto>(Api.InvokeHeartbeat, _dalamudUtil.PlayerNameHashed, token).ConfigureAwait(false);
|
||||
|
||||
ServerState = ServerState.Connected;
|
||||
|
||||
if (_connectionDto.ServerVersion != Api.Version)
|
||||
{
|
||||
ServerState = ServerState.VersionMisMatch;
|
||||
await StopConnection(token).ConfigureAwait(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (ServerState is ServerState.Connected) // user is authorized && server is legit
|
||||
{
|
||||
await InitializeData(token).ConfigureAwait(false);
|
||||
|
||||
_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).ConfigureAwait(false);
|
||||
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).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).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
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).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Task MareHubOnReconnected(string? arg)
|
||||
{
|
||||
_ = Task.Run(CreateConnections);
|
||||
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<bool>(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;
|
||||
|
||||
Logger.Debug("Initializing data");
|
||||
_mareHub.On<ClientPairDto>(Api.OnUserUpdateClientPairs,
|
||||
UpdateLocalClientPairsCallback);
|
||||
_mareHub.On<CharacterCacheDto, string>(Api.OnUserReceiveCharacterData,
|
||||
ReceiveCharacterDataCallback);
|
||||
_mareHub.On<string>(Api.OnUserRemoveOnlinePairedPlayer,
|
||||
(s) => PairedClientOffline?.Invoke(s));
|
||||
_mareHub.On<string>(Api.OnUserAddOnlinePairedPlayer,
|
||||
(s) => PairedClientOnline?.Invoke(s));
|
||||
_mareHub.On(Api.OnAdminForcedReconnect, UserForcedReconnectCallback);
|
||||
_mareHub.On<GroupDto>(Api.OnGroupChange, GroupChangedCallback);
|
||||
_mareHub.On<GroupPairDto>(Api.OnGroupUserChange, GroupPairChangedCallback);
|
||||
|
||||
PairedClients =
|
||||
await _mareHub!.InvokeAsync<List<ClientPairDto>>(Api.InvokeUserGetPairedClients, token).ConfigureAwait(false);
|
||||
Groups = await GetGroups().ConfigureAwait(false);
|
||||
GroupPairedClients.Clear();
|
||||
foreach (var group in Groups)
|
||||
{
|
||||
GroupPairedClients.AddRange(await GetUsersInGroup(group.GID).ConfigureAwait(false));
|
||||
}
|
||||
|
||||
if (IsModerator)
|
||||
{
|
||||
AdminForbiddenFiles =
|
||||
await _mareHub.InvokeAsync<List<ForbiddenFileDto>>(Api.InvokeAdminGetForbiddenFiles,
|
||||
token).ConfigureAwait(false);
|
||||
AdminBannedUsers =
|
||||
await _mareHub.InvokeAsync<List<BannedUserDto>>(Api.InvokeAdminGetBannedUsers,
|
||||
token).ConfigureAwait(false);
|
||||
_mareHub.On<BannedUserDto>(Api.OnAdminUpdateOrAddBannedUser,
|
||||
UpdateOrAddBannedUserCallback);
|
||||
_mareHub.On<BannedUserDto>(Api.OnAdminDeleteBannedUser, DeleteBannedUserCallback);
|
||||
_mareHub.On<ForbiddenFileDto>(Api.OnAdminUpdateOrAddForbiddenFile,
|
||||
UpdateOrAddForbiddenFileCallback);
|
||||
_mareHub.On<ForbiddenFileDto>(Api.OnAdminDeleteForbiddenFile,
|
||||
DeleteForbiddenFileCallback);
|
||||
}
|
||||
|
||||
_healthCheckTokenSource?.Cancel();
|
||||
_healthCheckTokenSource?.Dispose();
|
||||
_healthCheckTokenSource = new CancellationTokenSource();
|
||||
_ = ClientHealthCheck(_healthCheckTokenSource.Token);
|
||||
|
||||
Connected?.Invoke();
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Logger.Verbose("Disposing " + nameof(ApiController));
|
||||
|
||||
_dalamudUtil.LogIn -= DalamudUtilOnLogIn;
|
||||
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
|
||||
|
||||
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token).ConfigureAwait(false));
|
||||
_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)
|
||||
{
|
||||
_connectionDto = null;
|
||||
_healthCheckTokenSource?.Cancel();
|
||||
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).ConfigureAwait(false);
|
||||
await _mareHub.DisposeAsync().ConfigureAwait(false);
|
||||
CurrentUploads.Clear();
|
||||
CurrentDownloads.Clear();
|
||||
_uploadCancellationTokenSource?.Cancel();
|
||||
Disconnected?.Invoke();
|
||||
_mareHub = null;
|
||||
}
|
||||
|
||||
if (ServerState != ServerState.Disconnected)
|
||||
{
|
||||
while (ServerState != ServerState.Offline)
|
||||
{
|
||||
await Task.Delay(16).ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
11
MareSynchronos/WebAPI/ServerState.cs
Normal file
11
MareSynchronos/WebAPI/ServerState.cs
Normal file
@@ -0,0 +1,11 @@
|
||||
namespace MareSynchronos.WebAPI;
|
||||
|
||||
public enum ServerState
|
||||
{
|
||||
Offline,
|
||||
Disconnected,
|
||||
Connected,
|
||||
Unauthorized,
|
||||
VersionMisMatch,
|
||||
RateLimited
|
||||
}
|
||||
Reference in New Issue
Block a user