Merge pull request #27 from Penumbra-Sync/groups

Syncshells
This commit is contained in:
rootdarkarchon
2022-10-04 14:18:19 +02:00
committed by GitHub
39 changed files with 5376 additions and 4460 deletions

102
.editorconfig Normal file
View 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

Submodule MareAPI updated: 9dc1e901aa...2d5d9d9d1c

View File

@@ -9,6 +9,11 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MareSynchronos.API", "MareA
EndProject EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumbra\Penumbra.GameData\Penumbra.GameData.csproj", "{89DD407C-B2B7-4BB3-BF26-C550BA1841F8}" Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Penumbra.GameData", "Penumbra\Penumbra.GameData\Penumbra.GameData.csproj", "{89DD407C-B2B7-4BB3-BF26-C550BA1841F8}"
EndProject EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{585B740D-BA2C-429B-9CF3-B2D223423748}"
ProjectSection(SolutionItems) = preProject
.editorconfig = .editorconfig
EndProjectSection
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU

View File

@@ -7,10 +7,10 @@ using System.Linq;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
namespace MareSynchronos namespace MareSynchronos;
public static class ConfigurationExtensions
{ {
public static class ConfigurationExtensions
{
public static bool HasValidSetup(this Configuration configuration) public static bool HasValidSetup(this Configuration configuration)
{ {
return configuration.AcceptedAgreement && configuration.InitialScanComplete return configuration.AcceptedAgreement && configuration.InitialScanComplete
@@ -23,23 +23,40 @@ namespace MareSynchronos
{ {
return configuration.UidServerComments.ContainsKey(configuration.ApiUri) return configuration.UidServerComments.ContainsKey(configuration.ApiUri)
? configuration.UidServerComments[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) public static void SetCurrentServerUidComment(this Configuration configuration, string uid, string comment)
{ {
if (!configuration.UidServerComments.ContainsKey(configuration.ApiUri)) 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; configuration.UidServerComments[configuration.ApiUri][uid] = comment;
} }
} }
[Serializable] [Serializable]
public class Configuration : IPluginConfiguration public class Configuration : IPluginConfiguration
{ {
private string _apiUri = string.Empty; private string _apiUri = string.Empty;
private int _maxParallelScan = 10; private int _maxParallelScan = 10;
[NonSerialized] [NonSerialized]
@@ -54,10 +71,10 @@ namespace MareSynchronos
} }
public string CacheFolder { get; set; } = string.Empty; public string CacheFolder { get; set; } = string.Empty;
public Dictionary<string, string> ClientSecret { get; set; } = new(); public Dictionary<string, string> ClientSecret { get; set; } = new(StringComparer.Ordinal);
public Dictionary<string, string> CustomServerList { get; set; } = new(); public Dictionary<string, string> CustomServerList { get; set; } = new(StringComparer.Ordinal);
public int MaxLocalCacheInGiB { get; set; } = 20; 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 int TimeSpanBetweenScansInSeconds { get; set; } = 30;
public bool FileScanPaused { get; set; } = false; public bool FileScanPaused { get; set; } = false;
@@ -65,9 +82,10 @@ namespace MareSynchronos
public bool InitialScanComplete { get; set; } = false; public bool InitialScanComplete { get; set; } = false;
public bool FullPause { 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 int Version { get; set; } = 5;
public bool ShowTransferWindow { get; set; } = true; public bool ShowTransferWindow { get; set; } = true;
@@ -96,10 +114,10 @@ namespace MareSynchronos
{ {
Logger.Debug("Migrating Configuration from V0 to V1"); Logger.Debug("Migrating Configuration from V0 to V1");
Version = 1; Version = 1;
ApiUri = ApiUri.Replace("https", "wss"); ApiUri = ApiUri.Replace("https", "wss", StringComparison.Ordinal);
foreach (var kvp in ClientSecret.ToList()) 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); ClientSecret.Remove(kvp.Key);
if (ClientSecret.ContainsKey(newKey)) if (ClientSecret.ContainsKey(newKey))
{ {
@@ -110,7 +128,7 @@ namespace MareSynchronos
ClientSecret.Add(newKey, kvp.Value); 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(); UidComments.Clear();
Save(); Save();
} }
@@ -118,10 +136,10 @@ namespace MareSynchronos
if (Version == 1) if (Version == 1)
{ {
Logger.Debug("Migrating Configuration from V1 to V2"); 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()) 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); ClientSecret.Remove(kvp.Key);
if (ClientSecret.ContainsKey(newKey)) if (ClientSecret.ContainsKey(newKey))
{ {
@@ -135,7 +153,7 @@ namespace MareSynchronos
foreach (var kvp in UidServerComments.ToList()) 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.Remove(kvp.Key);
UidServerComments.Add(newKey, kvp.Value); UidServerComments.Add(newKey, kvp.Value);
} }
@@ -159,10 +177,10 @@ namespace MareSynchronos
{ {
Logger.Debug("Migrating Configuration from V3 to V4"); 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()) 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); ClientSecret.Remove(kvp.Key);
if (ClientSecret.ContainsKey(newKey)) if (ClientSecret.ContainsKey(newKey))
{ {
@@ -176,7 +194,7 @@ namespace MareSynchronos
foreach (var kvp in UidServerComments.ToList()) 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.Remove(kvp.Key);
UidServerComments.Add(newKey, kvp.Value); UidServerComments.Add(newKey, kvp.Value);
} }
@@ -189,7 +207,7 @@ namespace MareSynchronos
{ {
Logger.Debug("Migrating Configuration from V4 to V5"); 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"); ClientSecret.Remove("wss://v2202207178628194299.powersrv.de:6872");
UidServerComments.Remove("wss://v2202207178628194299.powersrv.de:6872"); UidServerComments.Remove("wss://v2202207178628194299.powersrv.de:6872");
@@ -197,5 +215,4 @@ namespace MareSynchronos
Save(); Save();
} }
} }
}
} }

View File

@@ -158,7 +158,7 @@ public class CharacterDataFactory
if (cache.FileReplacements.ContainsKey(objectKind)) 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; return;
} }
@@ -196,7 +196,7 @@ public class CharacterDataFactory
if (cache.FileReplacements.ContainsKey(objectKind)) 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; return;
} }
@@ -214,7 +214,7 @@ public class CharacterDataFactory
if (cache.FileReplacements.ContainsKey(objectKind)) 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; return;
} }
@@ -225,7 +225,7 @@ public class CharacterDataFactory
cache.AddFileReplacement(objectKind, texFileReplacement); cache.AddFileReplacement(objectKind, texFileReplacement);
if (texPath.Contains("/--")) return; if (texPath.Contains("/--", StringComparison.Ordinal)) return;
var texDx11Replacement = var texDx11Replacement =
CreateFileReplacement(texPath.Insert(texPath.LastIndexOf('/') + 1, "--"), doNotReverseResolve); CreateFileReplacement(texPath.Insert(texPath.LastIndexOf('/') + 1, "--"), doNotReverseResolve);
@@ -322,13 +322,13 @@ public class CharacterDataFactory
previousData.FileReplacements.Add(objectKind, new()); 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 penumResolve = _ipcManager.PenumbraResolvePath(item.GamePaths.First()).ToLowerInvariant();
var gamePath = 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); transientResourceManager.RemoveTransientResource(charaPointer, item);
} }
else else

View File

