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

View File

@@ -7,8 +7,8 @@ using System.Linq;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos
{
namespace MareSynchronos;
public static class ConfigurationExtensions
{
public static bool HasValidSetup(this Configuration configuration)
@@ -23,14 +23,31 @@ namespace MareSynchronos
{
return configuration.UidServerComments.ContainsKey(configuration.ApiUri)
? configuration.UidServerComments[configuration.ApiUri]
: new Dictionary<string, string>();
: new Dictionary<string, string>(StringComparer.Ordinal);
}
public static Dictionary<string, string> GetCurrentServerGidComments(this Configuration configuration)
{
return configuration.GidServerComments.ContainsKey(configuration.ApiUri)
? configuration.GidServerComments[configuration.ApiUri]
: new Dictionary<string, string>(StringComparer.Ordinal);
}
public static void SetCurrentServerGidComment(this Configuration configuration, string gid, string comment)
{
if (!configuration.GidServerComments.ContainsKey(configuration.ApiUri))
{
configuration.GidServerComments[configuration.ApiUri] = new Dictionary<string, string>(StringComparer.Ordinal);
}
configuration.GidServerComments[configuration.ApiUri][gid] = comment;
}
public static void SetCurrentServerUidComment(this Configuration configuration, string uid, string comment)
{
if (!configuration.UidServerComments.ContainsKey(configuration.ApiUri))
{
configuration.UidServerComments[configuration.ApiUri] = new Dictionary<string, string>();
configuration.UidServerComments[configuration.ApiUri] = new Dictionary<string, string>(StringComparer.Ordinal);
}
configuration.UidServerComments[configuration.ApiUri][uid] = comment;
@@ -54,10 +71,10 @@ namespace MareSynchronos
}
public string CacheFolder { get; set; } = string.Empty;
public Dictionary<string, string> ClientSecret { get; set; } = new();
public Dictionary<string, string> CustomServerList { get; set; } = new();
public Dictionary<string, string> ClientSecret { get; set; } = new(StringComparer.Ordinal);
public Dictionary<string, string> CustomServerList { get; set; } = new(StringComparer.Ordinal);
public int MaxLocalCacheInGiB { get; set; } = 20;
public bool ReverseUserSort { get; set; } = true;
public bool ReverseUserSort { get; set; } = false;
public int TimeSpanBetweenScansInSeconds { get; set; } = 30;
public bool FileScanPaused { get; set; } = false;
@@ -65,9 +82,10 @@ namespace MareSynchronos
public bool InitialScanComplete { get; set; } = false;
public bool FullPause { get; set; } = false;
public Dictionary<string, Dictionary<string, string>> UidServerComments { get; set; } = new();
public Dictionary<string, Dictionary<string, string>> UidServerComments { get; set; } = new(StringComparer.Ordinal);
public Dictionary<string, Dictionary<string, string>> GidServerComments { get; set; } = new(StringComparer.Ordinal);
public Dictionary<string, string> UidComments { get; set; } = new();
public Dictionary<string, string> UidComments { get; set; } = new(StringComparer.Ordinal);
public int Version { get; set; } = 5;
public bool ShowTransferWindow { get; set; } = true;
@@ -96,10 +114,10 @@ namespace MareSynchronos
{
Logger.Debug("Migrating Configuration from V0 to V1");
Version = 1;
ApiUri = ApiUri.Replace("https", "wss");
ApiUri = ApiUri.Replace("https", "wss", StringComparison.Ordinal);
foreach (var kvp in ClientSecret.ToList())
{
var newKey = kvp.Key.Replace("https", "wss");
var newKey = kvp.Key.Replace("https", "wss", StringComparison.Ordinal);
ClientSecret.Remove(kvp.Key);
if (ClientSecret.ContainsKey(newKey))
{
@@ -110,7 +128,7 @@ namespace MareSynchronos
ClientSecret.Add(newKey, kvp.Value);
}
}
UidServerComments.Add(ApiUri, UidComments.ToDictionary(k => k.Key, k => k.Value));
UidServerComments.Add(ApiUri, UidComments.ToDictionary(k => k.Key, k => k.Value, StringComparer.Ordinal));
UidComments.Clear();
Save();
}
@@ -118,10 +136,10 @@ namespace MareSynchronos
if (Version == 1)
{
Logger.Debug("Migrating Configuration from V1 to V2");
ApiUri = ApiUri.Replace("5001", "5000");
ApiUri = ApiUri.Replace("5001", "5000", StringComparison.Ordinal);
foreach (var kvp in ClientSecret.ToList())
{
var newKey = kvp.Key.Replace("5001", "5000");
var newKey = kvp.Key.Replace("5001", "5000", StringComparison.Ordinal);
ClientSecret.Remove(kvp.Key);
if (ClientSecret.ContainsKey(newKey))
{
@@ -135,7 +153,7 @@ namespace MareSynchronos
foreach (var kvp in UidServerComments.ToList())
{
var newKey = kvp.Key.Replace("5001", "5000");
var newKey = kvp.Key.Replace("5001", "5000", StringComparison.Ordinal);
UidServerComments.Remove(kvp.Key);
UidServerComments.Add(newKey, kvp.Value);
}
@@ -159,10 +177,10 @@ namespace MareSynchronos
{
Logger.Debug("Migrating Configuration from V3 to V4");
ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872");
ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872", StringComparison.Ordinal);
foreach (var kvp in ClientSecret.ToList())
{
var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872");
var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872", StringComparison.Ordinal);
ClientSecret.Remove(kvp.Key);
if (ClientSecret.ContainsKey(newKey))
{
@@ -176,7 +194,7 @@ namespace MareSynchronos
foreach (var kvp in UidServerComments.ToList())
{
var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872");
var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872", StringComparison.Ordinal);
UidServerComments.Remove(kvp.Key);
UidServerComments.Add(newKey, kvp.Value);
}
@@ -189,7 +207,7 @@ namespace MareSynchronos
{
Logger.Debug("Migrating Configuration from V4 to V5");
ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6872", "wss://maresynchronos.com");
ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6872", "wss://maresynchronos.com", StringComparison.Ordinal);
ClientSecret.Remove("wss://v2202207178628194299.powersrv.de:6872");
UidServerComments.Remove("wss://v2202207178628194299.powersrv.de:6872");
@@ -198,4 +216,3 @@ namespace MareSynchronos
}
}
}
}

View File

@@ -158,7 +158,7 @@ public class CharacterDataFactory
if (cache.FileReplacements.ContainsKey(objectKind))
{
if (cache.FileReplacements[objectKind].Any(c => c.ResolvedPath.Contains(mtrlPath)))
if (cache.FileReplacements[objectKind].Any(c => c.ResolvedPath.Contains(mtrlPath, StringComparison.Ordinal)))
{
return;
}
@@ -196,7 +196,7 @@ public class CharacterDataFactory
if (cache.FileReplacements.ContainsKey(objectKind))
{
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(varPath)))
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(varPath, StringComparer.Ordinal)))
{
return;
}
@@ -214,7 +214,7 @@ public class CharacterDataFactory
if (cache.FileReplacements.ContainsKey(objectKind))
{
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(texPath)))
if (cache.FileReplacements[objectKind].Any(c => c.GamePaths.Contains(texPath, StringComparer.Ordinal)))
{
return;
}
@@ -225,7 +225,7 @@ public class CharacterDataFactory
cache.AddFileReplacement(objectKind, texFileReplacement);
if (texPath.Contains("/--")) return;
if (texPath.Contains("/--", StringComparison.Ordinal)) return;
var texDx11Replacement =
CreateFileReplacement(texPath.Insert(texPath.LastIndexOf('/') + 1, "--"), doNotReverseResolve);
@@ -322,13 +322,13 @@ public class CharacterDataFactory
previousData.FileReplacements.Add(objectKind, new());
}
if (!previousData.FileReplacements[objectKind].Any(k => k.GamePaths.Any(p => item.GamePaths.Select(p => p.ToLowerInvariant()).Contains(p.ToLowerInvariant()))))
if (!previousData.FileReplacements[objectKind].Any(k => k.GamePaths.Any(p => item.GamePaths.Contains(p, StringComparer.OrdinalIgnoreCase))))
{
var penumResolve = _ipcManager.PenumbraResolvePath(item.GamePaths.First()).ToLowerInvariant();
var gamePath = item.GamePaths.First().ToLowerInvariant();
if (penumResolve == gamePath)
if (string.Equals(penumResolve, gamePath, StringComparison.Ordinal))
{
Logger.Debug("PenumResolve was same as GamePath, not adding " + item);
Logger.Verbose("PenumResolve was same as GamePath, not adding " + item);
transientResourceManager.RemoveTransientResource(charaPointer, item);
}
else

View File

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

View File

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

View File

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

View File

@@ -4,8 +4,8 @@ using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using Penumbra.Interop.Structs;
namespace MareSynchronos.Interop
{
namespace MareSynchronos.Interop;
[StructLayout(LayoutKind.Explicit)]
public unsafe struct Weapon
{
@@ -35,4 +35,3 @@ namespace MareSynchronos.Interop
[FieldOffset(0x0)] public Character Character;
[FieldOffset(0x650)] public Character* Mount;
}
}

View File

@@ -1,7 +1,7 @@
using CheapLoc;
namespace MareSynchronos.Localization
{
namespace MareSynchronos.Localization;
public static class Strings
{
public class ToSStrings
@@ -65,4 +65,3 @@ namespace MareSynchronos.Localization
public static ToSStrings ToS { get; set; } = new();
}
}

View File

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

View File

@@ -8,8 +8,8 @@ using MareSynchronos.WebAPI;
using Action = System.Action;
using System.Collections.Concurrent;
namespace MareSynchronos.Managers
{
namespace MareSynchronos.Managers;
public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx);
public delegate void HeelsOffsetChange(float change);
public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath);
@@ -158,7 +158,7 @@ namespace MareSynchronos.Managers
{
try
{
return _heelsGetApiVersion.InvokeFunc() == "1.0.1";
return string.Equals(_heelsGetApiVersion.InvokeFunc(), "1.0.1", StringComparison.Ordinal);
}
catch
{
@@ -398,4 +398,3 @@ namespace MareSynchronos.Managers
actionQueue.Clear();
}
}
}

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
<PropertyGroup>
<Authors></Authors>
<Company></Company>
<Version>0.4.24</Version>
<Version>0.5.0</Version>
<Description></Description>
<Copyright></Copyright>
<PackageProjectUrl>https://github.com/Penumbra-Sync/client</PackageProjectUrl>
@@ -28,6 +28,10 @@
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.8" />
<PackageReference Include="lz4net" Version="1.0.15.93" />
<PackageReference Include="Meziantou.Analyzer" Version="1.0.733">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="6.0.8" />
</ItemGroup>
@@ -86,4 +90,8 @@
<EmbeddedResource Include="Localization\fr.json" />
</ItemGroup>
<ItemGroup>
<None Include="..\.editorconfig" Link=".editorconfig" />
</ItemGroup>
</Project>