@@ -6,14 +6,14 @@ using System.Globalization;
namespace MareSynchronos.FileCache; namespace MareSynchronos.FileCache;
public class FileCache public class FileCacheEntity
{ {
public string ResolvedFilepath { get; private set; } = string.Empty; public string ResolvedFilepath { get; private set; } = string.Empty;
public string Hash { get; set; } public string Hash { get; set; }
public string PrefixedFilePath { get; init; } public string PrefixedFilePath { get; init; }
public string LastModifiedDateTicks { get; set; } public string LastModifiedDateTicks { get; set; }
public FileCache(string hash, string path, string lastModifiedDateTicks) public FileCacheEntity(string hash, string path, string lastModifiedDateTicks)
{ {
Hash = hash; Hash = hash;
PrefixedFilePath = path; PrefixedFilePath = path;
@@ -22,7 +22,7 @@ public class FileCache
public void SetResolvedFilePath(string filePath) 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)}"; public string CsvEntry => $"{Hash}{FileCacheManager.CsvSplit}{PrefixedFilePath}{FileCacheManager.CsvSplit}{LastModifiedDateTicks.ToString(CultureInfo.InvariantCulture)}";

View File

@@ -26,9 +26,9 @@ public class FileCacheManager : IDisposable
private readonly Configuration _configuration; private readonly Configuration _configuration;
private readonly string CsvPath; private readonly string CsvPath;
private string CsvBakPath => CsvPath + ".bak"; 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 = "|"; public const string CsvSplit = "|";
private object _fileWriteLock = new object(); private object _fileWriteLock = new();
public FileCacheManager(IpcManager ipcManager, Configuration configuration, string configDirectoryName) public FileCacheManager(IpcManager ipcManager, Configuration configuration, string configDirectoryName)
{ {
@@ -52,7 +52,7 @@ public class FileCacheManager : IDisposable
var hash = splittedEntry[0]; var hash = splittedEntry[0];
var path = splittedEntry[1]; var path = splittedEntry[1];
var time = splittedEntry[2]; var time = splittedEntry[2];
FileCaches[path] = new FileCache(hash, path, time); FileCaches[path] = new FileCacheEntity(hash, path, time);
} }
catch (Exception) catch (Exception)
{ {
@@ -64,7 +64,7 @@ public class FileCacheManager : IDisposable
public void WriteOutFullCsv() public void WriteOutFullCsv()
{ {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new();
foreach (var entry in FileCaches.OrderBy(f => f.Value.PrefixedFilePath)) foreach (var entry in FileCaches.OrderBy(f => f.Value.PrefixedFilePath))
{ {
sb.AppendLine(entry.Value.CsvEntry); 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; return null;
} }
public (FileState, FileCache) ValidateFileCacheEntity(FileCache fileCache) public (FileState, FileCacheEntity) ValidateFileCacheEntity(FileCacheEntity fileCache)
{ {
fileCache = ReplacePathPrefixes(fileCache); fileCache = ReplacePathPrefixes(fileCache);
FileInfo fi = new(fileCache.ResolvedFilepath); FileInfo fi = new(fileCache.ResolvedFilepath);
@@ -107,7 +107,7 @@ public class FileCacheManager : IDisposable
{ {
return (FileState.RequireDeletion, fileCache); 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); return (FileState.RequireUpdate, fileCache);
} }
@@ -115,10 +115,10 @@ public class FileCacheManager : IDisposable
return (FileState.Valid, fileCache); return (FileState.Valid, fileCache);
} }
public FileCache? GetFileCacheByPath(string path) public FileCacheEntity? GetFileCacheByPath(string path)
{ {
var cleanedPath = path.Replace("/", "\\").ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), ""); var cleanedPath = path.Replace("/", "\\", StringComparison.Ordinal).ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), "", StringComparison.Ordinal);
var entry = FileCaches.FirstOrDefault(f => f.Value.ResolvedFilepath.EndsWith(cleanedPath)).Value; var entry = FileCaches.FirstOrDefault(f => f.Value.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.Ordinal)).Value;
if (entry == null) if (entry == null)
{ {
@@ -131,35 +131,35 @@ public class FileCacheManager : IDisposable
return validatedCacheEntry; 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); FileInfo fi = new(path);
if (!fi.Exists) return null; if (!fi.Exists) return null;
var fullName = fi.FullName.ToLowerInvariant(); var fullName = fi.FullName.ToLowerInvariant();
if (!fullName.Contains(_configuration.CacheFolder.ToLowerInvariant())) return null; if (!fullName.Contains(_configuration.CacheFolder.ToLowerInvariant(), StringComparison.Ordinal)) return null;
string prefixedPath = fullName.Replace(_configuration.CacheFolder.ToLowerInvariant(), CachePrefix + "\\").Replace("\\\\", "\\"); string prefixedPath = fullName.Replace(_configuration.CacheFolder.ToLowerInvariant(), CachePrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
return CreateFileCacheEntity(fi, prefixedPath, fi.Name.ToUpper()); 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); FileInfo fi = new(path);
if (!fi.Exists) return null; if (!fi.Exists) return null;
var fullName = fi.FullName.ToLowerInvariant(); var fullName = fi.FullName.ToLowerInvariant();
if (!fullName.Contains(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant())) return null; if (!fullName.Contains(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), StringComparison.Ordinal)) return null;
string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), PenumbraPrefix + "\\").Replace("\\\\", "\\"); string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), PenumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
return CreateFileCacheEntity(fi, prefixedPath); 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) if (hash == null)
{ {
hash = Crypto.GetFileHash(fileInfo.FullName); 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); entity = ReplacePathPrefixes(entity);
FileCaches[prefixedPath] = entity; FileCaches[prefixedPath] = entity;
lock (_fileWriteLock) lock (_fileWriteLock)
@@ -171,14 +171,14 @@ public class FileCacheManager : IDisposable
return result; return result;
} }
private FileCache? GetValidatedFileCache(FileCache fileCache) private FileCacheEntity? GetValidatedFileCache(FileCacheEntity fileCache)
{ {
var resulingFileCache = ReplacePathPrefixes(fileCache); var resulingFileCache = ReplacePathPrefixes(fileCache);
resulingFileCache = Validate(resulingFileCache); resulingFileCache = Validate(resulingFileCache);
return resulingFileCache; return resulingFileCache;
} }
private FileCache? Validate(FileCache fileCache) private FileCacheEntity? Validate(FileCacheEntity fileCache)
{ {
var file = new FileInfo(fileCache.ResolvedFilepath); var file = new FileInfo(fileCache.ResolvedFilepath);
if (!file.Exists) if (!file.Exists)
@@ -187,7 +187,7 @@ public class FileCacheManager : IDisposable
return null; return null;
} }
if (file.LastWriteTimeUtc.Ticks.ToString() != fileCache.LastModifiedDateTicks) if (!string.Equals(file.LastWriteTimeUtc.Ticks.ToString(), fileCache.LastModifiedDateTicks, StringComparison.Ordinal))
{ {
UpdateHash(fileCache); UpdateHash(fileCache);
} }
@@ -195,12 +195,12 @@ public class FileCacheManager : IDisposable
return fileCache; return fileCache;
} }
public void RemoveHash(FileCache entity) public void RemoveHash(FileCacheEntity entity)
{ {
FileCaches.Remove(entity.Hash, out _); FileCaches.Remove(entity.Hash, out _);
} }
public void UpdateHash(FileCache fileCache) public void UpdateHash(FileCacheEntity fileCache)
{ {
Logger.Debug("Updating hash for " + fileCache.ResolvedFilepath); Logger.Debug("Updating hash for " + fileCache.ResolvedFilepath);
fileCache.Hash = Crypto.GetFileHash(fileCache.ResolvedFilepath); fileCache.Hash = Crypto.GetFileHash(fileCache.ResolvedFilepath);
@@ -209,15 +209,15 @@ public class FileCacheManager : IDisposable
FileCaches[fileCache.PrefixedFilePath] = fileCache; 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; return fileCache;

View File

@@ -1,5 +1,6 @@
using System; using System;
using System.Collections.Concurrent; using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO; using System.IO;
using System.Linq; using System.Linq;
using System.Threading; using System.Threading;
@@ -19,7 +20,7 @@ public class PeriodicFileScanner : IDisposable
private readonly DalamudUtil _dalamudUtil; private readonly DalamudUtil _dalamudUtil;
private CancellationTokenSource? _scanCancellationTokenSource; private CancellationTokenSource? _scanCancellationTokenSource;
private Task? _fileScannerTask = null; 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) public PeriodicFileScanner(IpcManager ipcManager, Configuration pluginConfiguration, FileCacheManager fileDbManager, ApiController apiController, DalamudUtil dalamudUtil)
{ {
Logger.Verbose("Creating " + nameof(PeriodicFileScanner)); Logger.Verbose("Creating " + nameof(PeriodicFileScanner));
@@ -127,7 +128,7 @@ public class PeriodicFileScanner : IDisposable
{ {
while (haltScanLocks.Any(f => f.Value > 0)) while (haltScanLocks.Any(f => f.Value > 0))
{ {
await Task.Delay(TimeSpan.FromSeconds(1)); await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
} }
isForced |= RecalculateFileCacheSize(); isForced |= RecalculateFileCacheSize();
@@ -143,7 +144,7 @@ public class PeriodicFileScanner : IDisposable
_timeUntilNextScan = TimeSpan.FromSeconds(timeBetweenScans); _timeUntilNextScan = TimeSpan.FromSeconds(timeBetweenScans);
while (_timeUntilNextScan.TotalSeconds >= 0) while (_timeUntilNextScan.TotalSeconds >= 0)
{ {
await Task.Delay(TimeSpan.FromSeconds(1), token); await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
_timeUntilNextScan -= TimeSpan.FromSeconds(1); _timeUntilNextScan -= TimeSpan.FromSeconds(1);
} }
} }
@@ -203,13 +204,16 @@ public class PeriodicFileScanner : IDisposable
Logger.Debug("Getting files from " + penumbraDir + " and " + _pluginConfiguration.CacheFolder); Logger.Debug("Getting files from " + penumbraDir + " and " + _pluginConfiguration.CacheFolder);
string[] ext = { ".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".scd", ".skp" }; 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()) .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) .Concat(Directory.EnumerateFiles(_pluginConfiguration.CacheFolder, "*.*", SearchOption.TopDirectoryOnly)
.Where(f => new FileInfo(f).Name.Length == 40) .Where(f => new FileInfo(f).Name.Length == 40)
.Select(s => s.ToLowerInvariant()).ToList()) .Select(s => s.ToLowerInvariant()).ToList())
.ToDictionary(c => c, c => false); .Select(c => new KeyValuePair<string, bool>(c, false)), StringComparer.OrdinalIgnoreCase);
TotalFiles = scannedFiles.Count; TotalFiles = scannedFiles.Count;
@@ -217,8 +221,8 @@ public class PeriodicFileScanner : IDisposable
var cpuCount = (int)(Environment.ProcessorCount / 2.0f); var cpuCount = (int)(Environment.ProcessorCount / 2.0f);
Task[] dbTasks = Enumerable.Range(0, cpuCount).Select(c => Task.CompletedTask).ToArray(); Task[] dbTasks = Enumerable.Range(0, cpuCount).Select(c => Task.CompletedTask).ToArray();
ConcurrentBag<FileCache> entitiesToRemove = new(); ConcurrentBag<FileCacheEntity> entitiesToRemove = new();
ConcurrentBag<FileCache> entitiesToUpdate = new(); ConcurrentBag<FileCacheEntity> entitiesToUpdate = new();
try try
{ {
foreach (var cache in _fileDbManager.GetAllFileCaches()) foreach (var cache in _fileDbManager.GetAllFileCaches())
@@ -280,7 +284,7 @@ public class PeriodicFileScanner : IDisposable
_fileDbManager.WriteOutFullCsv(); _fileDbManager.WriteOutFullCsv();
} }
Logger.Debug("Scanner validated existing db files"); Logger.Verbose("Scanner validated existing db files");
if (ct.IsCancellationRequested) return; if (ct.IsCancellationRequested) return;
@@ -311,7 +315,7 @@ public class PeriodicFileScanner : IDisposable
Task.WaitAll(dbTasks); Task.WaitAll(dbTasks);
Logger.Debug("Scanner added new files to db"); Logger.Verbose("Scanner added new files to db");
Logger.Debug("Scan complete"); Logger.Debug("Scan complete");
TotalFiles = 0; TotalFiles = 0;

View File

@@ -4,35 +4,34 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene; using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.Interop.Structs; 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(0x18)] public IntPtr Parent;
[FieldOffset(0x20)] public IntPtr NextSibling; [FieldOffset(0x20)] public IntPtr NextSibling;
[FieldOffset(0x28)] public IntPtr PreviousSibling; [FieldOffset(0x28)] public IntPtr PreviousSibling;
[FieldOffset(0xA8)] public WeaponDrawObject* WeaponRenderModel; [FieldOffset(0xA8)] public WeaponDrawObject* WeaponRenderModel;
} }
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
public unsafe struct WeaponDrawObject public unsafe struct WeaponDrawObject
{ {
[FieldOffset(0x00)] public RenderModel* RenderModel; [FieldOffset(0x00)] public RenderModel* RenderModel;
} }
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
public unsafe struct HumanExt public unsafe struct HumanExt
{ {
[FieldOffset(0x0)] public Human Human; [FieldOffset(0x0)] public Human Human;
[FieldOffset(0x9E8)] public ResourceHandle* Decal; [FieldOffset(0x9E8)] public ResourceHandle* Decal;
[FieldOffset(0x9F0)] public ResourceHandle* LegacyBodyDecal; [FieldOffset(0x9F0)] public ResourceHandle* LegacyBodyDecal;
} }
[StructLayout(LayoutKind.Explicit)] [StructLayout(LayoutKind.Explicit)]
public unsafe struct CharaExt public unsafe struct CharaExt
{ {
[FieldOffset(0x0)] public Character Character; [FieldOffset(0x0)] public Character Character;
[FieldOffset(0x650)] public Character* Mount; [FieldOffset(0x650)] public Character* Mount;
}
} }

View File

@@ -1,9 +1,9 @@
using CheapLoc; using CheapLoc;
namespace MareSynchronos.Localization namespace MareSynchronos.Localization;
public static class Strings
{ {
public static class Strings
{
public class ToSStrings public class ToSStrings
{ {
public readonly string LanguageLabel = Loc.Localize("LanguageLabel", "Language"); public readonly string LanguageLabel = Loc.Localize("LanguageLabel", "Language");
@@ -64,5 +64,4 @@ namespace MareSynchronos.Localization
} }
public static ToSStrings ToS { get; set; } = new(); public static ToSStrings ToS { get; set; } = new();
}
} }

View File

@@ -71,7 +71,7 @@ public class CachedPlayer
if (characterData.GetHashCode() == _cachedData.GetHashCode()) return; if (characterData.GetHashCode() == _cachedData.GetHashCode()) return;
bool updateModdedPaths = false; bool updateModdedPaths = false;
List<ObjectKind> charaDataToUpdate = new List<ObjectKind>(); List<ObjectKind> charaDataToUpdate = new();
foreach (var objectKind in Enum.GetValues<ObjectKind>()) foreach (var objectKind in Enum.GetValues<ObjectKind>())
{ {
_cachedData.FileReplacements.TryGetValue(objectKind, out var existingFileReplacements); _cachedData.FileReplacements.TryGetValue(objectKind, out var existingFileReplacements);
@@ -108,7 +108,7 @@ public class CachedPlayer
if (hasNewAndOldGlamourerData) if (hasNewAndOldGlamourerData)
{ {
bool glamourerDataDifferent = _cachedData.GlamourerData[objectKind] != characterData.GlamourerData[objectKind]; bool glamourerDataDifferent = !string.Equals(_cachedData.GlamourerData[objectKind], characterData.GlamourerData[objectKind], StringComparison.Ordinal);
if (glamourerDataDifferent) if (glamourerDataDifferent)
{ {
Logger.Debug("Updating " + objectKind); Logger.Debug("Updating " + objectKind);
@@ -159,7 +159,7 @@ public class CachedPlayer
Logger.Debug("Downloading missing files for player " + PlayerName + ", kind: " + objectKind); Logger.Debug("Downloading missing files for player " + PlayerName + ", kind: " + objectKind);
if (toDownloadReplacements.Any()) if (toDownloadReplacements.Any())
{ {
await _apiController.DownloadFiles(downloadId, toDownloadReplacements, downloadToken); await _apiController.DownloadFiles(downloadId, toDownloadReplacements, downloadToken).ConfigureAwait(false);
_apiController.CancelDownload(downloadId); _apiController.CancelDownload(downloadId);
} }
if (downloadToken.IsCancellationRequested) if (downloadToken.IsCancellationRequested)
@@ -168,7 +168,7 @@ public class CachedPlayer
return; 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; break;
} }
@@ -195,7 +195,7 @@ public class CachedPlayer
private List<FileReplacementDto> TryCalculateModdedDictionary(out Dictionary<string, string> moddedDictionary) private List<FileReplacementDto> TryCalculateModdedDictionary(out Dictionary<string, string> moddedDictionary)
{ {
List<FileReplacementDto> missingFiles = new(); List<FileReplacementDto> missingFiles = new();
moddedDictionary = new Dictionary<string, string>(); moddedDictionary = new Dictionary<string, string>(StringComparer.Ordinal);
try try
{ {
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => string.IsNullOrEmpty(v.FileSwapPath))).ToList()) 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) private void IpcManagerOnPenumbraRedrawEvent(IntPtr address, int idx)
{ {
var player = _dalamudUtil.GetCharacterFromObjectTableByIndex(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; if (!_penumbraRedrawEventTask?.IsCompleted ?? false) return;
_penumbraRedrawEventTask = Task.Run(() => _penumbraRedrawEventTask = Task.Run(() =>

View File

@@ -8,13 +8,13 @@ using MareSynchronos.WebAPI;
using Action = System.Action; using Action = System.Action;
using System.Collections.Concurrent; 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<int> _glamourerApiVersion;
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyAll; private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyAll;
private readonly ICallGateSubscriber<GameObject?, string>? _glamourerGetAllCustomization; private readonly ICallGateSubscriber<GameObject?, string>? _glamourerGetAllCustomization;
@@ -158,7 +158,7 @@ namespace MareSynchronos.Managers
{ {
try try
{ {
return _heelsGetApiVersion.InvokeFunc() == "1.0.1"; return string.Equals(_heelsGetApiVersion.InvokeFunc(), "1.0.1", StringComparison.Ordinal);
} }
catch catch
{ {
@@ -397,5 +397,4 @@ namespace MareSynchronos.Managers
PenumbraDisposed?.Invoke(); PenumbraDisposed?.Invoke();
actionQueue.Clear(); actionQueue.Clear();
} }
}
} }

View File

@@ -19,8 +19,8 @@ public class OnlinePlayerManager : IDisposable
private readonly IpcManager _ipcManager; private readonly IpcManager _ipcManager;
private readonly PlayerManager _playerManager; private readonly PlayerManager _playerManager;
private readonly FileCacheManager _fileDbManager; private readonly FileCacheManager _fileDbManager;
private readonly ConcurrentDictionary<string, CachedPlayer> _onlineCachedPlayers = new(); private readonly ConcurrentDictionary<string, CachedPlayer> _onlineCachedPlayers = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<string, CharacterCacheDto> _temporaryStoredCharacterCache = new(); private readonly ConcurrentDictionary<string, CharacterCacheDto> _temporaryStoredCharacterCache = new(StringComparer.Ordinal);
private readonly ConcurrentDictionary<CachedPlayer, CancellationTokenSource> _playerTokenDisposal = new(); private readonly ConcurrentDictionary<CachedPlayer, CancellationTokenSource> _playerTokenDisposal = new();
private List<string> OnlineVisiblePlayerHashes => _onlineCachedPlayers.Select(p => p.Value).Where(p => p.PlayerCharacter != IntPtr.Zero) 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; _fileDbManager = fileDbManager;
_apiController.PairedClientOnline += ApiControllerOnPairedClientOnline; _apiController.PairedClientOnline += ApiControllerOnPairedClientOnline;
_apiController.PairedClientOffline += ApiControllerOnPairedClientOffline; _apiController.PairedClientOffline += ApiControllerOnPairedClientOffline;
_apiController.PairedWithOther += ApiControllerOnPairedWithOther;
_apiController.UnpairedFromOther += ApiControllerOnUnpairedFromOther;
_apiController.Connected += ApiControllerOnConnected; _apiController.Connected += ApiControllerOnConnected;
_apiController.Disconnected += ApiControllerOnDisconnected; _apiController.Disconnected += ApiControllerOnDisconnected;
_apiController.CharacterReceived += ApiControllerOnCharacterReceived; _apiController.CharacterReceived += ApiControllerOnCharacterReceived;
@@ -137,8 +135,6 @@ public class OnlinePlayerManager : IDisposable
_apiController.PairedClientOnline -= ApiControllerOnPairedClientOnline; _apiController.PairedClientOnline -= ApiControllerOnPairedClientOnline;
_apiController.PairedClientOffline -= ApiControllerOnPairedClientOffline; _apiController.PairedClientOffline -= ApiControllerOnPairedClientOffline;
_apiController.PairedWithOther -= ApiControllerOnPairedWithOther;
_apiController.UnpairedFromOther -= ApiControllerOnUnpairedFromOther;
_apiController.Disconnected -= ApiControllerOnDisconnected; _apiController.Disconnected -= ApiControllerOnDisconnected;
_apiController.Connected -= ApiControllerOnConnected; _apiController.Connected -= ApiControllerOnConnected;
@@ -169,20 +165,6 @@ public class OnlinePlayerManager : IDisposable
return; 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) private void AddPlayer(string characterNameHash)
{ {
if (_onlineCachedPlayers.TryGetValue(characterNameHash, out var cachedPlayer)) if (_onlineCachedPlayers.TryGetValue(characterNameHash, out var cachedPlayer))
@@ -244,7 +226,7 @@ public class OnlinePlayerManager : IDisposable
Task.Run(async () => Task.Run(async () =>
{ {
await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData, await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData,
visiblePlayers); visiblePlayers).ConfigureAwait(false);
}); });
} }
} }

View File

@@ -14,12 +14,12 @@ using MareSynchronos.FileCache;
using Newtonsoft.Json; using Newtonsoft.Json;
#endif #endif
namespace MareSynchronos.Managers namespace MareSynchronos.Managers;
{
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
public class PlayerManager : IDisposable public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
{
public class PlayerManager : IDisposable
{
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly CharacterDataFactory _characterDataFactory; private readonly CharacterDataFactory _characterDataFactory;
private readonly DalamudUtil _dalamudUtil; private readonly DalamudUtil _dalamudUtil;
@@ -34,7 +34,7 @@ namespace MareSynchronos.Managers
private CancellationTokenSource? _playerChangedCts = new(); private CancellationTokenSource? _playerChangedCts = new();
private CancellationTokenSource _transientUpdateCts = new(); private CancellationTokenSource _transientUpdateCts = new();
private List<PlayerRelatedObject> playerRelatedObjects = new List<PlayerRelatedObject>(); private List<PlayerRelatedObject> playerRelatedObjects = new();
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager, public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager, CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager,
@@ -88,7 +88,7 @@ namespace MareSynchronos.Managers
Task.Run(async () => Task.Run(async () =>
{ {
Logger.Debug("Delaying transient resource load update"); 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; if (obj.HasUnprocessedUpdate || token.IsCancellationRequested) return;
Logger.Debug("Firing transient resource load update"); Logger.Debug("Firing transient resource load update");
obj.HasTransientsUpdate = true; obj.HasTransientsUpdate = true;
@@ -169,7 +169,7 @@ namespace MareSynchronos.Managers
while (!PermanentDataCache.IsReady && !token.IsCancellationRequested) while (!PermanentDataCache.IsReady && !token.IsCancellationRequested)
{ {
Logger.Verbose("Waiting until cache is ready"); Logger.Verbose("Waiting until cache is ready");
await Task.Delay(50, token); await Task.Delay(50, token).ConfigureAwait(false);
} }
if (token.IsCancellationRequested) return null; if (token.IsCancellationRequested) return null;
@@ -215,7 +215,7 @@ namespace MareSynchronos.Managers
var token = _playerChangedCts.Token; var token = _playerChangedCts.Token;
// fix for redraw from anamnesis // 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"); Logger.Debug("Waiting Until Player is Present");
Thread.Sleep(100); Thread.Sleep(100);
@@ -234,6 +234,9 @@ namespace MareSynchronos.Managers
} }
Task.Run(async () => Task.Run(async () =>
{
CharacterCacheDto? cacheDto = null;
try
{ {
_periodicFileScanner.HaltScan("Character creation"); _periodicFileScanner.HaltScan("Character creation");
foreach (var item in unprocessedObjects) foreach (var item in unprocessedObjects)
@@ -241,8 +244,13 @@ namespace MareSynchronos.Managers
_dalamudUtil.WaitWhileCharacterIsDrawing("self " + item.ObjectKind.ToString(), item.Address, 10000, token); _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"); _periodicFileScanner.ResumeScan("Character creation");
}
if (cacheDto == null || token.IsCancellationRequested) return; if (cacheDto == null || token.IsCancellationRequested) return;
#if DEBUG #if DEBUG
@@ -267,5 +275,4 @@ namespace MareSynchronos.Managers
} }
}, token); }, token);
} }
}
} }

View File

@@ -8,12 +8,12 @@ using System.Linq;
using System.Threading; using System.Threading;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace MareSynchronos.Managers namespace MareSynchronos.Managers;
{
public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
public class TransientResourceManager : IDisposable public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
{
public class TransientResourceManager : IDisposable
{
private readonly IpcManager manager; private readonly IpcManager manager;
private readonly DalamudUtil dalamudUtil; private readonly DalamudUtil dalamudUtil;
@@ -82,7 +82,7 @@ namespace MareSynchronos.Managers
private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath) 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; return;
} }
@@ -93,25 +93,25 @@ namespace MareSynchronos.Managers
if (!TransientResources.ContainsKey(gameObject)) 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.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) || if (TransientResources[gameObject].Contains(replacedGamePath) ||
SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f.GamePaths.First(), replacedGamePath , StringComparison.OrdinalIgnoreCase)
&& f.ResolvedPath.ToLowerInvariant() == filePath))) && string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase))))
{ {
Logger.Debug("Not adding " + replacedGamePath + ":" + filePath); Logger.Verbose("Not adding " + replacedGamePath + ":" + filePath);
Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => f.GamePaths.First().ToLowerInvariant() == replacedGamePath Logger.Verbose("SemiTransientAny: " + SemiTransientResources.Any(r => r.Value.Any(f => string.Equals(f.GamePaths.First(), replacedGamePath, StringComparison.OrdinalIgnoreCase)
&& f.ResolvedPath.ToLowerInvariant() == filePath)).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath)); && string.Equals(f.ResolvedPath, filePath, StringComparison.OrdinalIgnoreCase))).ToString() + ", TransientAny: " + TransientResources[gameObject].Contains(replacedGamePath));
} }
else else
{ {
@@ -125,7 +125,7 @@ namespace MareSynchronos.Managers
{ {
if (TransientResources.ContainsKey(gameObject)) 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"); Logger.Debug("Persisting " + transientResources.Count + " transient resources");
foreach (var gamePath in transientResources) 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) if (existingResource)
{ {
Logger.Debug("Semi Transient resource replaced: " + gamePath); 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 try
@@ -196,10 +196,9 @@ namespace MareSynchronos.Managers
SemiTransientResources[objectKind] = new HashSet<FileReplacement>(); 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); SemiTransientResources[objectKind].Add(item);
} }
} }
}
} }