View File

@@ -6,8 +6,8 @@ using MareSynchronos.API;
using MareSynchronos.Utils;
using Lumina.Excel.GeneratedSheets;
namespace MareSynchronos.Models
{
namespace MareSynchronos.Models;
[JsonObject(MemberSerialization.OptIn)]
public class CharacterData
{
@@ -31,10 +31,10 @@ namespace MareSynchronos.Models
if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new List<FileReplacement>());
var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => f.ResolvedPath == fileReplacement.ResolvedPath);
var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => string.Equals(f.ResolvedPath, fileReplacement.ResolvedPath, System.StringComparison.OrdinalIgnoreCase));
if (existingReplacement != null)
{
existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e)));
existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e, System.StringComparer.OrdinalIgnoreCase)));
}
else
{
@@ -44,11 +44,11 @@ namespace MareSynchronos.Models
public CharacterCacheDto ToCharacterCacheDto()
{
var fileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement && !f.IsFileSwap).GroupBy(f => f.Hash).Select(g =>
var fileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement && !f.IsFileSwap).GroupBy(f => f.Hash, System.StringComparer.OrdinalIgnoreCase).Select(g =>
{
return new FileReplacementDto()
{
GamePaths = g.SelectMany(f => f.GamePaths).Distinct().ToArray(),
GamePaths = g.SelectMany(f => f.GamePaths).Distinct(System.StringComparer.OrdinalIgnoreCase).ToArray(),
Hash = g.First().Hash,
};
}).ToList());
@@ -85,4 +85,3 @@ namespace MareSynchronos.Models
return stringBuilder.ToString();
}
}
}

View File

@@ -6,8 +6,8 @@ using MareSynchronos.API;
using System.Text.RegularExpressions;
using MareSynchronos.FileCache;
namespace MareSynchronos.Models
{
namespace MareSynchronos.Models;
public class FileReplacement
{
private readonly FileCacheManager fileDbManager;
@@ -21,9 +21,9 @@ namespace MareSynchronos.Models
public List<string> GamePaths { get; set; } = new();
public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => p != ResolvedPath);
public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => !string.Equals(p, ResolvedPath, System.StringComparison.Ordinal));
public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && GamePaths.First() != ResolvedPath;
public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && !string.Equals(GamePaths.First(), ResolvedPath, System.StringComparison.Ordinal);
public string Hash { get; set; } = string.Empty;
@@ -57,4 +57,3 @@ namespace MareSynchronos.Models
return builder.ToString();
}
}
}

View File

@@ -5,8 +5,8 @@ using System.Runtime.InteropServices;
using MareSynchronos.Utils;
using Penumbra.GameData.ByteString;
namespace MareSynchronos.Models
{
namespace MareSynchronos.Models;
public class PlayerRelatedObject
{
private readonly Func<IntPtr> getAddress;
@@ -61,7 +61,7 @@ namespace MareSynchronos.Models
bool equip = CompareAndUpdateByteData(chara->EquipSlotData, chara->CustomizeData);
bool drawObj = (IntPtr)chara->GameObject.DrawObject != DrawObjectAddress;
var name = new Utf8String(chara->GameObject.Name).ToString();
bool nameChange = (name != _name);
bool nameChange = (!string.Equals(name, _name, StringComparison.Ordinal));
if (addr || equip || drawObj || nameChange)
{
_name = name;
@@ -133,4 +133,3 @@ namespace MareSynchronos.Models
return hasChanges;
}
}
}

View File

@@ -14,9 +14,10 @@ using MareSynchronos.UI;
using MareSynchronos.Utils;
using Dalamud.Game.ClientState.Conditions;
using MareSynchronos.FileCache;
using Dalamud.Logging;
namespace MareSynchronos;
namespace MareSynchronos
{
public sealed class Plugin : IDalamudPlugin
{
private const string CommandName = "/mare";
@@ -174,7 +175,7 @@ namespace MareSynchronos
{
while (!_dalamudUtil.IsPlayerPresent)
{
await Task.Delay(100);
await Task.Delay(100).ConfigureAwait(false);
}
try
@@ -236,4 +237,3 @@ namespace MareSynchronos
_introUi.Toggle();
}
}
}