View File

@@ -3,7 +3,7 @@
<PropertyGroup> <PropertyGroup>
<Authors></Authors> <Authors></Authors>
<Company></Company> <Company></Company>
<Version>0.4.24</Version> <Version>0.5.0</Version>
<Description></Description> <Description></Description>
<Copyright></Copyright> <Copyright></Copyright>
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl> <PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>
@@ -28,6 +28,10 @@
<ItemGroup> <ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.8" /> <PackageReference Include="DalamudPackager" Version="2.1.8" />
<PackageReference Include="lz4net" Version="1.0.15.93" /> <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" /> <PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.8" />
</ItemGroup> </ItemGroup>
@@ -86,4 +90,8 @@
<EmbeddedResource Include="Localization\fr.json" /> <EmbeddedResource Include="Localization\fr.json" />
</ItemGroup> </ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
</Project> </Project>

View File

@@ -6,11 +6,11 @@ using MareSynchronos.API;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using Lumina.Excel.GeneratedSheets; using Lumina.Excel.GeneratedSheets;
namespace MareSynchronos.Models namespace MareSynchronos.Models;
[JsonObject(MemberSerialization.OptIn)]
public class CharacterData
{ {
[JsonObject(MemberSerialization.OptIn)]
public class CharacterData
{
[JsonProperty] [JsonProperty]
public Dictionary<ObjectKind, List<FileReplacement>> FileReplacements { get; set; } = new(); 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>()); 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) 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 else
{ {
@@ -44,11 +44,11 @@ namespace MareSynchronos.Models
public CharacterCacheDto ToCharacterCacheDto() 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() 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, Hash = g.First().Hash,
}; };
}).ToList()); }).ToList());
@@ -84,5 +84,4 @@ namespace MareSynchronos.Models
} }
return stringBuilder.ToString(); return stringBuilder.ToString();
} }
}
} }