View File

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

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,8 +12,8 @@ using MareSynchronos.Localization;
using Dalamud.Utility;
using MareSynchronos.FileCache;
namespace MareSynchronos.UI
{
namespace MareSynchronos.UI;
internal class IntroUi : Window, IDisposable
{
private readonly UiShared _uiShared;
@@ -37,12 +37,12 @@ namespace MareSynchronos.UI
private Task _timeoutTask;
private string _timeoutTime;
private Dictionary<string, string> _languages = new() { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } };
private Dictionary<string, string> _languages = new(StringComparer.Ordinal) { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } };
private int _currentLanguage;
private bool DarkSoulsCaptchaValid => _darkSoulsCaptcha1.Item2 == _enteredDarkSoulsCaptcha1.Trim()
&& _darkSoulsCaptcha2.Item2 == _enteredDarkSoulsCaptcha2.Trim()
&& _darkSoulsCaptcha3.Item2 == _enteredDarkSoulsCaptcha3.Trim();
private bool DarkSoulsCaptchaValid => string.Equals(_darkSoulsCaptcha1.Item2, _enteredDarkSoulsCaptcha1.Trim()
, StringComparison.Ordinal) && string.Equals(_darkSoulsCaptcha2.Item2, _enteredDarkSoulsCaptcha2.Trim()
, StringComparison.Ordinal) && string.Equals(_darkSoulsCaptcha3.Item2, _enteredDarkSoulsCaptcha3.Trim(), StringComparison.Ordinal);
public void Dispose()
@@ -158,7 +158,7 @@ namespace MareSynchronos.UI
{
_timeoutTime = $"{i}s " + Strings.ToS.RemainingLabel;
Logger.Debug(_timeoutTime);
await Task.Delay(TimeSpan.FromSeconds(1));
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
}
});
}
@@ -286,13 +286,12 @@ namespace MareSynchronos.UI
private Tuple<string, string> GetCaptchaTuple()
{
Random random = new Random();
Random random = new();
var paragraphIdx = random.Next(TosParagraphs.Length);
var splitParagraph = TosParagraphs[paragraphIdx].Split(".", StringSplitOptions.RemoveEmptyEntries).Select(c => c.Trim()).ToArray();
var sentenceIdx = random.Next(splitParagraph.Length);
var splitSentence = splitParagraph[sentenceIdx].Split(" ").Select(c => c.Trim()).Select(c => c.Replace(".", "").Replace(",", "").Replace("'", "")).ToArray();
var splitSentence = splitParagraph[sentenceIdx].Split(" ").Select(c => c.Trim()).Select(c => c.Replace(".", "", StringComparison.Ordinal).Replace(",", "", StringComparison.Ordinal).Replace("'", "", StringComparison.Ordinal)).ToArray();
var wordIdx = random.Next(splitSentence.Length);
return new($"{Strings.ToS.ParagraphLabel} {paragraphIdx + 1}, {Strings.ToS.SentenceLabel} {sentenceIdx + 1}, {Strings.ToS.WordLabel} {wordIdx + 1}", splitSentence[wordIdx]);
}
}
}

View File

@@ -14,8 +14,8 @@ using MareSynchronos.WebAPI.Utils;
using System.Diagnostics;
using Dalamud.Utility;
namespace MareSynchronos.UI
{
namespace MareSynchronos.UI;
public delegate void SwitchUi();
public class SettingsUi : Window, IDisposable
{
@@ -335,7 +335,7 @@ namespace MareSynchronos.UI
});
}
ImGui.SameLine();
if (onlineUser.UID != _apiController.UID && _apiController.IsAdmin)
if (!string.Equals(onlineUser.UID, _apiController.UID, StringComparison.Ordinal) && _apiController.IsAdmin)
{
if (!onlineUser.IsModerator)
{
@@ -610,4 +610,3 @@ namespace MareSynchronos.UI
base.OnClose();
}
}
}

View File

@@ -18,8 +18,8 @@ using MareSynchronos.Managers;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.UI
{
namespace MareSynchronos.UI;
public class UiShared : IDisposable
{
[DllImport("user32")]
@@ -39,8 +39,8 @@ namespace MareSynchronos.UI
public bool EditTrackerPosition { get; set; }
public ImFontPtr UidFont { get; private set; }
public bool UidFontBuilt { get; private set; }
public static bool CtrlPressed() => (GetKeyState(0xA2) & 0x8000) != 0 || (GetKeyState(0xA3) & 0x8000) != 0;
public static bool ShiftPressed() => (GetKeyState(0xA1) & 0x8000) != 0 || (GetKeyState(0xA0) & 0x8000) != 0;
public ApiController ApiController => _apiController;
@@ -88,8 +88,8 @@ namespace MareSynchronos.UI
}
catch (Exception ex)
{
Logger.Debug($"Font failed to load. {fontFile}");
Logger.Debug(ex.ToString());
Logger.Warn($"Font failed to load. {fontFile}");
Logger.Warn(ex.ToString());
}
}
else
@@ -294,7 +294,7 @@ namespace MareSynchronos.UI
bool isSelected = _serverSelectionIndex == i;
if (ImGui.Selectable(comboEntries[i], isSelected))
{
_pluginConfiguration.ApiUri = _apiController.ServerDictionary.Single(k => k.Value == comboEntries[i]).Key;
_pluginConfiguration.ApiUri = _apiController.ServerDictionary.Single(k => string.Equals(k.Value, comboEntries[i], StringComparison.Ordinal)).Key;
_pluginConfiguration.Save();
_ = _apiController.CreateConnections();
}
@@ -451,7 +451,7 @@ namespace MareSynchronos.UI
{
if (!success) return;
_isPenumbraDirectory = path.ToLowerInvariant() == _ipcManager.PenumbraModDirectory()?.ToLowerInvariant();
_isPenumbraDirectory = string.Equals(path.ToLowerInvariant(), _ipcManager.PenumbraModDirectory()?.ToLowerInvariant(), StringComparison.Ordinal);
_isDirectoryWritable = IsDirectoryWritable(path);
_cacheDirectoryHasOtherFilesThanCache = Directory.GetFiles(path, "*", SearchOption.AllDirectories).Any(f => new FileInfo(f).Name.Length != 40);
_cacheDirectoryIsValidPath = Regex.IsMatch(path, @"^(?:[a-zA-Z]:\\[\w\s\-\\]+?|\/(?:[\w\s\-\/])+?)$", RegexOptions.ECMAScript);
@@ -548,9 +548,49 @@ namespace MareSynchronos.UI
DrawHelpText("This allows you to stop the periodic scans of your Penumbra and Mare cache directories. Use this to move the Mare cache and Penumbra mod folders around. If you enable this permanently, run a Force rescan after adding mods to Penumbra.");
}
public static Vector2 GetIconSize(FontAwesomeIcon icon)
{
ImGui.PushFont(UiBuilder.IconFont);
var iconSize = ImGui.CalcTextSize(icon.ToIconString());
ImGui.PopFont();
return iconSize;
}
public static bool IconTextButton(FontAwesomeIcon icon, string text)
{
var buttonClicked = false;
var iconSize = GetIconSize(icon);
var textSize = ImGui.CalcTextSize(text);
var padding = ImGui.GetStyle().FramePadding;
var spacing = ImGui.GetStyle().ItemSpacing;
var buttonSizeX = iconSize.X + textSize.X + padding.X * 2 + spacing.X;
var buttonSizeY = (iconSize.Y > textSize.Y ? iconSize.Y : textSize.Y) + padding.Y * 2;
var buttonSize = new Vector2(buttonSizeX, buttonSizeY);
if (ImGui.BeginChild(icon.ToIconString() + text, buttonSize))
{
if (ImGui.Button("", buttonSize))
{
buttonClicked = true;
}
ImGui.SameLine();
ImGui.SetCursorPosX(padding.X);
ImGui.PushFont(UiBuilder.IconFont);
ImGui.Text(icon.ToIconString());
ImGui.PopFont();
ImGui.SameLine();
ImGui.Text(text);
ImGui.EndChild();
}
return buttonClicked;
}
public void Dispose()
{
_pluginInterface.UiBuilder.BuildFonts -= BuildFont;
}
}
}

View File

@@ -4,32 +4,31 @@ using System.Security.Cryptography;
using System.Text;
using Dalamud.Game.ClientState.Objects.SubKinds;
namespace MareSynchronos.Utils
{
namespace MareSynchronos.Utils;
public class Crypto
{
public static string GetFileHash(string filePath)
{
using SHA1CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", "");
return BitConverter.ToString(cryptoProvider.ComputeHash(File.ReadAllBytes(filePath))).Replace("-", "", StringComparison.Ordinal);
}
public static string GetHash(string stringToHash)
{
using SHA1CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "");
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "", StringComparison.Ordinal);
}
public static string GetHash256(string stringToHash)
{
using SHA256CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "");
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(stringToHash))).Replace("-", "", StringComparison.Ordinal);
}
public static string GetHash256(PlayerCharacter character)
{
using SHA256CryptoServiceProvider cryptoProvider = new();
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", "");
}
return BitConverter.ToString(cryptoProvider.ComputeHash(Encoding.UTF8.GetBytes(character.Name + character.HomeWorld.Id.ToString()))).Replace("-", "", StringComparison.Ordinal);
}
}

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,8 +11,8 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
namespace MareSynchronos.Utils
{
namespace MareSynchronos.Utils;
public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor);
public delegate void LogIn();
@@ -186,7 +186,7 @@ namespace MareSynchronos.Utils
{
return _objectTable.Where(obj =>
obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player &&
obj.Name.ToString() != PlayerName).Select(p => (PlayerCharacter)p).ToList();
!string.Equals(obj.Name.ToString(), PlayerName, StringComparison.Ordinal)).Select(p => (PlayerCharacter)p).ToList();
}
public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index)
@@ -201,7 +201,7 @@ namespace MareSynchronos.Utils
foreach (var item in _objectTable)
{
if (item.ObjectKind != Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player) continue;
if (item.Name.ToString() == characterName) return (PlayerCharacter)item;
if (string.Equals(item.Name.ToString(), characterName, StringComparison.Ordinal)) return (PlayerCharacter)item;
}
return null;
@@ -209,7 +209,7 @@ namespace MareSynchronos.Utils
public async Task<T> RunOnFrameworkThread<T>(Func<T> func)
{
return await _framework.RunOnFrameworkThread(func);
return await _framework.RunOnFrameworkThread(func).ConfigureAwait(false);
}
public unsafe void WaitWhileCharacterIsDrawing(string name, IntPtr characterAddress, int timeOut = 5000, CancellationToken? ct = null)
@@ -239,4 +239,3 @@ namespace MareSynchronos.Utils
_framework.Update -= FrameworkOnUpdate;
}
}
}

View File