View File

@@ -6,10 +6,10 @@ using MareSynchronos.API;
using System.Text.RegularExpressions; using System.Text.RegularExpressions;
using MareSynchronos.FileCache; using MareSynchronos.FileCache;
namespace MareSynchronos.Models namespace MareSynchronos.Models;
public class FileReplacement
{ {
public class FileReplacement
{
private readonly FileCacheManager fileDbManager; private readonly FileCacheManager fileDbManager;
public FileReplacement(FileCacheManager fileDbManager) public FileReplacement(FileCacheManager fileDbManager)
@@ -21,9 +21,9 @@ namespace MareSynchronos.Models
public List<string> GamePaths { get; set; } = new(); 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; public string Hash { get; set; } = string.Empty;
@@ -56,5 +56,4 @@ namespace MareSynchronos.Models
builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}"); builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}");
return builder.ToString(); return builder.ToString();
} }
}
} }

View File

@@ -5,10 +5,10 @@ using System.Runtime.InteropServices;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using Penumbra.GameData.ByteString; using Penumbra.GameData.ByteString;
namespace MareSynchronos.Models namespace MareSynchronos.Models;
public class PlayerRelatedObject
{ {
public class PlayerRelatedObject
{
private readonly Func<IntPtr> getAddress; private readonly Func<IntPtr> getAddress;
public unsafe Character* Character => (Character*)Address; public unsafe Character* Character => (Character*)Address;
@@ -61,7 +61,7 @@ namespace MareSynchronos.Models
bool equip = CompareAndUpdateByteData(chara->EquipSlotData, chara->CustomizeData); bool equip = CompareAndUpdateByteData(chara->EquipSlotData, chara->CustomizeData);
bool drawObj = (IntPtr)chara->GameObject.DrawObject != DrawObjectAddress; bool drawObj = (IntPtr)chara->GameObject.DrawObject != DrawObjectAddress;
var name = new Utf8String(chara->GameObject.Name).ToString(); 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) if (addr || equip || drawObj || nameChange)
{ {
_name = name; _name = name;
@@ -132,5 +132,4 @@ namespace MareSynchronos.Models
return hasChanges; return hasChanges;
} }
}
} }

View File

@@ -14,11 +14,12 @@ using MareSynchronos.UI;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using Dalamud.Game.ClientState.Conditions; using Dalamud.Game.ClientState.Conditions;
using MareSynchronos.FileCache; 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 const string CommandName = "/mare";
private readonly ApiController _apiController; private readonly ApiController _apiController;
private readonly CommandManager _commandManager; private readonly CommandManager _commandManager;
@@ -174,7 +175,7 @@ namespace MareSynchronos
{ {
while (!_dalamudUtil.IsPlayerPresent) while (!_dalamudUtil.IsPlayerPresent)
{ {
await Task.Delay(100); await Task.Delay(100).ConfigureAwait(false);
} }
try try
@@ -235,5 +236,4 @@ namespace MareSynchronos
else else
_introUi.Toggle(); _introUi.Toggle();
} }
}
} }

View File

@@ -1,6 +1,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Globalization;
using System.Linq; using System.Linq;
using System.Numerics; using System.Numerics;
using System.Reflection; using System.Reflection;
@@ -14,26 +15,31 @@ using MareSynchronos.API;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using MareSynchronos.WebAPI; 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 ApiController _apiController;
private readonly Configuration _configuration; 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 UiShared _uiShared;
private readonly WindowSystem _windowSystem; private readonly WindowSystem _windowSystem;
private string _characterOrCommentFilter = string.Empty; private string _characterOrCommentFilter = string.Empty;
private string _editCharComment = string.Empty; public string EditUserComment = string.Empty;
private string _editNickEntry = string.Empty; public string EditNickEntry = string.Empty;
private string _pairToAdd = string.Empty; private string _pairToAdd = string.Empty;
private readonly Stopwatch _timeout = new(); private readonly Stopwatch _timeout = new();
private bool _buttonState; 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, public CompactUi(WindowSystem windowSystem,
UiShared uiShared, Configuration configuration, ApiController apiController) : base("###MareSynchronosMainUI") UiShared uiShared, Configuration configuration, ApiController apiController) : base("###MareSynchronosMainUI")
@@ -54,7 +60,7 @@ namespace MareSynchronos.UI
this.WindowName = "Mare Synchronos " + dateTime + "###MareSynchronosMainUI"; this.WindowName = "Mare Synchronos " + dateTime + "###MareSynchronosMainUI";
Toggle(); Toggle();
#else #else
this.WindowName = "Mare Synchronos " + Assembly.GetExecutingAssembly().GetName().Version; this.WindowName = "Mare Synchronos " + Assembly.GetExecutingAssembly().GetName().Version + "###MareSynchronosMainUI";
#endif #endif
Logger.Verbose("Creating " + nameof(CompactUi)); Logger.Verbose("Creating " + nameof(CompactUi));
@@ -63,10 +69,12 @@ namespace MareSynchronos.UI
_configuration = configuration; _configuration = configuration;
_apiController = apiController; _apiController = apiController;
groupPanel = new(this, uiShared, configuration, apiController);
SizeConstraints = new WindowSizeConstraints() SizeConstraints = new WindowSizeConstraints()
{ {
MinimumSize = new Vector2(300, 400), MinimumSize = new Vector2(350, 400),
MaximumSize = new Vector2(300, 2000), MaximumSize = new Vector2(350, 2000),
}; };
windowSystem.AddWindow(this); windowSystem.AddWindow(this);
@@ -88,29 +96,74 @@ namespace MareSynchronos.UI
if (_apiController.ServerState is ServerState.Connected) 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(); ImGui.Separator();
if (!hasShownSyncShells)
{
UiShared.DrawWithID("pairlist", DrawPairList); UiShared.DrawWithID("pairlist", DrawPairList);
}
else
{
UiShared.DrawWithID("syncshells", groupPanel.DrawSyncshells);
}
ImGui.Separator(); ImGui.Separator();
UiShared.DrawWithID("transfers", DrawTransfers); UiShared.DrawWithID("transfers", DrawTransfers);
_transferPartHeight = ImGui.GetCursorPosY() - _transferPartHeight; TransferPartHeight = ImGui.GetCursorPosY() - TransferPartHeight;
} }
} }
public override void OnClose() public override void OnClose()
{ {
_editNickEntry = string.Empty; EditNickEntry = string.Empty;
_editCharComment = string.Empty; EditUserComment = string.Empty;
base.OnClose(); base.OnClose();
} }
private void DrawAddPair() private void DrawAddPair()
{ {
var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus); var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus);
ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonSize.X); 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); ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus)) 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); _ = _apiController.SendPairedClientAddition(_pairToAdd);
_pairToAdd = string.Empty; _pairToAdd = string.Empty;
@@ -132,7 +185,7 @@ namespace MareSynchronos.UI
_configuration.ReverseUserSort = true; _configuration.ReverseUserSort = true;
_configuration.Save(); _configuration.Save();
} }
UiShared.AttachToolTip("Sort by newest additions first"); UiShared.AttachToolTip("Sort by name descending");
} }
else else
{ {
@@ -141,7 +194,7 @@ namespace MareSynchronos.UI
_configuration.ReverseUserSort = false; _configuration.ReverseUserSort = false;
_configuration.Save(); _configuration.Save();
} }
UiShared.AttachToolTip("Sort by oldest additions first"); UiShared.AttachToolTip("Sort by name ascending");
} }
ImGui.SameLine(); ImGui.SameLine();
@@ -246,7 +299,7 @@ namespace MareSynchronos.UI
} }
var textIsUid = true; 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 (!showUidInsteadOfName && _configuration.GetCurrentServerUidComments().TryGetValue(entry.OtherUID, out var playerText))
{ {
if (string.IsNullOrEmpty(playerText)) if (string.IsNullOrEmpty(playerText))
@@ -264,7 +317,7 @@ namespace MareSynchronos.UI
} }
ImGui.SameLine(); ImGui.SameLine();
if (_editNickEntry != entry.OtherUID) if (!string.Equals(EditNickEntry, entry.OtherUID, StringComparison.Ordinal))
{ {
ImGui.SetCursorPosY(textPos); ImGui.SetCursorPosY(textPos);
if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont); if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont);
@@ -275,22 +328,22 @@ namespace MareSynchronos.UI
if (ImGui.IsItemClicked(ImGuiMouseButton.Left)) if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{ {
var prevState = textIsUid; 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)) if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{ {
_configuration.SetCurrentServerUidComment(_editNickEntry, _editCharComment); _configuration.SetCurrentServerUidComment(EditNickEntry, EditUserComment);
_configuration.Save(); _configuration.Save();
_editCharComment = _configuration.GetCurrentServerUidComments().ContainsKey(entry.OtherUID) EditUserComment = _configuration.GetCurrentServerUidComments().ContainsKey(entry.OtherUID)
? _configuration.GetCurrentServerUidComments()[entry.OtherUID] ? _configuration.GetCurrentServerUidComments()[entry.OtherUID]
: string.Empty; : string.Empty;
_editNickEntry = entry.OtherUID; EditNickEntry = entry.OtherUID;
} }
} }
else else
@@ -298,16 +351,16 @@ namespace MareSynchronos.UI
ImGui.SetCursorPosY(originalY); ImGui.SetCursorPosY(originalY);
ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * 2); 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(); _configuration.Save();
_editNickEntry = string.Empty; EditNickEntry = string.Empty;
} }
if (ImGui.IsItemClicked(ImGuiMouseButton.Right)) if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{ {
_editNickEntry = string.Empty; EditNickEntry = string.Empty;
} }
UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel"); UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel");
} }
@@ -319,7 +372,6 @@ namespace MareSynchronos.UI
if (UiShared.CtrlPressed()) if (UiShared.CtrlPressed())
{ {
_ = _apiController.SendPairedClientRemoval(entry.OtherUID); _ = _apiController.SendPairedClientRemoval(entry.OtherUID);
_apiController.PairedClients.Remove(entry);
} }
} }
UiShared.AttachToolTip("Hold CTRL and click to unpair permanently from " + entryUID); UiShared.AttachToolTip("Hold CTRL and click to unpair permanently from " + entryUID);
@@ -342,17 +394,18 @@ namespace MareSynchronos.UI
{ {
UiShared.DrawWithID("addpair", DrawAddPair); UiShared.DrawWithID("addpair", DrawAddPair);
UiShared.DrawWithID("pairs", DrawPairs); UiShared.DrawWithID("pairs", DrawPairs);
_transferPartHeight = ImGui.GetCursorPosY(); TransferPartHeight = ImGui.GetCursorPosY();
UiShared.DrawWithID("filter", DrawFilter); UiShared.DrawWithID("filter", DrawFilter);
} }
private void DrawPairs() private void DrawPairs()
{ {
var ySize = _transferPartHeight == 0 var ySize = TransferPartHeight == 0
? 1 ? 1
: (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - _transferPartHeight - ImGui.GetCursorPosY(); : (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - TransferPartHeight - ImGui.GetCursorPosY();
var users = GetFilteredUsers(); 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(); if (_configuration.ReverseUserSort) users = users.Reverse();
ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), false); ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), false);
@@ -363,6 +416,8 @@ namespace MareSynchronos.UI
ImGui.EndChild(); ImGui.EndChild();
} }
private IEnumerable<ClientPairDto> GetFilteredUsers() private IEnumerable<ClientPairDto> GetFilteredUsers()
{ {
return _apiController.PairedClients.Where(p => return _apiController.PairedClients.Where(p =>
@@ -370,17 +425,23 @@ namespace MareSynchronos.UI
if (_characterOrCommentFilter.IsNullOrEmpty()) return true; if (_characterOrCommentFilter.IsNullOrEmpty()) return true;
_configuration.GetCurrentServerUidComments().TryGetValue(p.OtherUID, out var comment); _configuration.GetCurrentServerUidComments().TryGetValue(p.OtherUID, out var comment);
var uid = p.VanityUID.IsNullOrEmpty() ? p.OtherUID : p.VanityUID; var uid = p.VanityUID.IsNullOrEmpty() ? p.OtherUID : p.VanityUID;
return uid.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) || return uid.Contains(_characterOrCommentFilter, StringComparison.OrdinalIgnoreCase) ||
(comment?.ToLowerInvariant().Contains(_characterOrCommentFilter.ToLowerInvariant()) ?? false); (comment?.Contains(_characterOrCommentFilter, StringComparison.OrdinalIgnoreCase) ?? false);
}); });
} }
private void DrawServerStatus() private void DrawServerStatus()
{ {
var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Link); var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Link);
var userCount = _apiController.OnlineUsers.ToString(); var userCount = _apiController.OnlineUsers.ToString(CultureInfo.InvariantCulture);
var userSize = ImGui.CalcTextSize(userCount); var userSize = ImGui.CalcTextSize(userCount);
var textSize = ImGui.CalcTextSize("Users Online"); 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) if (_apiController.ServerState is ServerState.Connected)
{ {
@@ -410,6 +471,12 @@ namespace MareSynchronos.UI
} }
ImGui.PopStyleColor(); ImGui.PopStyleColor();
UiShared.AttachToolTip(!_configuration.FullPause ? "Disconnect from " + _apiController.ServerDictionary[_configuration.ApiUri] : "Connect to " + _apiController.ServerDictionary[_configuration.ApiUri]); 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() private void DrawTransfers()
@@ -559,5 +626,4 @@ namespace MareSynchronos.UI
_ => string.Empty _ => string.Empty
}; };
} }
}
} }