@@ -1,32 +1,10 @@
using System;
using System.Collections.Concurrent;
using System.Diagnostics;
using Dalamud.Logging;
using Dalamud.Utility;
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();
}
}
namespace MareSynchronos.Utils;
internal class Logger : ILogger
{
@@ -41,7 +19,7 @@ namespace MareSynchronos.Utils
public static void Debug(string debug, string stringToHighlight = "")
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
if (debug.Contains(stringToHighlight) && !stringToHighlight.IsNullOrEmpty())
if (debug.Contains(stringToHighlight, StringComparison.Ordinal) && !stringToHighlight.IsNullOrEmpty())
{
PluginLog.Warning($"[{caller}] {debug}");
}
@@ -109,4 +87,3 @@ namespace MareSynchronos.Utils
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,8 +13,8 @@ using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI
{
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
private readonly HashSet<string> _verifiedUploadedHashes;
@@ -33,7 +33,7 @@ namespace MareSynchronos.WebAPI
public async Task DeleteAllMyFiles()
{
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles);
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles).ConfigureAwait(false);
}
private async Task<string> DownloadFile(int downloadId, string hash, Uri downloadUri, CancellationToken ct)
@@ -44,7 +44,7 @@ namespace MareSynchronos.WebAPI
{
try
{
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = e.BytesReceived;
CurrentDownloads[downloadId].Single(f => string.Equals(f.Hash, hash, StringComparison.Ordinal)).Transferred = e.BytesReceived;
}
catch (Exception ex)
{
@@ -61,11 +61,11 @@ namespace MareSynchronos.WebAPI
try
{
await wc.DownloadFileTaskAsync(downloadUri, fileName);
await wc.DownloadFileTaskAsync(downloadUri, fileName).ConfigureAwait(false);
}
catch { }
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = CurrentDownloads[downloadId].Single(f => f.Hash == hash).Total;
CurrentDownloads[downloadId].Single(f => string.Equals(f.Hash, hash, StringComparison.Ordinal)).Transferred = CurrentDownloads[downloadId].Single(f => string.Equals(f.Hash, hash, StringComparison.Ordinal)).Total;
wc.DownloadProgressChanged -= progChanged;
return fileName;
@@ -78,7 +78,7 @@ namespace MareSynchronos.WebAPI
DownloadStarted?.Invoke();
try
{
await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct);
await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct).ConfigureAwait(false);
}
catch
{
@@ -94,8 +94,8 @@ namespace MareSynchronos.WebAPI
{
Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")");
List<DownloadFileDto> downloadFileInfoFromService = new List<DownloadFileDto>();
downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync<List<DownloadFileDto>>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct));
List<DownloadFileDto> downloadFileInfoFromService = new();
downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync<List<DownloadFileDto>>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct).ConfigureAwait(false));
Logger.Debug("Files with size 0 or less: " + string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
@@ -104,7 +104,7 @@ namespace MareSynchronos.WebAPI
foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden))
{
if (ForbiddenTransfers.All(f => f.Hash != dto.Hash))
if (ForbiddenTransfers.All(f => !string.Equals(f.Hash, dto.Hash, StringComparison.Ordinal)))
{
ForbiddenTransfers.Add(new DownloadFileTransfer(dto));
}
@@ -118,7 +118,7 @@ namespace MareSynchronos.WebAPI
async (file, token) =>
{
var hash = file.Hash;
var tempFile = await DownloadFile(currentDownloadId, file.Hash, file.DownloadUri, token);
var tempFile = await DownloadFile(currentDownloadId, file.Hash, file.DownloadUri, token).ConfigureAwait(false);
if (token.IsCancellationRequested)
{
File.Delete(tempFile);
@@ -128,16 +128,16 @@ namespace MareSynchronos.WebAPI
return;
}
var tempFileData = await File.ReadAllBytesAsync(tempFile, token);
var tempFileData = await File.ReadAllBytesAsync(tempFile, token).ConfigureAwait(false);
var extractedFile = LZ4Codec.Unwrap(tempFileData);
File.Delete(tempFile);
var filePath = Path.Combine(_pluginConfiguration.CacheFolder, file.Hash);
await File.WriteAllBytesAsync(filePath, extractedFile, token);
await File.WriteAllBytesAsync(filePath, extractedFile, token).ConfigureAwait(false);
var fi = new FileInfo(filePath);
Func<DateTime> RandomDayFunc()
{
DateTime start = new DateTime(1995, 1, 1);
Random gen = new Random();
DateTime start = new(1995, 1, 1);
Random gen = new();
int range = (DateTime.Today - start).Days;
return () => start.AddDays(gen.Next(range));
}
@@ -155,7 +155,7 @@ namespace MareSynchronos.WebAPI
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace);
}
});
}).ConfigureAwait(false);
Logger.Debug("Download complete, removing " + currentDownloadId);
CancelDownload(currentDownloadId);
@@ -163,7 +163,7 @@ namespace MareSynchronos.WebAPI
public async Task PushCharacterData(CharacterCacheDto character, List<string> visibleCharacterIds)
{
if (!IsConnected || SecretKey == "-") return;
if (!IsConnected || string.Equals(SecretKey, "-", StringComparison.Ordinal)) return;
Logger.Debug("Sending Character data to service " + ApiUri);
CancelUpload();
@@ -172,7 +172,7 @@ namespace MareSynchronos.WebAPI
Logger.Verbose("New Token Created");
List<string> unverifiedUploadHashes = new();
foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct()).Distinct().ToList())
foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct(StringComparer.Ordinal)).Distinct(StringComparer.Ordinal).ToList())
{
if (!_verifiedUploadedHashes.Contains(item))
{
@@ -183,7 +183,7 @@ namespace MareSynchronos.WebAPI
if (unverifiedUploadHashes.Any())
{
Logger.Debug("Verifying " + unverifiedUploadHashes.Count + " files");
var filesToUpload = await _mareHub!.InvokeAsync<List<UploadFileDto>>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken);
var filesToUpload = await _mareHub!.InvokeAsync<List<UploadFileDto>>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken).ConfigureAwait(false);
foreach (var file in filesToUpload.Where(f => !f.IsForbidden))
{
@@ -203,7 +203,7 @@ namespace MareSynchronos.WebAPI
foreach (var file in filesToUpload.Where(c => c.IsForbidden))
{
if (ForbiddenTransfers.All(f => f.Hash != file.Hash))
if (ForbiddenTransfers.All(f => !string.Equals(f.Hash, file.Hash, StringComparison.Ordinal)))
{
ForbiddenTransfers.Add(new UploadFileTransfer(file)
{
@@ -217,9 +217,9 @@ namespace MareSynchronos.WebAPI
foreach (var file in CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList())
{
Logger.Debug("Compressing and uploading " + file);
var data = await GetCompressedFileData(file.Hash, uploadToken);
CurrentUploads.Single(e => e.Hash == data.Item1).Total = data.Item2.Length;
await UploadFile(data.Item2, file.Hash, uploadToken);
var data = await GetCompressedFileData(file.Hash, uploadToken).ConfigureAwait(false);
CurrentUploads.Single(e => string.Equals(e.Hash, data.Item1, StringComparison.Ordinal)).Total = data.Item2.Length;
await UploadFile(data.Item2, file.Hash, uploadToken).ConfigureAwait(false);
if (!uploadToken.IsCancellationRequested) continue;
Logger.Warn("Cancel in filesToUpload loop detected");
CurrentUploads.Clear();
@@ -233,12 +233,12 @@ namespace MareSynchronos.WebAPI
}
Logger.Debug("Upload tasks complete, waiting for server to confirm");
var anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
var anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken).ConfigureAwait(false);
Logger.Debug("Uploads open: " + anyUploadsOpen);
while (anyUploadsOpen && !uploadToken.IsCancellationRequested)
{
anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken);
anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken).ConfigureAwait(false);
await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken).ConfigureAwait(false);
Logger.Debug("Waiting for uploads to finish");
}
@@ -257,7 +257,7 @@ namespace MareSynchronos.WebAPI
if (!uploadToken.IsCancellationRequested)
{
Logger.Info("Pushing character data for " + character.GetHashCode() + " to " + string.Join(", ", visibleCharacterIds));
StringBuilder sb = new StringBuilder();
StringBuilder sb = new();
foreach (var item in character.FileReplacements)
{
sb.AppendLine($"FileReplacements for {item.Key}: {item.Value.Count}");
@@ -267,7 +267,7 @@ namespace MareSynchronos.WebAPI
sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}");
}
Logger.Debug("Chara data contained: " + Environment.NewLine + sb.ToString());
await _mareHub!.InvokeAsync(Api.InvokeUserPushCharacterDataToVisibleClients, character, visibleCharacterIds, uploadToken);
await _mareHub!.InvokeAsync(Api.InvokeUserPushCharacterDataToVisibleClients, character, visibleCharacterIds, uploadToken).ConfigureAwait(false);
}
else
{
@@ -281,7 +281,7 @@ namespace MareSynchronos.WebAPI
private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
{
var fileCache = _fileDbManager.GetFileCacheByHash(fileHash)!.ResolvedFilepath;
return (fileHash, LZ4Codec.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken), 0,
return (fileHash, LZ4Codec.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken).ConfigureAwait(false), 0,
(int)new FileInfo(fileCache).Length));
}
@@ -295,15 +295,15 @@ namespace MareSynchronos.WebAPI
using var ms = new MemoryStream(compressedFile);
var buffer = new byte[chunkSize];
int bytesRead;
while ((bytesRead = await ms.ReadAsync(buffer, 0, chunkSize, token)) > 0 && !token.IsCancellationRequested)
while ((bytesRead = await ms.ReadAsync(buffer, 0, chunkSize, token).ConfigureAwait(false)) > 0 && !token.IsCancellationRequested)
{
CurrentUploads.Single(f => f.Hash == fileHash).Transferred += bytesRead;
CurrentUploads.Single(f => string.Equals(f.Hash, fileHash, StringComparison.Ordinal)).Transferred += bytesRead;
token.ThrowIfCancellationRequested();
yield return bytesRead == chunkSize ? buffer.ToArray() : buffer.Take(bytesRead).ToArray();
}
}
await _mareHub!.SendAsync(Api.SendFileUploadFileStreamAsync, fileHash, AsyncFileData(uploadToken), uploadToken);
await _mareHub!.SendAsync(Api.SendFileUploadFileStreamAsync, fileHash, AsyncFileData(uploadToken), uploadToken).ConfigureAwait(false);
}
public void CancelDownload(int downloadId)
@@ -315,4 +315,3 @@ namespace MareSynchronos.WebAPI
}
}
}

View File

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

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 MareSynchronos.API;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI
{
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public async Task AddOrUpdateForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
{
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile);
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile).ConfigureAwait(false);
}
public async Task DeleteForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
{
await _mareHub!.SendAsync(Api.SendAdminDeleteForbiddenFile, forbiddenFile);
await _mareHub!.SendAsync(Api.SendAdminDeleteForbiddenFile, forbiddenFile).ConfigureAwait(false);
}
public async Task AddOrUpdateBannedUserEntry(BannedUserDto bannedUser)
{
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddBannedUser, bannedUser);
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddBannedUser, bannedUser).ConfigureAwait(false);
}
public async Task DeleteBannedUserEntry(BannedUserDto bannedUser)
{
await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser);
await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser).ConfigureAwait(false);
}
public async Task RefreshOnlineUsers()
{
AdminOnlineUsers = await _mareHub!.InvokeAsync<List<OnlineUserDto>>(Api.InvokeAdminGetOnlineUsers);
AdminOnlineUsers = await _mareHub!.InvokeAsync<List<OnlineUserDto>>(Api.InvokeAdminGetOnlineUsers).ConfigureAwait(false);
}
public List<OnlineUserDto> AdminOnlineUsers { get; set; } = new List<OnlineUserDto>();
@@ -44,4 +45,3 @@ namespace MareSynchronos.WebAPI
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false);
}
}
}

View File