View 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();
}
}
}
}

View File

@@ -12,10 +12,10 @@ using MareSynchronos.Localization;
using Dalamud.Utility; using Dalamud.Utility;
using MareSynchronos.FileCache; 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 UiShared _uiShared;
private readonly Configuration _pluginConfiguration; private readonly Configuration _pluginConfiguration;
private readonly PeriodicFileScanner _fileCacheManager; private readonly PeriodicFileScanner _fileCacheManager;
@@ -37,12 +37,12 @@ namespace MareSynchronos.UI
private Task _timeoutTask; private Task _timeoutTask;
private string _timeoutTime; 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 int _currentLanguage;
private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1.Trim() private bool DarkSoulsCaptchaValid => string.Equals(_darkSoulsCaptcha1.Item2, _enteredDarkSoulsCaptcha1.Trim()
&& _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2.Trim() , StringComparison.Ordinal) && string.Equals(_darkSoulsCaptcha2.Item2, _enteredDarkSoulsCaptcha2.Trim()
&& _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3.Trim(); , StringComparison.Ordinal) && string.Equals(_darkSoulsCaptcha3.Item2, _enteredDarkSoulsCaptcha3.Trim(), StringComparison.Ordinal);
public void Dispose() public void Dispose()
@@ -158,7 +158,7 @@ namespace MareSynchronos.UI
{ {
_timeoutTime = $"{i}s " + Strings.ToS.RemainingLabel; _timeoutTime = $"{i}s " + Strings.ToS.RemainingLabel;
Logger.Debug(_timeoutTime); 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() private Tuple<string, string> GetCaptchaTuple()
{ {
Random random = new Random(); Random random = new();
var paragraphIdx = random.Next(TosParagraphs.Length); var paragraphIdx = random.Next(TosParagraphs.Length);
var splitParagraph = TosParagraphs[paragraphIdx].Split(".", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray(); var splitParagraph = TosParagraphs[paragraphIdx].Split(".", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray();
var sentenceIdx = random.Next(splitParagraph.Length); 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); 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]); return new($"{Strings.ToS.ParagraphLabel} {paragraphIdx + 1}, {Strings.ToS.SentenceLabel} {sentenceIdx + 1}, {Strings.ToS.WordLabel} {wordIdx + 1}", splitSentence[wordIdx]);
} }
}
} }

View File

@@ -14,11 +14,11 @@ using MareSynchronos.WebAPI.Utils;
using System.Diagnostics; using System.Diagnostics;
using Dalamud.Utility; 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 Configuration _configuration;
private readonly WindowSystem _windowSystem; private readonly WindowSystem _windowSystem;
private readonly ApiController _apiController; private readonly ApiController _apiController;
@@ -335,7 +335,7 @@ namespace MareSynchronos.UI
}); });
} }
ImGui.SameLine(); ImGui.SameLine();
if (onlineUser.UID != _apiController.UID && _apiController.IsAdmin) if (!string.Equals(onlineUser.UID, _apiController.UID, StringComparison.Ordinal) && _apiController.IsAdmin)
{ {
if (!onlineUser.IsModerator) if (!onlineUser.IsModerator)
{ {
@@ -609,5 +609,4 @@ namespace MareSynchronos.UI
_uiShared.EditTrackerPosition = false; _uiShared.EditTrackerPosition = false;
base.OnClose(); base.OnClose();
} }
}
} }

View File