@@ -1,11 +1,13 @@
using System.Linq;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI;
namespace MareSynchronos.WebAPI
{
public partial class ApiController
{
private void UserForcedReconnectCallback()
@@ -13,13 +15,12 @@ namespace MareSynchronos.WebAPI
_ = CreateConnections();
}
private void UpdateLocalClientPairsCallback(ClientPairDto dto, string characterIdentifier)
private void UpdateLocalClientPairsCallback(ClientPairDto dto)
{
var entry = PairedClients.SingleOrDefault(e => e.OtherUID == dto.OtherUID);
var entry = PairedClients.SingleOrDefault(e => string.Equals(e.OtherUID, dto.OtherUID, System.StringComparison.Ordinal));
if (dto.IsRemoved)
{
PairedClients.RemoveAll(p => p.OtherUID == dto.OtherUID);
UnpairedFromOther?.Invoke(characterIdentifier);
PairedClients.RemoveAll(p => string.Equals(p.OtherUID, dto.OtherUID, System.StringComparison.Ordinal));
return;
}
if (entry == null)
@@ -28,20 +29,9 @@ namespace MareSynchronos.WebAPI
return;
}
if ((entry.IsPausedFromOthers != dto.IsPausedFromOthers || entry.IsSynced != dto.IsSynced || entry.IsPaused != dto.IsPaused)
&& !dto.IsPaused && dto.IsSynced && !dto.IsPausedFromOthers)
{
PairedWithOther?.Invoke(characterIdentifier);
}
entry.IsPaused = dto.IsPaused;
entry.IsPausedFromOthers = dto.IsPausedFromOthers;
entry.IsSynced = dto.IsSynced;
if (dto.IsPaused || dto.IsPausedFromOthers || !dto.IsSynced)
{
UnpairedFromOther?.Invoke(characterIdentifier);
}
}
private Task ReceiveCharacterDataCallback(CharacterCacheDto character, string characterHash)
@@ -53,7 +43,7 @@ namespace MareSynchronos.WebAPI
private void UpdateOrAddBannedUserCallback(BannedUserDto obj)
{
var user = AdminBannedUsers.SingleOrDefault(b => b.CharacterHash == obj.CharacterHash);
var user = AdminBannedUsers.SingleOrDefault(b => string.Equals(b.CharacterHash, obj.CharacterHash, System.StringComparison.Ordinal));
if (user == null)
{
AdminBannedUsers.Add(obj);
@@ -66,12 +56,12 @@ namespace MareSynchronos.WebAPI
private void DeleteBannedUserCallback(BannedUserDto obj)
{
AdminBannedUsers.RemoveAll(a => a.CharacterHash == obj.CharacterHash);
AdminBannedUsers.RemoveAll(a => string.Equals(a.CharacterHash, obj.CharacterHash, System.StringComparison.Ordinal));
}
private void UpdateOrAddForbiddenFileCallback(ForbiddenFileDto obj)
{
var user = AdminForbiddenFiles.SingleOrDefault(b => b.Hash == obj.Hash);
var user = AdminForbiddenFiles.SingleOrDefault(b => string.Equals(b.Hash, obj.Hash, System.StringComparison.Ordinal));
if (user == null)
{
AdminForbiddenFiles.Add(obj);
@@ -84,7 +74,48 @@ namespace MareSynchronos.WebAPI
private void DeleteForbiddenFileCallback(ForbiddenFileDto obj)
{
AdminForbiddenFiles.RemoveAll(f => f.Hash == obj.Hash);
}
AdminForbiddenFiles.RemoveAll(f => string.Equals(f.Hash, obj.Hash, System.StringComparison.Ordinal));
}
private void GroupPairChangedCallback(GroupPairDto dto)
{
if (dto.IsRemoved.GetValueOrDefault(false))
{
GroupPairedClients.RemoveAll(g => string.Equals(g.GroupGID, dto.GroupGID, System.StringComparison.Ordinal) && string.Equals(g.UserUID, dto.UserUID, System.StringComparison.Ordinal));
return;
}
var existingUser = GroupPairedClients.FirstOrDefault(f => string.Equals(f.GroupGID, dto.GroupGID, System.StringComparison.Ordinal) && string.Equals(f.UserUID, dto.UserUID, System.StringComparison.Ordinal));
if (existingUser == null)
{
GroupPairedClients.Add(dto);
return;
}
existingUser.IsPaused = dto.IsPaused ?? existingUser.IsPaused;
existingUser.UserAlias = dto.UserAlias ?? existingUser.UserAlias;
existingUser.IsPinned = dto.IsPinned ?? existingUser.IsPinned;
}
private async Task GroupChangedCallback(GroupDto dto)
{
if (dto.IsDeleted.GetValueOrDefault(false))
{
Groups.RemoveAll(g => string.Equals(g.GID, dto.GID, System.StringComparison.Ordinal));
GroupPairedClients.RemoveAll(g => string.Equals(g.GroupGID, dto.GID, System.StringComparison.Ordinal));
return;
}
var existingGroup = Groups.FirstOrDefault(g => string.Equals(g.GID, dto.GID, System.StringComparison.Ordinal));
if (existingGroup == null)
{
Groups.Add(dto);
GroupPairedClients.AddRange(await _mareHub!.InvokeAsync<List<GroupPairDto>>(Api.InvokeGroupGetUsersInGroup, dto.GID).ConfigureAwait(false));
return;
}
existingGroup.OwnedBy = dto.OwnedBy ?? existingGroup.OwnedBy;
existingGroup.InvitesEnabled = dto.InvitesEnabled ?? existingGroup.InvitesEnabled;
existingGroup.IsPaused = dto.IsPaused ?? existingGroup.IsPaused;
}
}

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
}