@@ -18,10 +18,10 @@ using MareSynchronos.Managers;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using MareSynchronos.WebAPI; using MareSynchronos.WebAPI;
namespace MareSynchronos.UI namespace MareSynchronos.UI;
public class UiShared : IDisposable
{ {
public class UiShared : IDisposable
{
[DllImport("user32")] [DllImport("user32")]
public static extern short GetKeyState(int nVirtKey); public static extern short GetKeyState(int nVirtKey);
@@ -39,8 +39,8 @@ namespace MareSynchronos.UI
public bool EditTrackerPosition { get; set; } public bool EditTrackerPosition { get; set; }
public ImFontPtr UidFont { get; private set; } public ImFontPtr UidFont { get; private set; }
public bool UidFontBuilt { get; private set; } public bool UidFontBuilt { get; private set; }
public static bool CtrlPressed() => (GetKeyState(0xA2) & 0x8000) != 0 || (GetKeyState(0xA3) & 0x8000) != 0; 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; public ApiController ApiController => _apiController;
@@ -88,8 +88,8 @@ namespace MareSynchronos.UI
} }
catch (Exception ex) catch (Exception ex)
{ {
Logger.Debug($"Font failed to load. {fontFile}"); Logger.Warn($"Font failed to load. {fontFile}");
Logger.Debug(ex.ToString()); Logger.Warn(ex.ToString());
} }
} }
else else
@@ -294,7 +294,7 @@ namespace MareSynchronos.UI
bool isSelected = _serverSelectionIndex == i; bool isSelected = _serverSelectionIndex == i;
if (ImGui.Selectable(comboEntries[i], isSelected)) 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(); _pluginConfiguration.Save();
_ = _apiController.CreateConnections(); _ = _apiController.CreateConnections();
} }
@@ -451,7 +451,7 @@ namespace MareSynchronos.UI
{ {
if (!success) return; if (!success) return;
_isPenumbraDirectory = path.ToLowerInvariant() == _ipcManager.PenumbraModDirectory()?.ToLowerInvariant(); _isPenumbraDirectory = string.Equals(path.ToLowerInvariant(), _ipcManager.PenumbraModDirectory()?.ToLowerInvariant(), StringComparison.Ordinal);
_isDirectoryWritable = IsDirectoryWritable(path); _isDirectoryWritable = IsDirectoryWritable(path);
_cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => new FileInfo(f).Name.Length != 40); _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); _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."); 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() public void Dispose()
{ {
_pluginInterface.UiBuilder.BuildFonts -= BuildFont; _pluginInterface.UiBuilder.BuildFonts -= BuildFont;
} }
}
} }

View File

@@ -4,32 +4,31 @@ using System.Security.Cryptography;
using System.Text; using System.Text;
using Dalamud.Game.ClientState.Objects.SubKinds; using Dalamud.Game.ClientState.Objects.SubKinds;
namespace MareSynchronos.Utils namespace MareSynchronos.Utils;
public class Crypto
{ {
public class Crypto
{
public static string GetFileHash(string filePath) public static string GetFileHash(string filePath)
{ {
using SHA1CryptoServiceProvider cryptoProvider = new(); 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) public static string GetHash(string stringToHash)
{ {
using SHA1CryptoServiceProvider cryptoProvider = new(); 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) public static string GetHash256(string stringToHash)
{ {
using SHA256CryptoServiceProvider cryptoProvider = new(); 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) public static string GetHash256(PlayerCharacter character)
{ {
using SHA256CryptoServiceProvider cryptoProvider = new(); 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);
}
} }
} }

View 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();
}
}

View File

@@ -11,19 +11,19 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.Game.Character; using FFXIVClientStructs.FFXIV.Client.Game.Character;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject; 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 ClientState _clientState;
private readonly ObjectTable _objectTable; private readonly ObjectTable _objectTable;
private readonly Framework _framework; private readonly Framework _framework;
@@ -186,7 +186,7 @@ namespace MareSynchronos.Utils
{ {
return _objectTable.Where(obj => return _objectTable.Where(obj =>
obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player && 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) public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index)
@@ -201,7 +201,7 @@ namespace MareSynchronos.Utils
foreach (var item in _objectTable) foreach (var item in _objectTable)
{ {
if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue; 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; return null;
@@ -209,7 +209,7 @@ namespace MareSynchronos.Utils
public async Task<T> RunOnFrameworkThread<T>(Func<T> func) 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) public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, int timeOut = 5000, CancellationToken? ct = null)
@@ -238,5 +238,4 @@ namespace MareSynchronos.Utils
_clientState.Logout -= ClientStateOnLogout; _clientState.Logout -= ClientStateOnLogout;
_framework.Update -= FrameworkOnUpdate; _framework.Update -= FrameworkOnUpdate;
} }
}
} }

View File

@@ -1,35 +1,13 @@
using System; using System;
using System.Collections.Concurrent;
using System.Diagnostics; using System.Diagnostics;
using Dalamud.Logging; using Dalamud.Logging;
using Dalamud.Utility; using Dalamud.Utility;
using Microsoft.Extensions.Logging; 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; private readonly string name;
public static void Info(string info) public static void Info(string info)
@@ -41,7 +19,7 @@ namespace MareSynchronos.Utils
public static void Debug(string debug, string stringToHighlight = "") public static void Debug(string debug, string stringToHighlight = "")
{ {
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown"; 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}"); PluginLog.Warning($"[{caller}] {debug}");
} }
@@ -108,5 +86,4 @@ namespace MareSynchronos.Utils
} }
public IDisposable BeginScope<TState>(TState state) => default!; public IDisposable BeginScope<TState>(TState state) => default!;
}
} }

View File

@@ -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;
}
}
}

View 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;
}
}

View File

@@ -13,10 +13,10 @@ using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils; using MareSynchronos.WebAPI.Utils;
using Microsoft.AspNetCore.SignalR.Client; 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 readonly HashSet<string> _verifiedUploadedHashes;
private int _downloadId = 0; private int _downloadId = 0;
@@ -33,7 +33,7 @@ namespace MareSynchronos.WebAPI
public async Task DeleteAllMyFiles() 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) private async Task<string> DownloadFile(int downloadId, string hash, Uri downloadUri, CancellationToken ct)
@@ -44,7 +44,7 @@ namespace MareSynchronos.WebAPI
{ {
try 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) catch (Exception ex)
{ {
@@ -61,11 +61,11 @@ namespace MareSynchronos.WebAPI
try try
{ {
await wc.DownloadFileTaskAsync(downloadUri, fileName); await wc.DownloadFileTaskAsync(downloadUri, fileName).ConfigureAwait(false);
} }
catch { } 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; wc.DownloadProgressChanged -= progChanged;
return fileName; return fileName;
@@ -78,7 +78,7 @@ namespace MareSynchronos.WebAPI
DownloadStarted?.Invoke(); DownloadStarted?.Invoke();
try try
{ {
await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct); await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct).ConfigureAwait(false);
} }
catch catch
{ {
@@ -94,8 +94,8 @@ namespace MareSynchronos.WebAPI
{ {
Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")"); Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")");
List<DownloadFileDto> downloadFileInfoFromService = new List<DownloadFileDto>(); List<DownloadFileDto> downloadFileInfoFromService = new();
downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync<List<DownloadFileDto>>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct)); 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))); 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)) 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)); ForbiddenTransfers.Add(new DownloadFileTransfer(dto));
} }
@@ -118,7 +118,7 @@ namespace MareSynchronos.WebAPI
async (file, token) => async (file, token) =>
{ {
var hash = file.Hash; 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) if (token.IsCancellationRequested)
{ {
File.Delete(tempFile); File.Delete(tempFile);
@@ -128,16 +128,16 @@ namespace MareSynchronos.WebAPI
return; return;
} }
var tempFileData = await File.ReadAllBytesAsync(tempFile, token); var tempFileData = await File.ReadAllBytesAsync(tempFile, token).ConfigureAwait(false);
var extractedFile = LZ4Codec.Unwrap(tempFileData); var extractedFile = LZ4Codec.Unwrap(tempFileData);
File.Delete(tempFile); File.Delete(tempFile);
var filePath = Path.Combine(_pluginConfiguration.CacheFolder, file.Hash); 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); var fi = new FileInfo(filePath);
Func<DateTime> RandomDayFunc() Func<DateTime> RandomDayFunc()
{ {
DateTime start = new DateTime(1995, 1, 1); DateTime start = new(1995, 1, 1);
Random gen = new Random(); Random gen = new();
int range = (DateTime.Today - start).Days; int range = (DateTime.Today - start).Days;
return () => start.AddDays(gen.Next(range)); return () => start.AddDays(gen.Next(range));
} }
@@ -155,7 +155,7 @@ namespace MareSynchronos.WebAPI
Logger.Warn(ex.Message); Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace); Logger.Warn(ex.StackTrace);
} }
}); }).ConfigureAwait(false);
Logger.Debug("Download complete, removing " + currentDownloadId); Logger.Debug("Download complete, removing " + currentDownloadId);
CancelDownload(currentDownloadId); CancelDownload(currentDownloadId);
@@ -163,7 +163,7 @@ namespace MareSynchronos.WebAPI
public async Task PushCharacterData(CharacterCacheDto character, List<string> visibleCharacterIds) 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); Logger.Debug("Sending Character data to service " + ApiUri);
CancelUpload(); CancelUpload();
@@ -172,7 +172,7 @@ namespace MareSynchronos.WebAPI
Logger.Verbose("New Token Created"); Logger.Verbose("New Token Created");
List<string> unverifiedUploadHashes = new(); 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)) if (!_verifiedUploadedHashes.Contains(item))
{ {
@@ -183,7 +183,7 @@ namespace MareSynchronos.WebAPI
if (unverifiedUploadHashes.Any()) if (unverifiedUploadHashes.Any())
{ {
Logger.Debug("Verifying " + unverifiedUploadHashes.Count + " files"); 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)) 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)) 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) ForbiddenTransfers.Add(new UploadFileTransfer(file)
{ {
@@ -217,9 +217,9 @@ namespace MareSynchronos.WebAPI
foreach (var file in CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList()) foreach (var file in CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList())
{ {
Logger.Debug("Compressing and uploading " + file); Logger.Debug("Compressing and uploading " + file);
var data = await GetCompressedFileData(file.Hash, uploadToken); var data = await GetCompressedFileData(file.Hash, uploadToken).ConfigureAwait(false);
CurrentUploads.Single(e => e.Hash == data.Item1).Total = data.Item2.Length; CurrentUploads.Single(e => string.Equals(e.Hash, data.Item1, StringComparison.Ordinal)).Total = data.Item2.Length;
await UploadFile(data.Item2, file.Hash, uploadToken); await UploadFile(data.Item2, file.Hash, uploadToken).ConfigureAwait(false);
if (!uploadToken.IsCancellationRequested) continue; if (!uploadToken.IsCancellationRequested) continue;
Logger.Warn("Cancel in filesToUpload loop detected"); Logger.Warn("Cancel in filesToUpload loop detected");
CurrentUploads.Clear(); CurrentUploads.Clear();
@@ -233,12 +233,12 @@ namespace MareSynchronos.WebAPI
} }
Logger.Debug("Upload tasks complete, waiting for server to confirm"); 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); Logger.Debug("Uploads open: " + anyUploadsOpen);
while (anyUploadsOpen && !uploadToken.IsCancellationRequested) while (anyUploadsOpen && !uploadToken.IsCancellationRequested)
{ {
anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken); anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken).ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken); await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken).ConfigureAwait(false);
Logger.Debug("Waiting for uploads to finish"); Logger.Debug("Waiting for uploads to finish");
} }
@@ -257,7 +257,7 @@ namespace MareSynchronos.WebAPI
if (!uploadToken.IsCancellationRequested) if (!uploadToken.IsCancellationRequested)
{ {
Logger.Info("Pushing character data for " + character.GetHashCode() + " to " + string.Join(", ", visibleCharacterIds)); 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) foreach (var item in character.FileReplacements)
{ {
sb.AppendLine($"FileReplacements for {item.Key}: {item.Value.Count}"); 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)}"); sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}");
} }
Logger.Debug("Chara data contained: " + Environment.NewLine + sb.ToString()); 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 else
{ {
@@ -281,7 +281,7 @@ namespace MareSynchronos.WebAPI
private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken) private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
{ {
var fileCache = _fileDbManager.GetFileCacheByHash(fileHash)!.ResolvedFilepath; 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)); (int)new FileInfo(fileCache).Length));
} }
@@ -295,15 +295,15 @@ namespace MareSynchronos.WebAPI
using var ms = new MemoryStream(compressedFile); using var ms = new MemoryStream(compressedFile);
var buffer = new byte[chunkSize]; var buffer = new byte[chunkSize];
int bytesRead; 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(); token.ThrowIfCancellationRequested();
yield return bytesRead == chunkSize ? buffer.ToArray() : buffer.Take(bytesRead).ToArray(); 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) public void CancelDownload(int downloadId)
@@ -313,6 +313,5 @@ namespace MareSynchronos.WebAPI
CurrentDownloads.TryRemove(downloadId, out _); CurrentDownloads.TryRemove(downloadId, out _);
} }
} }
}
} }

View File

@@ -1,44 +1,43 @@
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using MareSynchronos.API; using MareSynchronos.API;
using MareSynchronos.Utils;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI namespace MareSynchronos.WebAPI;
public partial class ApiController
{ {
public partial class ApiController
{
public async Task DeleteAccount() public async Task DeleteAccount()
{ {
_pluginConfiguration.ClientSecret.Remove(ApiUri); _pluginConfiguration.ClientSecret.Remove(ApiUri);
_pluginConfiguration.Save(); _pluginConfiguration.Save();
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles); await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles).ConfigureAwait(false);
await _mareHub!.SendAsync(Api.SendUserDeleteAccount); await _mareHub!.SendAsync(Api.SendUserDeleteAccount).ConfigureAwait(false);
await CreateConnections(); await CreateConnections().ConfigureAwait(false);
} }
public async Task<List<string>> GetOnlineCharacters() 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) public async Task SendPairedClientAddition(string uid)
{ {
if (!IsConnected || SecretKey == "-") return; if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(Api.SendUserPairedClientAddition, uid); await _mareHub!.SendAsync(Api.SendUserPairedClientAddition, uid).ConfigureAwait(false);
} }
public async Task SendPairedClientPauseChange(string uid, bool paused) public async Task SendPairedClientPauseChange(string uid, bool paused)
{ {
if (!IsConnected || SecretKey == "-") return; if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(Api.SendUserPairedClientPauseChange, uid, paused); await _mareHub!.SendAsync(Api.SendUserPairedClientPauseChange, uid, paused).ConfigureAwait(false);
} }
public async Task SendPairedClientRemoval(string uid) public async Task SendPairedClientRemoval(string uid)
{ {
if (!IsConnected || SecretKey == "-") return; if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(Api.SendUserPairedClientRemoval, uid); await _mareHub!.SendAsync(Api.SendUserPairedClientRemoval, uid).ConfigureAwait(false);
} }
}
} }

View File

@@ -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);
}
}
}
}
}

View File

@@ -1,35 +1,36 @@
using System.Collections.Generic; using System;
using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using MareSynchronos.API; using MareSynchronos.API;
using Microsoft.AspNetCore.SignalR.Client; using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI namespace MareSynchronos.WebAPI;
public partial class ApiController
{ {
public partial class ApiController
{
public async Task AddOrUpdateForbiddenFileEntry(ForbiddenFileDto forbiddenFile) public async Task AddOrUpdateForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
{ {
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile); await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile).ConfigureAwait(false);
} }
public async Task DeleteForbiddenFileEntry(ForbiddenFileDto forbiddenFile) 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) 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) public async Task DeleteBannedUserEntry(BannedUserDto bannedUser)
{ {
await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser); await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser).ConfigureAwait(false);
} }
public async Task RefreshOnlineUsers() 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>(); public List<OnlineUserDto> AdminOnlineUsers { get; set; } = new List<OnlineUserDto>();
@@ -43,5 +44,4 @@ namespace MareSynchronos.WebAPI
{ {
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false); _mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false);
} }
}
} }

View File

@@ -1,25 +1,26 @@
using System.Linq; using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using MareSynchronos.API; using MareSynchronos.API;
using MareSynchronos.Utils; using MareSynchronos.Utils;
using MareSynchronos.WebAPI.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() private void UserForcedReconnectCallback()
{ {
_ = CreateConnections(); _ = 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) if (dto.IsRemoved)
{ {
PairedClients.RemoveAll(p => p.OtherUID == dto.OtherUID); PairedClients.RemoveAll(p => string.Equals(p.OtherUID, dto.OtherUID, System.StringComparison.Ordinal));
UnpairedFromOther?.Invoke(characterIdentifier);
return; return;
} }
if (entry == null) if (entry == null)
@@ -28,20 +29,9 @@ namespace MareSynchronos.WebAPI
return; 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.IsPaused = dto.IsPaused;
entry.IsPausedFromOthers = dto.IsPausedFromOthers; entry.IsPausedFromOthers = dto.IsPausedFromOthers;
entry.IsSynced = dto.IsSynced; entry.IsSynced = dto.IsSynced;
if (dto.IsPaused || dto.IsPausedFromOthers || !dto.IsSynced)
{
UnpairedFromOther?.Invoke(characterIdentifier);
}
} }
private Task ReceiveCharacterDataCallback(CharacterCacheDto character, string characterHash) private Task ReceiveCharacterDataCallback(CharacterCacheDto character, string characterHash)
@@ -53,7 +43,7 @@ namespace MareSynchronos.WebAPI
private void UpdateOrAddBannedUserCallback(BannedUserDto obj) 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) if (user == null)
{ {
AdminBannedUsers.Add(obj); AdminBannedUsers.Add(obj);
@@ -66,12 +56,12 @@ namespace MareSynchronos.WebAPI
private void DeleteBannedUserCallback(BannedUserDto obj) 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) 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) if (user == null)
{ {
AdminForbiddenFiles.Add(obj); AdminForbiddenFiles.Add(obj);
@@ -84,7 +74,48 @@ namespace MareSynchronos.WebAPI
private void DeleteForbiddenFileCallback(ForbiddenFileDto obj) 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;
} }
} }

View 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);
}
}

View 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);
}
}
}
}

View File

@@ -0,0 +1,11 @@
namespace MareSynchronos.WebAPI;
public enum ServerState
{
Offline,
Disconnected,
Connected,
Unauthorized,
VersionMisMatch,
RateLimited
}