Client rework for API change and paradigm shift (#39)

* most of the groups refactoring on client

* register OnMethods for group stuff

* start implementing client (still pretty broken)

* finish implementing new api first iteration

* idk rework everything for pair shit (still WIP); goal is to remove PairedClients and GroupPairClients from ApiController

* move everything to PairManager, remove dictionaries from APiController

* remove admin stuff from client, cleanup

* adjust reconnection handling, add new settings, todo still to remove access from old stuff that's marked obsolete from config

* add back adding servers, fix intro ui

* fix obsolete calls

* adjust config namespace

* add UI for setting animation/sound permissions to syncshells

* add ConfigurationService to hot reload config on change from external

* move transient data cache to configuration

* add deleting service to ui

* fix saving of transient resources

* fix group pair user assignments

* halt scanner when penumbra inactive, add visible/online/offline split to individual pairs and tags

* add presence to syncshell ui

* move fullpause from config to server config

* fixes in code style

* more codestyle

* show info icon on player in shells, don't show icon when no changes from default state are made, add online notifs

* fixes to intro UI

---------

Co-authored-by: rootdarkarchon <root.darkarchon@outlook.com>
This commit is contained in:
rootdarkarchon
2023-01-29 15:13:53 +01:00
committed by GitHub
parent 616af3626a
commit b2276a1883
79 changed files with 3585 additions and 2701 deletions

View File

@@ -1,37 +1,249 @@
[*.cs]
# Remove the line below if you want to inherit .editorconfig settings from higher directories
root = true
# 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
# C# files
[*.cs]
#### Core EditorConfig Options ####
# Indentation and spacing
indent_size = 4
indent_style = space
tab_width = 4
# New line preferences
end_of_line = crlf
insert_final_newline = false
#### .NET Coding Conventions ####
# Organize usings
dotnet_separate_import_directive_groups = false
dotnet_sort_system_directives_first = false
file_header_template = unset
# this. and Me. preferences
dotnet_style_qualification_for_event = false
dotnet_style_qualification_for_field = false
dotnet_style_qualification_for_method = false
dotnet_style_qualification_for_property = false
# Language keywords vs BCL types preferences
dotnet_style_predefined_type_for_locals_parameters_members = true
dotnet_style_predefined_type_for_member_access = true
# Parentheses preferences
dotnet_style_parentheses_in_arithmetic_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_binary_operators = always_for_clarity
dotnet_style_parentheses_in_other_operators = never_if_unnecessary
dotnet_style_parentheses_in_relational_binary_operators = always_for_clarity
# Modifier preferences
dotnet_style_require_accessibility_modifiers = for_non_interface_members
# Expression-level preferences
dotnet_style_coalesce_expression = true
dotnet_style_collection_initializer = true
dotnet_style_explicit_tuple_names = true
dotnet_style_namespace_match_folder = true
dotnet_style_null_propagation = true
dotnet_style_object_initializer = true
dotnet_style_operator_placement_when_wrapping = beginning_of_line
dotnet_style_prefer_auto_properties = true
dotnet_style_prefer_compound_assignment = true
dotnet_style_prefer_conditional_expression_over_assignment = true
dotnet_style_prefer_conditional_expression_over_return = true
dotnet_style_prefer_foreach_explicit_cast_in_source = when_strongly_typed
dotnet_style_prefer_inferred_anonymous_type_member_names = true
dotnet_style_prefer_inferred_tuple_names = true
dotnet_style_prefer_is_null_check_over_reference_equality_method = true
dotnet_style_prefer_simplified_boolean_expressions = true
dotnet_style_prefer_simplified_interpolation = true
# Field preferences
dotnet_style_readonly_field = true
# Parameter preferences
dotnet_code_quality_unused_parameters = all
# Suppression preferences
dotnet_remove_unnecessary_suppression_exclusions = 0
# New line preferences
dotnet_style_allow_multiple_blank_lines_experimental = true
dotnet_style_allow_statement_immediately_after_block_experimental = true
#### C# Coding Conventions ####
# var preferences
csharp_style_var_elsewhere = false
csharp_style_var_for_built_in_types = false
csharp_style_var_when_type_is_apparent = false
# Expression-bodied members
csharp_style_expression_bodied_accessors = true:silent
csharp_style_expression_bodied_constructors = false:silent
csharp_style_expression_bodied_indexers = true:silent
csharp_style_expression_bodied_lambdas = true:silent
csharp_style_expression_bodied_local_functions = false:silent
csharp_style_expression_bodied_methods = false:silent
csharp_style_expression_bodied_operators = false:silent
csharp_style_expression_bodied_properties = true:silent
# Pattern matching preferences
csharp_style_pattern_matching_over_as_with_null_check = true
csharp_style_pattern_matching_over_is_with_cast_check = true
csharp_style_prefer_extended_property_pattern = true
csharp_style_prefer_not_pattern = true
csharp_style_prefer_pattern_matching = true
csharp_style_prefer_switch_expression = true
# Null-checking preferences
csharp_style_conditional_delegate_call = true:suggestion
# Modifier preferences
csharp_prefer_static_local_function = true:suggestion
csharp_preferred_modifier_order = public,private,protected,internal,static,extern,new,virtual,abstract,sealed,override,readonly,unsafe,volatile,async
csharp_style_prefer_readonly_struct = true:suggestion
# Code-block preferences
csharp_prefer_braces = true:silent
csharp_prefer_simple_using_statement = true:suggestion
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
# Expression-level preferences
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_deconstructed_variable_declaration = true:suggestion
csharp_style_implicit_object_creation_when_type_is_apparent = true:suggestion
csharp_style_inlined_variable_declaration = true:suggestion
csharp_style_prefer_index_operator = true:suggestion
csharp_style_prefer_local_over_anonymous_function = true:suggestion
csharp_style_prefer_null_check_over_type_check = true:suggestion
csharp_style_prefer_range_operator = 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
csharp_style_throw_expression = true:suggestion
csharp_style_unused_value_assignment_preference = discard_variable:suggestion
csharp_style_unused_value_expression_statement_preference = discard_variable:silent
# MA0009: Add regex evaluation timeout
dotnet_diagnostic.MA0009.severity = silent
# 'using' directive preferences
csharp_using_directive_placement = outside_namespace:silent
# New line preferences
csharp_style_allow_blank_line_after_colon_in_constructor_initializer_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_arrow_expression_clause_experimental = true:silent
csharp_style_allow_blank_line_after_token_in_conditional_expression_experimental = true:silent
csharp_style_allow_blank_lines_between_consecutive_braces_experimental = true:silent
csharp_style_allow_embedded_statements_on_same_line_experimental = true:silent
#### C# Formatting Rules ####
# New line preferences
csharp_new_line_before_catch = true
csharp_new_line_before_else = true
csharp_new_line_before_finally = true
csharp_new_line_before_members_in_anonymous_types = true
csharp_new_line_before_members_in_object_initializers = true
csharp_new_line_before_open_brace = all
csharp_new_line_between_query_expression_clauses = true
# Indentation preferences
csharp_indent_block_contents = true
csharp_indent_braces = false
csharp_indent_case_contents = true
csharp_indent_case_contents_when_block = true
csharp_indent_labels = one_less_than_current
csharp_indent_switch_labels = true
# Space preferences
csharp_space_after_cast = false
csharp_space_after_colon_in_inheritance_clause = true
csharp_space_after_comma = true
csharp_space_after_dot = false
csharp_space_after_keywords_in_control_flow_statements = true
csharp_space_after_semicolon_in_for_statement = true
csharp_space_around_binary_operators = before_and_after
csharp_space_around_declaration_statements = false
csharp_space_before_colon_in_inheritance_clause = true
csharp_space_before_comma = false
csharp_space_before_dot = false
csharp_space_before_open_square_brackets = false
csharp_space_before_semicolon_in_for_statement = false
csharp_space_between_empty_square_brackets = false
csharp_space_between_method_call_empty_parameter_list_parentheses = false
csharp_space_between_method_call_name_and_opening_parenthesis = false
csharp_space_between_method_call_parameter_list_parentheses = false
csharp_space_between_method_declaration_empty_parameter_list_parentheses = false
csharp_space_between_method_declaration_name_and_open_parenthesis = false
csharp_space_between_method_declaration_parameter_list_parentheses = false
csharp_space_between_parentheses = false
csharp_space_between_square_brackets = false
# Wrapping preferences
csharp_preserve_single_line_blocks = true
csharp_preserve_single_line_statements = true
#### 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.private_or_internal_field_should_be_fieldstyle.severity = suggestion
dotnet_naming_rule.private_or_internal_field_should_be_fieldstyle.symbols = private_or_internal_field
dotnet_naming_rule.private_or_internal_field_should_be_fieldstyle.style = fieldstyle
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.private_or_internal_field.applicable_kinds = field
dotnet_naming_symbols.private_or_internal_field.applicable_accessibilities = internal, private, private_protected
dotnet_naming_symbols.private_or_internal_field.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.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.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.fieldstyle.required_prefix = _
dotnet_naming_style.fieldstyle.required_suffix =
dotnet_naming_style.fieldstyle.word_separator =
dotnet_naming_style.fieldstyle.capitalization = camel_case
dotnet_diagnostic.MA0016.severity = suggestion
dotnet_diagnostic.MA0026.severity = warning
dotnet_diagnostic.MA0046.severity = suggestion
dotnet_diagnostic.MA0051.severity = suggestion
dotnet_diagnostic.MA0011.severity = suggestion
[*.{cs,vb}]
dotnet_style_operator_placement_when_wrapping = beginning_of_line
@@ -53,50 +265,9 @@ 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
dotnet_style_readonly_field = true:suggestion
dotnet_style_predefined_type_for_locals_parameters_members = true:silent
dotnet_style_predefined_type_for_member_access = true:silent
dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent
dotnet_style_allow_multiple_blank_lines_experimental = true:silent
dotnet_style_allow_statement_immediately_after_block_experimental = true:silent

Submodule MareAPI updated: 2015496ec0...981f62a071

View File

@@ -1,235 +0,0 @@
using Dalamud.Configuration;
using Dalamud.Plugin;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using MareSynchronos.API;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using Microsoft.Extensions.Primitives;
namespace MareSynchronos;
public static class ConfigurationExtensions
{
public static bool HasValidSetup(this Configuration configuration)
{
return configuration.AcceptedAgreement && configuration.InitialScanComplete
&& !string.IsNullOrEmpty(configuration.CacheFolder)
&& Directory.Exists(configuration.CacheFolder)
&& configuration.ClientSecret.ContainsKey(configuration.ApiUri);
}
public static Dictionary<string, string> GetCurrentServerUidComments(this Configuration configuration)
{
return configuration.UidServerComments.ContainsKey(configuration.ApiUri)
? configuration.UidServerComments[configuration.ApiUri]
: 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>(StringComparer.Ordinal);
}
configuration.UidServerComments[configuration.ApiUri][uid] = comment;
}
}
[Serializable]
public class Configuration : IPluginConfiguration
{
private string _apiUri = string.Empty;
[NonSerialized]
private DalamudPluginInterface? _pluginInterface;
public bool AcceptedAgreement { get; set; } = false;
public string ApiUri
{
get => string.IsNullOrEmpty(_apiUri) ? ApiController.MainServiceUri : _apiUri;
set => _apiUri = value;
}
public string CacheFolder { get; set; } = string.Empty;
public Dictionary<string, string> ClientSecret { get; set; } = new(StringComparer.Ordinal);
public Dictionary<string, string> CustomServerList { get; set; } = new(StringComparer.Ordinal);
public double MaxLocalCacheInGiB { get; set; } = 20;
public bool ReverseUserSort { get; set; } = false;
public int TimeSpanBetweenScansInSeconds { get; set; } = 30;
public bool FileScanPaused { get; set; } = false;
public bool InitialScanComplete { get; set; } = false;
public bool FullPause { get; set; } = false;
public bool HideInfoMessages { get; set; } = false;
public bool DisableOptionalPluginWarnings { get; set; } = false;
public bool OpenGposeImportOnGposeStart { get; set; } = false;
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(StringComparer.Ordinal);
/// <summary>
/// Each paired user can have multiple tags. Each tag will create a category, and the user will
/// be displayed into that category.
/// The dictionary first maps a server URL to a dictionary, and that
/// dictionary maps the OtherUID of the <see cref="ClientPairDto"/> to a list of tags.
/// </summary>
public Dictionary<string, Dictionary<string, List<string>>> UidServerPairedUserTags = new(StringComparer.Ordinal);
/// <summary>
/// A dictionary that maps a server URL to the tags the user has added for that server.
/// </summary>
public Dictionary<string, HashSet<string>> ServerAvailablePairTags = new(StringComparer.Ordinal);
public HashSet<string> OpenPairTags = new(StringComparer.Ordinal);
public int Version { get; set; } = 5;
public bool ShowTransferWindow { get; set; } = true;
public bool OpenPopupOnAdd { get; set; } = true;
// the below exist just to make saving less cumbersome
public void Initialize(DalamudPluginInterface pluginInterface)
{
_pluginInterface = pluginInterface;
if (!Directory.Exists(CacheFolder))
{
InitialScanComplete = false;
}
Save();
}
public void Save()
{
_pluginInterface!.SavePluginConfig(this);
}
public void Migrate()
{
if (Version == 0)
{
Logger.Debug("Migrating Configuration from V0 to V1");
Version = 1;
ApiUri = ApiUri.Replace("https", "wss", StringComparison.Ordinal);
foreach (var kvp in ClientSecret.ToList())
{
var newKey = kvp.Key.Replace("https", "wss", StringComparison.Ordinal);
ClientSecret.Remove(kvp.Key);
if (ClientSecret.ContainsKey(newKey))
{
ClientSecret[newKey] = kvp.Value;
}
else
{
ClientSecret.Add(newKey, kvp.Value);
}
}
UidServerComments.Add(ApiUri, UidComments.ToDictionary(k => k.Key, k => k.Value, StringComparer.Ordinal));
UidComments.Clear();
Save();
}
if (Version == 1)
{
Logger.Debug("Migrating Configuration from V1 to V2");
ApiUri = ApiUri.Replace("5001", "5000", StringComparison.Ordinal);
foreach (var kvp in ClientSecret.ToList())
{
var newKey = kvp.Key.Replace("5001", "5000", StringComparison.Ordinal);
ClientSecret.Remove(kvp.Key);
if (ClientSecret.ContainsKey(newKey))
{
ClientSecret[newKey] = kvp.Value;
}
else
{
ClientSecret.Add(newKey, kvp.Value);
}
}
foreach (var kvp in UidServerComments.ToList())
{
var newKey = kvp.Key.Replace("5001", "5000", StringComparison.Ordinal);
UidServerComments.Remove(kvp.Key);
UidServerComments.Add(newKey, kvp.Value);
}
Version = 2;
Save();
}
if (Version == 2)
{
Logger.Debug("Migrating Configuration from V2 to V3");
ApiUri = "wss://v2202207178628194299.powersrv.de:6871";
ClientSecret.Clear();
UidServerComments.Clear();
Version = 3;
Save();
}
if (Version == 3)
{
Logger.Debug("Migrating Configuration from V3 to V4");
ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872", StringComparison.Ordinal);
foreach (var kvp in ClientSecret.ToList())
{
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))
{
ClientSecret[newKey] = kvp.Value;
}
else
{
ClientSecret.Add(newKey, kvp.Value);
}
}
foreach (var kvp in UidServerComments.ToList())
{
var newKey = kvp.Key.Replace("wss://v2202207178628194299.powersrv.de:6871", "wss://v2202207178628194299.powersrv.de:6872", StringComparison.Ordinal);
UidServerComments.Remove(kvp.Key);
UidServerComments.Add(newKey, kvp.Value);
}
Version = 4;
Save();
}
if (Version == 4)
{
Logger.Debug("Migrating Configuration from V4 to V5");
ApiUri = ApiUri.Replace("wss://v2202207178628194299.powersrv.de:6872", "wss://maresynchronos.com", StringComparison.Ordinal);
ClientSecret.Remove("wss://v2202207178628194299.powersrv.de:6872");
UidServerComments.Remove("wss://v2202207178628194299.powersrv.de:6872");
Version = 5;
Save();
}
}
}

View File

@@ -0,0 +1,3 @@
namespace MareSynchronos.Delegates;
public delegate void CharacterDataDelegate(API.Data.CharacterData characterCache);

View File

@@ -0,0 +1,3 @@
namespace MareSynchronos.Delegates;
public delegate void DrawObjectDelegate(IntPtr address, int objTblIdx);

View File

@@ -0,0 +1,3 @@
namespace MareSynchronos.Delegates;
public delegate void FloatDelegate(float change);

View File

@@ -0,0 +1,3 @@
namespace MareSynchronos.Delegates;
public delegate void PenumbraFileResourceDelegate(IntPtr drawObject, string gamePath, string filePath);

View File

@@ -0,0 +1,3 @@
namespace MareSynchronos.Delegates;
public delegate void StringDelegate(string str);

View File

@@ -0,0 +1,3 @@
namespace MareSynchronos.Delegates;
public delegate void VoidDelegate();

View File

@@ -1,8 +1,7 @@
using MareSynchronos.API;
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.FileCache;
using Newtonsoft.Json;
using System.Collections.Generic;
using System.IO;
using System.Text;
namespace MareSynchronos.Export;
@@ -17,7 +16,7 @@ public record MareCharaFileData
public List<FileSwap> FileSwaps { get; set; } = new();
public MareCharaFileData() { }
public MareCharaFileData(FileCacheManager manager, string description, CharacterCacheDto dto)
public MareCharaFileData(FileCacheManager manager, string description, CharacterData dto)
{
Description = description;

View File

@@ -1,4 +1,4 @@
using MareSynchronos.API;
using MareSynchronos.API.Data;
using MareSynchronos.FileCache;
namespace MareSynchronos.Export;
@@ -12,7 +12,7 @@ internal class MareCharaFileDataFactory
_fileCacheManager = fileCacheManager;
}
public MareCharaFileData Create(string description, CharacterCacheDto characterCacheDto)
public MareCharaFileData Create(string description, CharacterData characterCacheDto)
{
return new MareCharaFileData(_fileCacheManager, description, characterCacheDto);
}

View File

@@ -1,9 +1,4 @@
using Lumina.Extensions;
using System.Collections.Generic;
using System.IO;
using System.Runtime.CompilerServices;
namespace MareSynchronos.Export;
namespace MareSynchronos.Export;
public record MareCharaFileHeader(byte Version, MareCharaFileData CharaFileData)
{
@@ -11,7 +6,7 @@ public record MareCharaFileHeader(byte Version, MareCharaFileData CharaFileData)
public byte Version { get; set; } = Version;
public MareCharaFileData CharaFileData { get; set; } = CharaFileData;
public string FilePath { get; private set; }
public string FilePath { get; private set; } = string.Empty;
public void WriteToStream(BinaryWriter writer)
{
@@ -28,7 +23,7 @@ public record MareCharaFileHeader(byte Version, MareCharaFileData CharaFileData)
public static MareCharaFileHeader? FromBinaryReader(string path, BinaryReader reader)
{
var chars = new string(reader.ReadChars(4));
if (!string.Equals(chars, "MCDF", System.StringComparison.Ordinal)) throw new System.Exception("Not a Mare Chara File");
if (!string.Equals(chars, "MCDF", StringComparison.Ordinal)) throw new System.Exception("Not a Mare Chara File");
MareCharaFileHeader? decoded = null;
@@ -37,8 +32,10 @@ public record MareCharaFileHeader(byte Version, MareCharaFileData CharaFileData)
{
var dataLength = reader.ReadInt32();
decoded = new(version, MareCharaFileData.FromByteArray(reader.ReadBytes(dataLength)));
decoded.FilePath = path;
decoded = new(version, MareCharaFileData.FromByteArray(reader.ReadBytes(dataLength)))
{
FilePath = path,
};
}
return decoded;

View File

@@ -1,33 +1,29 @@
using System.Linq;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.ClientState.Objects.Types;
using LZ4;
using MareSynchronos.API;
using MareSynchronos.FileCache;
using MareSynchronos.Managers;
using MareSynchronos.Utils;
using System;
using System.Collections.Generic;
using System.IO;
using System.Threading.Tasks;
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.MareConfiguration;
namespace MareSynchronos.Export;
public class MareCharaFileManager
{
private readonly FileCacheManager _manager;
private readonly IpcManager _ipcManager;
private readonly Configuration _configuration;
private readonly ConfigurationService _configService;
private readonly DalamudUtil _dalamudUtil;
private readonly MareCharaFileDataFactory _factory;
public MareCharaFileHeader? LoadedCharaFile { get; private set; }
public bool CurrentlyWorking { get; private set; } = false;
public MareCharaFileManager(FileCacheManager manager, IpcManager ipcManager, Configuration configuration, DalamudUtil dalamudUtil)
public MareCharaFileManager(FileCacheManager manager, IpcManager ipcManager, ConfigurationService configService, DalamudUtil dalamudUtil)
{
_factory = new(manager);
_manager = manager;
_ipcManager = ipcManager;
_configuration = configuration;
_configService = configService;
_dalamudUtil = dalamudUtil;
}
@@ -46,22 +42,23 @@ public class MareCharaFileManager
using var reader = new BinaryReader(lz4Stream);
LoadedCharaFile = MareCharaFileHeader.FromBinaryReader(filePath, reader);
Logger.Debug("Read Mare Chara File");
Logger.Debug("Version: " + LoadedCharaFile.Version);
Logger.Debug("Version: " + (LoadedCharaFile?.Version ?? -1));
}
catch { throw; }
finally { CurrentlyWorking = false; }
}
public async Task ApplyMareCharaFile(GameObject charaTarget)
public async Task ApplyMareCharaFile(GameObject? charaTarget)
{
Dictionary<string, string> extractedFiles = new();
Dictionary<string, string> extractedFiles = new(StringComparer.Ordinal);
CurrentlyWorking = true;
try
{
if (LoadedCharaFile == null || charaTarget == null || !File.Exists(LoadedCharaFile.FilePath)) return;
using var unwrapped = File.OpenRead(LoadedCharaFile.FilePath);
var unwrapped = File.OpenRead(LoadedCharaFile.FilePath);
await using (unwrapped.ConfigureAwait(false))
{
using var lz4Stream = new LZ4Stream(unwrapped, LZ4StreamMode.Decompress, LZ4StreamFlags.HighCompression);
using var reader = new BinaryReader(lz4Stream);
LoadedCharaFile.AdvanceReaderToData(reader);
@@ -75,7 +72,7 @@ public class MareCharaFileManager
fileSwaps.Add(path, fileSwap.FileSwapPath);
}
}
_ipcManager.ToggleGposeQueueMode(true);
_ipcManager.ToggleGposeQueueMode(on: true);
_ipcManager.PenumbraRemoveTemporaryCollection(charaTarget.Name.TextValue);
_ipcManager.PenumbraSetTemporaryMods(charaTarget.Name.TextValue,
extractedFiles.Union(fileSwaps).ToDictionary(d => d.Key, d => d.Value, StringComparer.Ordinal),
@@ -83,7 +80,8 @@ public class MareCharaFileManager
_ipcManager.GlamourerApplyAll(LoadedCharaFile.CharaFileData.GlamourerData, charaTarget.Address);
_dalamudUtil.WaitWhileGposeCharacterIsDrawing(charaTarget.Address);
_ipcManager.PenumbraRemoveTemporaryCollection(charaTarget.Name.TextValue);
_ipcManager.ToggleGposeQueueMode(false);
_ipcManager.ToggleGposeQueueMode(on: false);
}
}
catch { throw; }
finally
@@ -104,7 +102,7 @@ public class MareCharaFileManager
int i = 0;
foreach (var fileData in charaFileHeader.CharaFileData.Files)
{
var fileName = Path.Combine(_configuration.CacheFolder, "mare_" + (i++) + ".tmp");
var fileName = Path.Combine(_configService.Current.CacheFolder, "mare_" + (i++) + ".tmp");
var length = fileData.Length;
var bufferSize = 4 * 1024 * 1024;
var buffer = new byte[bufferSize];
@@ -127,7 +125,7 @@ public class MareCharaFileManager
return gamePathToFilePath;
}
public void SaveMareCharaFile(CharacterCacheDto? dto, string description, string filePath)
public void SaveMareCharaFile(CharacterData? dto, string description, string filePath)
{
CurrentlyWorking = true;
try
@@ -148,7 +146,7 @@ public class MareCharaFileManager
{
foreach (var file in replacement.Select(item => _manager.GetFileCacheByHash(item.Hash)).Where(file => file != null))
{
var length = new FileInfo(file.ResolvedFilepath).Length;
var length = new FileInfo(file!.ResolvedFilepath).Length;
using var fsRead = File.OpenRead(file.ResolvedFilepath);
using var br = new BinaryReader(fsRead);
int readBytes = 0;

View File

@@ -0,0 +1,26 @@
using MareSynchronos.API.Dto.User;
using MareSynchronos.FileCache;
using MareSynchronos.Managers;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.Factories;
public class CachedPlayerFactory
{
private readonly IpcManager _ipcManager;
private readonly DalamudUtil _dalamudUtil;
private readonly FileCacheManager _fileCacheManager;
public CachedPlayerFactory(IpcManager ipcManager, DalamudUtil dalamudUtil, FileCacheManager fileCacheManager)
{
_ipcManager = ipcManager;
_dalamudUtil = dalamudUtil;
_fileCacheManager = fileCacheManager;
}
public CachedPlayer Create(OnlineUserIdentDto dto, ApiController apiController)
{
return new CachedPlayer(dto, _ipcManager, apiController, _dalamudUtil, _fileCacheManager);
}
}

View File

@@ -1,18 +1,13 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Threading;
using System.Diagnostics;
using Dalamud.Utility;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
using MareSynchronos.API;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.Interop;
using MareSynchronos.Managers;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using Penumbra.Interop.Structs;
using Object = FFXIVClientStructs.FFXIV.Client.Graphics.Scene.Object;
using Penumbra.String;
using Weapon = MareSynchronos.Interop.Weapon;
@@ -44,7 +39,7 @@ public class CharacterDataFactory
{
if (!_ipcManager.Initialized)
{
throw new ArgumentException("Penumbra is not connected");
throw new InvalidOperationException("Penumbra is not connected");
}
bool pointerIsZero = true;
@@ -231,7 +226,7 @@ public class CharacterDataFactory
}
}
var shpkFileReplacement = CreateFileReplacement(shpkPath, true);
var shpkFileReplacement = CreateFileReplacement(shpkPath, doNotReverseResolve: true);
DebugPrint(shpkFileReplacement, objectKind, "Shader", inheritanceLevel);
cache.AddFileReplacement(objectKind, shpkFileReplacement);
}
@@ -284,7 +279,7 @@ public class CharacterDataFactory
}
var chara = _dalamudUtil.CreateGameObject(charaPointer)!;
while (!_dalamudUtil.IsObjectPresent(chara))
while (!DalamudUtil.IsObjectPresent(chara))
{
Logger.Verbose("Character is null but it shouldn't be, waiting");
Thread.Sleep(50);
@@ -396,7 +391,7 @@ public class CharacterDataFactory
foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)weaponObject))
{
Logger.Verbose("Found transient weapon resource: " + item);
AddReplacement(item, objectKind, previousData, 1, true);
AddReplacement(item, objectKind, previousData, 1, doNotReverseResolve: true);
}
if (weaponObject->NextSibling != (IntPtr)weaponObject)
@@ -413,7 +408,7 @@ public class CharacterDataFactory
foreach (var item in _transientResourceManager.GetTransientResources((IntPtr)offHandWeapon))
{
Logger.Verbose("Found transient offhand weapon resource: " + item);
AddReplacement(item, objectKind, previousData, 1, true);
AddReplacement(item, objectKind, previousData, 1, doNotReverseResolve: true);
}
}
}
@@ -421,7 +416,7 @@ public class CharacterDataFactory
AddReplacementSkeleton(((HumanExt*)human)->Human.RaceSexId, objectKind, previousData);
try
{
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->Decal->FileName()).ToString(), objectKind, previousData, 0, false);
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->Decal->FileName()).ToString(), objectKind, previousData, 0, doNotReverseResolve: false);
}
catch
{
@@ -429,7 +424,7 @@ public class CharacterDataFactory
}
try
{
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), objectKind, previousData, 0, false);
AddReplacementsFromTexture(new ByteString(((HumanExt*)human)->LegacyBodyDecal->FileName()).ToString(), objectKind, previousData, 0, doNotReverseResolve: false);
}
catch
{
@@ -448,7 +443,7 @@ public class CharacterDataFactory
string skeletonPath = $"chara/human/c{raceSexIdString}/skeleton/base/b0001/skl_c{raceSexIdString}b0001.sklb";
var replacement = CreateFileReplacement(skeletonPath, true);
var replacement = CreateFileReplacement(skeletonPath, doNotReverseResolve: true);
cache.AddFileReplacement(objectKind, replacement);
DebugPrint(replacement, objectKind, "SKLB", 0);

View File

@@ -0,0 +1,22 @@
using MareSynchronos.Managers;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Models;
namespace MareSynchronos.Factories;
public class PairFactory
{
private readonly ConfigurationService _configService;
private readonly ServerConfigurationManager _serverConfigurationManager;
public PairFactory(ConfigurationService configService, ServerConfigurationManager serverConfigurationManager)
{
_configService = configService;
_serverConfigurationManager = serverConfigurationManager;
}
public Pair Create()
{
return new Pair(_configService, _serverConfigurationManager);
}
}

View File

@@ -1,7 +1,6 @@
#nullable disable
using MareSynchronos;
using System.Globalization;
namespace MareSynchronos.FileCache;
@@ -22,7 +21,7 @@ public class FileCacheEntity
public void SetResolvedFilePath(string filePath)
{
ResolvedFilepath = filePath.ToLowerInvariant().Replace("\\\\", "\\", System.StringComparison.Ordinal);
ResolvedFilepath = filePath.ToLowerInvariant().Replace("\\\\", "\\", StringComparison.Ordinal);
}
public string CsvEntry => $"{Hash}{FileCacheManager.CsvSplit}{PrefixedFilePath}{FileCacheManager.CsvSplit}{LastModifiedDateTicks.ToString(CultureInfo.InvariantCulture)}";

View File

@@ -1,44 +1,41 @@
using MareSynchronos.Managers;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Globalization;
using System.IO;
using System.Linq;
using System.Text;
namespace MareSynchronos.FileCache;
public class FileCacheManager : IDisposable
{
private const string PenumbraPrefix = "{penumbra}";
private const string CachePrefix = "{cache}";
private const string _penumbraPrefix = "{penumbra}";
private const string _cachePrefix = "{cache}";
private readonly IpcManager _ipcManager;
private readonly Configuration _configuration;
private readonly string CsvPath;
private string CsvBakPath => CsvPath + ".bak";
private readonly ConcurrentDictionary<string, FileCacheEntity> FileCaches = new(StringComparer.Ordinal);
private readonly ConfigurationService _configService;
private readonly string _csvPath;
private string CsvBakPath => _csvPath + ".bak";
private readonly ConcurrentDictionary<string, FileCacheEntity> _fileCaches = new(StringComparer.Ordinal);
public const string CsvSplit = "|";
private object _fileWriteLock = new();
private readonly object _fileWriteLock = new();
public FileCacheManager(IpcManager ipcManager, Configuration configuration, string configDirectoryName)
public FileCacheManager(IpcManager ipcManager, ConfigurationService configService)
{
_ipcManager = ipcManager;
_configuration = configuration;
CsvPath = Path.Combine(configDirectoryName, "FileCache.csv");
_configService = configService;
_csvPath = Path.Combine(configService.ConfigurationDirectory, "FileCache.csv");
lock (_fileWriteLock)
{
if (File.Exists(CsvBakPath))
{
File.Move(CsvBakPath, CsvPath, true);
File.Move(CsvBakPath, _csvPath, overwrite: true);
}
}
if (File.Exists(CsvPath))
if (File.Exists(_csvPath))
{
var entries = File.ReadAllLines(CsvPath);
var entries = File.ReadAllLines(_csvPath);
foreach (var entry in entries)
{
var splittedEntry = entry.Split(CsvSplit, StringSplitOptions.None);
@@ -47,7 +44,7 @@ public class FileCacheManager : IDisposable
var hash = splittedEntry[0];
var path = splittedEntry[1];
var time = splittedEntry[2];
FileCaches[path] = ReplacePathPrefixes(new FileCacheEntity(hash, path, time));
_fileCaches[path] = ReplacePathPrefixes(new FileCacheEntity(hash, path, time));
}
catch (Exception)
{
@@ -60,19 +57,19 @@ public class FileCacheManager : IDisposable
public void WriteOutFullCsv()
{
StringBuilder sb = new();
foreach (var entry in FileCaches.OrderBy(f => f.Value.PrefixedFilePath))
foreach (var entry in _fileCaches.OrderBy(f => f.Value.PrefixedFilePath, StringComparer.OrdinalIgnoreCase))
{
sb.AppendLine(entry.Value.CsvEntry);
}
if (File.Exists(CsvPath))
if (File.Exists(_csvPath))
{
File.Copy(CsvPath, CsvBakPath, true);
File.Copy(_csvPath, CsvBakPath, overwrite: true);
}
lock (_fileWriteLock)
{
try
{
File.WriteAllText(CsvPath, sb.ToString());
File.WriteAllText(_csvPath, sb.ToString());
File.Delete(CsvBakPath);
}
catch
@@ -82,13 +79,13 @@ public class FileCacheManager : IDisposable
}
}
public List<FileCacheEntity> GetAllFileCaches() => FileCaches.Values.ToList();
public List<FileCacheEntity> GetAllFileCaches() => _fileCaches.Values.ToList();
public FileCacheEntity? GetFileCacheByHash(string hash)
{
if (FileCaches.Any(f => string.Equals(f.Value.Hash, hash, StringComparison.Ordinal)))
if (_fileCaches.Any(f => string.Equals(f.Value.Hash, hash, StringComparison.Ordinal)))
{
return GetValidatedFileCache(FileCaches.FirstOrDefault(f => string.Equals(f.Value.Hash, hash, StringComparison.Ordinal)).Value);
return GetValidatedFileCache(_fileCaches.FirstOrDefault(f => string.Equals(f.Value.Hash, hash, StringComparison.Ordinal)).Value);
}
return null;
@@ -113,7 +110,7 @@ public class FileCacheManager : IDisposable
public FileCacheEntity? GetFileCacheByPath(string path)
{
var cleanedPath = path.Replace("/", "\\", StringComparison.OrdinalIgnoreCase).ToLowerInvariant().Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), "", StringComparison.OrdinalIgnoreCase);
var entry = FileCaches.FirstOrDefault(f => f.Value.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.OrdinalIgnoreCase)).Value;
var entry = _fileCaches.FirstOrDefault(f => f.Value.ResolvedFilepath.EndsWith(cleanedPath, StringComparison.OrdinalIgnoreCase)).Value;
if (entry == null)
{
@@ -132,8 +129,8 @@ public class FileCacheManager : IDisposable
FileInfo fi = new(path);
if (!fi.Exists) return null;
var fullName = fi.FullName.ToLowerInvariant();
if (!fullName.Contains(_configuration.CacheFolder.ToLowerInvariant(), StringComparison.Ordinal)) return null;
string prefixedPath = fullName.Replace(_configuration.CacheFolder.ToLowerInvariant(), CachePrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
if (!fullName.Contains(_configService.Current.CacheFolder.ToLowerInvariant(), StringComparison.Ordinal)) return null;
string prefixedPath = fullName.Replace(_configService.Current.CacheFolder.ToLowerInvariant(), _cachePrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
return CreateFileCacheEntity(fi, prefixedPath, fi.Name.ToUpper(CultureInfo.InvariantCulture));
}
@@ -144,22 +141,19 @@ public class FileCacheManager : IDisposable
if (!fi.Exists) return null;
var fullName = fi.FullName.ToLowerInvariant();
if (!fullName.Contains(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), StringComparison.Ordinal)) return null;
string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), PenumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
string prefixedPath = fullName.Replace(_ipcManager.PenumbraModDirectory()!.ToLowerInvariant(), _penumbraPrefix + "\\", StringComparison.Ordinal).Replace("\\\\", "\\", StringComparison.Ordinal);
return CreateFileCacheEntity(fi, prefixedPath);
}
private FileCacheEntity? CreateFileCacheEntity(FileInfo fileInfo, string prefixedPath, string? hash = null)
{
if (hash == null)
{
hash = Crypto.GetFileHash(fileInfo.FullName);
}
hash ??= Crypto.GetFileHash(fileInfo.FullName);
var entity = new FileCacheEntity(hash, prefixedPath, fileInfo.LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture));
entity = ReplacePathPrefixes(entity);
FileCaches[prefixedPath] = entity;
_fileCaches[prefixedPath] = entity;
lock (_fileWriteLock)
{
File.AppendAllLines(CsvPath, new[] { entity.CsvEntry });
File.AppendAllLines(_csvPath, new[] { entity.CsvEntry });
}
var result = GetFileCacheByPath(fileInfo.FullName);
Logger.Debug("Creating file cache for " + fileInfo.FullName + " success: " + (result != null));
@@ -178,7 +172,7 @@ public class FileCacheManager : IDisposable
var file = new FileInfo(fileCache.ResolvedFilepath);
if (!file.Exists)
{
FileCaches.Remove(fileCache.PrefixedFilePath, out _);
_fileCaches.Remove(fileCache.PrefixedFilePath, out _);
return null;
}
@@ -193,7 +187,7 @@ public class FileCacheManager : IDisposable
public void RemoveHash(FileCacheEntity entity)
{
Logger.Verbose("Removing " + entity.ResolvedFilepath);
FileCaches.Remove(entity.PrefixedFilePath, out _);
_fileCaches.Remove(entity.PrefixedFilePath, out _);
}
public void UpdateHash(FileCacheEntity fileCache)
@@ -201,19 +195,19 @@ public class FileCacheManager : IDisposable
Logger.Verbose("Updating hash for " + fileCache.ResolvedFilepath);
fileCache.Hash = Crypto.GetFileHash(fileCache.ResolvedFilepath);
fileCache.LastModifiedDateTicks = new FileInfo(fileCache.ResolvedFilepath).LastWriteTimeUtc.Ticks.ToString(CultureInfo.InvariantCulture);
FileCaches.Remove(fileCache.PrefixedFilePath, out _);
FileCaches[fileCache.PrefixedFilePath] = fileCache;
_fileCaches.Remove(fileCache.PrefixedFilePath, out _);
_fileCaches[fileCache.PrefixedFilePath] = fileCache;
}
private FileCacheEntity ReplacePathPrefixes(FileCacheEntity fileCache)
{
if (fileCache.PrefixedFilePath.StartsWith(PenumbraPrefix, StringComparison.OrdinalIgnoreCase))
if (fileCache.PrefixedFilePath.StartsWith(_penumbraPrefix, StringComparison.OrdinalIgnoreCase))
{
fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(PenumbraPrefix, _ipcManager.PenumbraModDirectory(), StringComparison.Ordinal));
fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(_penumbraPrefix, _ipcManager.PenumbraModDirectory(), StringComparison.Ordinal));
}
else if (fileCache.PrefixedFilePath.StartsWith(CachePrefix, StringComparison.OrdinalIgnoreCase))
else if (fileCache.PrefixedFilePath.StartsWith(_cachePrefix, StringComparison.OrdinalIgnoreCase))
{
fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(CachePrefix, _configuration.CacheFolder, StringComparison.Ordinal));
fileCache.SetResolvedFilePath(fileCache.PrefixedFilePath.Replace(_cachePrefix, _configService.Current.CacheFolder, StringComparison.Ordinal));
}
return fileCache;

View File

@@ -1,11 +1,9 @@
using MareSynchronos.Models;
namespace MareSynchronos.FileCache;
namespace MareSynchronos.FileCache;
public enum FileState
{
Valid,
RequireUpdate,
RequireDeletion
RequireDeletion,
}

View File

@@ -1,11 +1,6 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using System.Collections.Concurrent;
using MareSynchronos.Managers;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
@@ -14,19 +9,19 @@ namespace MareSynchronos.FileCache;
public class PeriodicFileScanner : IDisposable
{
private readonly IpcManager _ipcManager;
private readonly Configuration _pluginConfiguration;
private readonly ConfigurationService _configService;
private readonly FileCacheManager _fileDbManager;
private readonly ApiController _apiController;
private readonly DalamudUtil _dalamudUtil;
private CancellationTokenSource? _scanCancellationTokenSource;
private Task? _fileScannerTask = null;
public ConcurrentDictionary<string, int> haltScanLocks = new(StringComparer.Ordinal);
public PeriodicFileScanner(IpcManager ipcManager, Configuration pluginConfiguration, FileCacheManager fileDbManager, ApiController apiController, DalamudUtil dalamudUtil)
public PeriodicFileScanner(IpcManager ipcManager, ConfigurationService configService, FileCacheManager fileDbManager, ApiController apiController, DalamudUtil dalamudUtil)
{
Logger.Verbose("Creating " + nameof(PeriodicFileScanner));
_ipcManager = ipcManager;
_pluginConfiguration = pluginConfiguration;
_configService = configService;
_fileDbManager = fileDbManager;
_apiController = apiController;
_dalamudUtil = dalamudUtil;
@@ -69,10 +64,10 @@ public class PeriodicFileScanner : IDisposable
haltScanLocks[source]--;
if (haltScanLocks[source] < 0) haltScanLocks[source] = 0;
if (fileScanWasRunning && haltScanLocks.All(f => f.Value == 0))
if (_fileScanWasRunning && haltScanLocks.All(f => f.Value == 0))
{
fileScanWasRunning = false;
InvokeScan(true);
_fileScanWasRunning = false;
InvokeScan(forced: true);
}
}
@@ -84,13 +79,13 @@ public class PeriodicFileScanner : IDisposable
if (IsScanRunning && haltScanLocks.Any(f => f.Value > 0))
{
_scanCancellationTokenSource?.Cancel();
fileScanWasRunning = true;
_fileScanWasRunning = true;
}
}
private bool fileScanWasRunning = false;
private long currentFileProgress = 0;
public long CurrentFileProgress => currentFileProgress;
private bool _fileScanWasRunning = false;
private long _currentFileProgress = 0;
public long CurrentFileProgress => _currentFileProgress;
public long FileCacheSize { get; set; }
@@ -100,7 +95,7 @@ public class PeriodicFileScanner : IDisposable
public string TimeUntilNextScan => _timeUntilNextScan.ToString(@"mm\:ss");
private TimeSpan _timeUntilNextScan = TimeSpan.Zero;
private int timeBetweenScans => _pluginConfiguration.TimeSpanBetweenScansInSeconds;
private int TimeBetweenScans => _configService.Current.TimeSpanBetweenScansInSeconds;
public void Dispose()
{
@@ -118,7 +113,7 @@ public class PeriodicFileScanner : IDisposable
{
bool isForced = forced;
TotalFiles = 0;
currentFileProgress = 0;
_currentFileProgress = 0;
_scanCancellationTokenSource?.Cancel();
_scanCancellationTokenSource = new CancellationTokenSource();
var token = _scanCancellationTokenSource.Token;
@@ -126,22 +121,22 @@ public class PeriodicFileScanner : IDisposable
{
while (!token.IsCancellationRequested)
{
while (haltScanLocks.Any(f => f.Value > 0))
while (haltScanLocks.Any(f => f.Value > 0) || !_ipcManager.CheckPenumbraApi())
{
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
}
isForced |= RecalculateFileCacheSize();
if (!_pluginConfiguration.FileScanPaused || isForced)
if (!_configService.Current.FileScanPaused || isForced)
{
isForced = false;
TotalFiles = 0;
currentFileProgress = 0;
_currentFileProgress = 0;
PeriodicFileScan(token);
TotalFiles = 0;
currentFileProgress = 0;
_currentFileProgress = 0;
}
_timeUntilNextScan = TimeSpan.FromSeconds(timeBetweenScans);
_timeUntilNextScan = TimeSpan.FromSeconds(TimeBetweenScans);
while (_timeUntilNextScan.TotalSeconds >= 0)
{
await Task.Delay(TimeSpan.FromSeconds(1), token).ConfigureAwait(false);
@@ -153,7 +148,7 @@ public class PeriodicFileScanner : IDisposable
public bool RecalculateFileCacheSize()
{
FileCacheSize = Directory.EnumerateFiles(_pluginConfiguration.CacheFolder).Sum(f =>
FileCacheSize = Directory.EnumerateFiles(_configService.Current.CacheFolder).Sum(f =>
{
try
{
@@ -165,13 +160,13 @@ public class PeriodicFileScanner : IDisposable
}
});
if (FileCacheSize < (long)_pluginConfiguration.MaxLocalCacheInGiB * 1024 * 1024 * 1024) return false;
if (FileCacheSize < (long)_configService.Current.MaxLocalCacheInGiB * 1024 * 1024 * 1024) return false;
var allFiles = Directory.EnumerateFiles(_pluginConfiguration.CacheFolder)
var allFiles = Directory.EnumerateFiles(_configService.Current.CacheFolder)
.Select(f => new FileInfo(f)).OrderBy(f => f.LastAccessTime).ToList();
while (FileCacheSize > (long)_pluginConfiguration.MaxLocalCacheInGiB * 1024 * 1024 * 1024)
while (FileCacheSize > (long)_configService.Current.MaxLocalCacheInGiB * 1024 * 1024 * 1024)
{
var oldestFile = allFiles.First();
var oldestFile = allFiles[0];
FileCacheSize -= oldestFile.Length;
File.Delete(oldestFile.FullName);
allFiles.Remove(oldestFile);
@@ -191,7 +186,7 @@ public class PeriodicFileScanner : IDisposable
penDirExists = false;
Logger.Warn("Penumbra directory is not set or does not exist.");
}
if (string.IsNullOrEmpty(_pluginConfiguration.CacheFolder) || !Directory.Exists(_pluginConfiguration.CacheFolder))
if (string.IsNullOrEmpty(_configService.Current.CacheFolder) || !Directory.Exists(_configService.Current.CacheFolder))
{
cacheDirExists = false;
Logger.Warn("Mare Cache directory is not set or does not exist.");
@@ -201,19 +196,19 @@ public class PeriodicFileScanner : IDisposable
return;
}
Logger.Debug("Getting files from " + penumbraDir + " and " + _pluginConfiguration.CacheFolder);
Logger.Debug("Getting files from " + penumbraDir + " and " + _configService.Current.CacheFolder);
string[] ext = { ".mdl", ".tex", ".mtrl", ".tmb", ".pap", ".avfx", ".atex", ".sklb", ".eid", ".phyb", ".scd", ".skp", ".shpk" };
var scannedFiles = new ConcurrentDictionary<string, bool>(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, StringComparison.OrdinalIgnoreCase))
&& !f.Contains(@"\bg\", StringComparison.OrdinalIgnoreCase)
&& !f.Contains(@"\bgcommon\", StringComparison.OrdinalIgnoreCase)
&& !f.Contains(@"\ui\", StringComparison.OrdinalIgnoreCase))
.Concat(Directory.EnumerateFiles(_pluginConfiguration.CacheFolder, "*.*", SearchOption.TopDirectoryOnly)
.Concat(Directory.EnumerateFiles(_configService.Current.CacheFolder, "*.*", SearchOption.TopDirectoryOnly)
.Where(f => new FileInfo(f).Name.Length == 40)
.Select(s => s.ToLowerInvariant()).ToList())
.Select(c => new KeyValuePair<string, bool>(c, false)), StringComparer.OrdinalIgnoreCase);
.Select(c => new KeyValuePair<string, bool>(c, value: false)), StringComparer.OrdinalIgnoreCase);
TotalFiles = scannedFiles.Count;
@@ -253,7 +248,7 @@ public class PeriodicFileScanner : IDisposable
Logger.Warn(ex.StackTrace);
}
Interlocked.Increment(ref currentFileProgress);
Interlocked.Increment(ref _currentFileProgress);
Thread.Sleep(1);
}, ct);
@@ -308,7 +303,7 @@ public class PeriodicFileScanner : IDisposable
Logger.Warn(ex.StackTrace);
}
Interlocked.Increment(ref currentFileProgress);
Interlocked.Increment(ref _currentFileProgress);
Thread.Sleep(1);
}, ct);
@@ -321,22 +316,22 @@ public class PeriodicFileScanner : IDisposable
Logger.Debug("Scan complete");
TotalFiles = 0;
currentFileProgress = 0;
_currentFileProgress = 0;
entitiesToRemove.Clear();
scannedFiles.Clear();
dbTasks = Array.Empty<Task>();
if (!_pluginConfiguration.InitialScanComplete)
if (!_configService.Current.InitialScanComplete)
{
_pluginConfiguration.InitialScanComplete = true;
_pluginConfiguration.Save();
_configService.Current.InitialScanComplete = true;
_configService.Save();
}
}
public void StartScan()
{
if (!_ipcManager.Initialized || !_pluginConfiguration.HasValidSetup()) return;
if (!_ipcManager.Initialized || !_configService.Current.HasValidSetup()) return;
Logger.Verbose("Penumbra is active, configuration is valid, scan");
InvokeScan(true);
InvokeScan(forced: true);
}
}

View File

@@ -0,0 +1,11 @@
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
namespace MareSynchronos.Interop;
[StructLayout(LayoutKind.Explicit)]
public unsafe struct CharaExt
{
[FieldOffset(0x0)] public Character Character;
[FieldOffset(0x650)] public Character* Mount;
}

View File

@@ -0,0 +1,12 @@
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
namespace MareSynchronos.Interop;
[StructLayout(LayoutKind.Explicit)]
public unsafe struct HumanExt
{
[FieldOffset(0x0)] public Human Human;
[FieldOffset(0x9E8)] public ResourceHandle* Decal;
[FieldOffset(0x9F0)] public ResourceHandle* LegacyBodyDecal;
}

View File

@@ -1,18 +1,10 @@
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
namespace Penumbra.Interop.Structs;
namespace MareSynchronos.Interop;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct Material
{
[FieldOffset( 0x10 )]
[FieldOffset(0x10)]
public ResourceHandle* ResourceHandle;
}
[StructLayout( LayoutKind.Explicit )]
public unsafe struct MaterialData
{
[FieldOffset( 0x0 )]
public byte* Data;
}

View File

@@ -0,0 +1,10 @@
using System.Runtime.InteropServices;
namespace MareSynchronos.Interop;
[StructLayout(LayoutKind.Explicit)]
public unsafe struct MaterialData
{
[FieldOffset(0x0)]
public byte* Data;
}

View File

@@ -1,29 +1,28 @@
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using System.Runtime.InteropServices;
namespace Penumbra.Interop.Structs;
namespace MareSynchronos.Interop;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct MtrlResource
{
[FieldOffset( 0x00 )]
[FieldOffset(0x00)]
public ResourceHandle Handle;
[FieldOffset( 0xD0 )]
[FieldOffset(0xD0)]
public ushort* TexSpace; // Contains the offsets for the tex files inside the string list.
[FieldOffset( 0xE0 )]
[FieldOffset(0xE0)]
public byte* StringList;
[FieldOffset( 0xF8 )]
[FieldOffset(0xF8)]
public ushort ShpkOffset;
[FieldOffset( 0xFA )]
[FieldOffset(0xFA)]
public byte NumTex;
public byte* ShpkString
=> StringList + ShpkOffset;
public byte* TexString( int idx )
=> StringList + *( TexSpace + 4 + idx * 8 );
public byte* TexString(int idx)
=> StringList + *(TexSpace + 4 + idx * 8);
}

View File

@@ -1,42 +1,41 @@
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Graphics.Render;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
namespace Penumbra.Interop.Structs;
namespace MareSynchronos.Interop;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct RenderModel
{
[FieldOffset( 0x18 )]
[FieldOffset(0x18)]
public RenderModel* PreviousModel;
[FieldOffset( 0x20 )]
[FieldOffset(0x20)]
public RenderModel* NextModel;
[FieldOffset( 0x30 )]
[FieldOffset(0x30)]
public ResourceHandle* ResourceHandle;
[FieldOffset( 0x40 )]
[FieldOffset(0x40)]
public Skeleton* Skeleton;
[FieldOffset( 0x58 )]
[FieldOffset(0x58)]
public void** BoneList;
[FieldOffset( 0x60 )]
[FieldOffset(0x60)]
public int BoneListCount;
[FieldOffset( 0x70 )]
[FieldOffset(0x70)]
private void* UnkDXBuffer1;
[FieldOffset( 0x78 )]
[FieldOffset(0x78)]
private void* UnkDXBuffer2;
[FieldOffset( 0x80 )]
[FieldOffset(0x80)]
private void* UnkDXBuffer3;
[FieldOffset( 0x98 )]
[FieldOffset(0x98)]
public void** Materials;
[FieldOffset( 0xA0 )]
[FieldOffset(0xA0)]
public int MaterialCount;
}

View File

@@ -1,33 +1,32 @@
using System;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.System.Resource;
namespace Penumbra.Interop.Structs;
namespace MareSynchronos.Interop;
[StructLayout( LayoutKind.Explicit )]
[StructLayout(LayoutKind.Explicit)]
public unsafe struct ResourceHandle
{
public const int SsoSize = 15;
public byte* FileName()
{
if( FileNameLength > SsoSize )
if (FileNameLength > SsoSize)
{
return FileNameData;
}
fixed( byte** name = &FileNameData )
fixed (byte** name = &FileNameData)
{
return ( byte* )name;
return (byte*)name;
}
}
[FieldOffset( 0x08 )]
[FieldOffset(0x08)]
public ResourceCategory Category;
[FieldOffset( 0x48 )]
[FieldOffset(0x48)]
public byte* FileNameData;
[FieldOffset( 0x58 )]
[FieldOffset(0x58)]
public int FileNameLength;
}

View File

@@ -1,9 +1,4 @@
using System;
using System.Runtime.InteropServices;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Graphics.Scene;
using FFXIVClientStructs.FFXIV.Client.System.Resource.Handle;
using Penumbra.Interop.Structs;
using System.Runtime.InteropServices;
namespace MareSynchronos.Interop;
@@ -15,24 +10,3 @@ public unsafe struct Weapon
[FieldOffset(0x28)] public IntPtr PreviousSibling;
[FieldOffset(0xA8)] public WeaponDrawObject* WeaponRenderModel;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct WeaponDrawObject
{
[FieldOffset(0x00)] public RenderModel* RenderModel;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct HumanExt
{
[FieldOffset(0x0)] public Human Human;
[FieldOffset(0x9E8)] public Penumbra.Interop.Structs.ResourceHandle* Decal;
[FieldOffset(0x9F0)] public Penumbra.Interop.Structs.ResourceHandle* LegacyBodyDecal;
}
[StructLayout(LayoutKind.Explicit)]
public unsafe struct CharaExt
{
[FieldOffset(0x0)] public Character Character;
[FieldOffset(0x650)] public Character* Mount;
}

View File

@@ -0,0 +1,9 @@
using System.Runtime.InteropServices;
namespace MareSynchronos.Interop;
[StructLayout(LayoutKind.Explicit)]
public unsafe struct WeaponDrawObject
{
[FieldOffset(0x00)] public RenderModel* RenderModel;
}

View File

@@ -1,11 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Logging;
using Dalamud.Logging;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using MareSynchronos.API;
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.API.Dto.User;
using MareSynchronos.FileCache;
using MareSynchronos.Models;
using MareSynchronos.Utils;
@@ -13,21 +10,30 @@ using MareSynchronos.WebAPI;
namespace MareSynchronos.Managers;
public class CachedPlayer
public class CachedPlayer : IDisposable
{
private readonly DalamudUtil _dalamudUtil;
private readonly FileCacheManager fileDbManager;
private readonly IpcManager _ipcManager;
private readonly ApiController _apiController;
private readonly DalamudUtil _dalamudUtil;
private readonly IpcManager _ipcManager;
private readonly FileCacheManager _fileDbManager;
private API.Data.CharacterData _cachedData = new();
private PlayerRelatedObject? _currentCharacterEquipment;
private CancellationTokenSource? _downloadCancellationTokenSource = new();
private bool _isVisible;
public CachedPlayer(string nameHash, IpcManager ipcManager, ApiController apiController, DalamudUtil dalamudUtil, FileCacheManager fileDbManager)
private string _lastGlamourerData = string.Empty;
private string _originalGlamourerData = string.Empty;
private Task? _penumbraRedrawEventTask;
public CachedPlayer(OnlineUserIdentDto onlineUser, IpcManager ipcManager, ApiController apiController, DalamudUtil dalamudUtil, FileCacheManager fileDbManager)
{
PlayerNameHash = nameHash;
OnlineUser = onlineUser;
_ipcManager = ipcManager;
_apiController = apiController;
_dalamudUtil = dalamudUtil;
this.fileDbManager = fileDbManager;
_fileDbManager = fileDbManager;
}
public bool IsVisible
@@ -40,35 +46,24 @@ public class CachedPlayer
}
}
private bool _isDisposed = true;
private CancellationTokenSource? _downloadCancellationTokenSource = new();
private string _lastGlamourerData = string.Empty;
private string _originalGlamourerData = string.Empty;
public OnlineUserIdentDto OnlineUser { get; set; }
public IntPtr PlayerCharacter { get; set; } = IntPtr.Zero;
public string? PlayerName { get; private set; }
public string PlayerNameHash { get; }
public string PlayerNameHash => OnlineUser.Ident;
public bool RequestedPenumbraRedraw { get; set; }
public bool WasVisible { get; private set; }
private CharacterCacheDto _cachedData = new();
private PlayerRelatedObject? _currentCharacterEquipment;
public void ApplyCharacterData(CharacterCacheDto characterData, OptionalPluginWarning warning)
public void ApplyCharacterData(API.Data.CharacterData characterData, OptionalPluginWarning warning)
{
Logger.Debug("Received data for " + this);
Logger.Debug("Checking for files to download for player " + PlayerName);
Logger.Debug("Hash for data is " + characterData.GetHashCode() + ", current cache hash is " + _cachedData.GetHashCode());
Logger.Debug("Hash for data is " + characterData.DataHash.Value + ", current cache hash is " + _cachedData.DataHash.Value);
if (characterData.GetHashCode() == _cachedData.GetHashCode()) return;
if (string.Equals(characterData.DataHash.Value, _cachedData.DataHash.Value, StringComparison.Ordinal)) return;
bool updateModdedPaths = false;
List<ObjectKind> charaDataToUpdate = new();
@@ -122,13 +117,12 @@ public class CachedPlayer
bool heelsOffsetDifferent = _cachedData.HeelsOffset != characterData.HeelsOffset;
if (heelsOffsetDifferent)
{
Logger.Debug("Updating " + objectKind);
charaDataToUpdate.Add(objectKind);
continue;
}
bool customizeDataDifferent = _cachedData.CustomizePlusData != characterData.CustomizePlusData;
bool customizeDataDifferent = !string.Equals(_cachedData.CustomizePlusData, characterData.CustomizePlusData, StringComparison.Ordinal);
if (customizeDataDifferent)
{
Logger.Debug("Updating " + objectKind);
@@ -160,6 +154,184 @@ public class CachedPlayer
DownloadAndApplyCharacter(charaDataToUpdate, updateModdedPaths);
}
public bool CheckExistence()
{
var curPlayerCharacter = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName!)?.Address ?? IntPtr.Zero;
if (PlayerCharacter == IntPtr.Zero || PlayerCharacter != curPlayerCharacter)
{
return false;
}
_currentCharacterEquipment?.CheckAndUpdateObject();
if (_currentCharacterEquipment?.HasUnprocessedUpdate ?? false)
{
OnPlayerChanged();
}
IsVisible = true;
return true;
}
public void Dispose()
{
if (string.IsNullOrEmpty(PlayerName)) return;
Logger.Debug("Disposing " + PlayerName + " (" + OnlineUser + ")");
try
{
Logger.Verbose("Restoring state for " + PlayerName);
_ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent;
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName);
_downloadCancellationTokenSource?.Cancel();
_downloadCancellationTokenSource?.Dispose();
_downloadCancellationTokenSource = null;
if (PlayerCharacter != IntPtr.Zero)
{
foreach (var item in _cachedData.FileReplacements)
{
RevertCustomizationData(item.Key);
}
}
}
catch (Exception ex)
{
Logger.Warn(ex.Message + Environment.NewLine + ex.StackTrace);
}
finally
{
_cachedData = new();
var tempPlayerName = PlayerName;
PlayerName = string.Empty;
PlayerCharacter = IntPtr.Zero;
IsVisible = false;
Logger.Debug("Disposing " + tempPlayerName + " complete");
}
}
public void Initialize(IntPtr character, string name)
{
IsVisible = true;
PlayerName = name;
PlayerCharacter = character;
Logger.Debug("Initializing Player " + this);
_ipcManager.PenumbraRedrawEvent += IpcManagerOnPenumbraRedrawEvent;
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
_currentCharacterEquipment = new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero,
() => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero);
}
public override string ToString()
{
return OnlineUser.User.AliasOrUID + ":" + PlayerName + ":HasChar " + (PlayerCharacter != IntPtr.Zero);
}
private void ApplyBaseData(Dictionary<string, string> moddedPaths)
{
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName!);
_ipcManager.PenumbraSetTemporaryMods(PlayerName!, moddedPaths, _cachedData.ManipulationData);
}
private unsafe void ApplyCustomizationData(ObjectKind objectKind, CancellationToken ct)
{
if (PlayerCharacter == IntPtr.Zero) return;
_cachedData.GlamourerData.TryGetValue(objectKind, out var glamourerData);
switch (objectKind)
{
case ObjectKind.Player:
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, ct);
ct.ThrowIfCancellationRequested();
_ipcManager.HeelsSetOffsetForPlayer(_cachedData.HeelsOffset, PlayerCharacter);
_ipcManager.CustomizePlusSetBodyScale(PlayerCharacter, _cachedData.CustomizePlusData);
RequestedPenumbraRedraw = true;
Logger.Debug(
$"Request Redraw for {PlayerName}");
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, PlayerCharacter);
}
else
{
_ipcManager.PenumbraRedraw(PlayerCharacter);
}
break;
case ObjectKind.MinionOrMount:
{
var minionOrMount = ((Character*)PlayerCharacter)->CompanionObject;
if (minionOrMount != null)
{
Logger.Debug($"Request Redraw for Minion/Mount");
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " minion or mount", (IntPtr)minionOrMount, 10000, ct);
ct.ThrowIfCancellationRequested();
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, (IntPtr)minionOrMount);
}
else
{
_ipcManager.PenumbraRedraw((IntPtr)minionOrMount);
}
}
break;
}
case ObjectKind.Pet:
{
int tick = 16;
var pet = _dalamudUtil.GetPet(PlayerCharacter);
if (pet != IntPtr.Zero)
{
var totalWait = 0;
var newPet = IntPtr.Zero;
const int maxWait = 3000;
Logger.Debug($"Request Redraw for Pet, waiting {maxWait}ms");
do
{
Thread.Sleep(tick);
totalWait += tick;
newPet = _dalamudUtil.GetPet(PlayerCharacter);
} while (newPet == pet && totalWait < maxWait);
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, newPet);
}
else
{
_ipcManager.PenumbraRedraw(newPet);
}
}
break;
}
case ObjectKind.Companion:
{
var companion = _dalamudUtil.GetCompanion(PlayerCharacter);
if (companion != IntPtr.Zero)
{
Logger.Debug("Request Redraw for Companion");
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " companion", companion, 10000, ct);
ct.ThrowIfCancellationRequested();
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, companion);
}
else
{
_ipcManager.PenumbraRedraw(companion);
}
}
break;
}
}
}
private void DownloadAndApplyCharacter(List<ObjectKind> objectKind, bool updateModdedPaths)
{
if (!objectKind.Any())
@@ -175,7 +347,7 @@ public class CachedPlayer
var downloadId = _apiController.GetDownloadId();
Task.Run(async () =>
{
List<FileReplacementDto> toDownloadReplacements;
List<FileReplacementData> toDownloadReplacements;
if (updateModdedPaths)
{
@@ -213,7 +385,6 @@ public class CachedPlayer
{
ApplyCustomizationData(kind, downloadToken);
}
}, downloadToken).ContinueWith(task =>
{
_downloadCancellationTokenSource = null;
@@ -225,138 +396,44 @@ public class CachedPlayer
});
}
private List<FileReplacementDto> TryCalculateModdedDictionary(out Dictionary<string, string> moddedDictionary)
private void IpcManagerOnPenumbraRedrawEvent(IntPtr address, int idx)
{
List<FileReplacementDto> missingFiles = new();
moddedDictionary = new Dictionary<string, string>(StringComparer.Ordinal);
try
var player = _dalamudUtil.GetCharacterFromObjectTableByIndex(idx);
if (player == null || !string.Equals(player.Name.ToString(), PlayerName, StringComparison.OrdinalIgnoreCase)) return;
if (!_penumbraRedrawEventTask?.IsCompleted ?? false) return;
_penumbraRedrawEventTask = Task.Run(() =>
{
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => string.IsNullOrEmpty(v.FileSwapPath))).ToList())
PlayerCharacter = address;
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, cts.Token);
cts.Dispose();
cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
if (RequestedPenumbraRedraw == false)
{
foreach (var gamePath in item.GamePaths)
{
var fileCache = fileDbManager.GetFileCacheByHash(item.Hash);
if (fileCache != null)
{
moddedDictionary[gamePath] = fileCache.ResolvedFilepath;
Logger.Debug("Unauthorized character change detected");
ApplyCustomizationData(ObjectKind.Player, cts.Token);
}
else
{
Logger.Verbose("Missing file: " + item.Hash);
missingFiles.Add(item);
}
}
}
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => !string.IsNullOrEmpty(v.FileSwapPath))).ToList())
{
foreach (var gamePath in item.GamePaths)
{
Logger.Verbose("Adding file swap for " + gamePath + ":" + item.FileSwapPath);
moddedDictionary[gamePath] = item.FileSwapPath;
}
}
}
catch (Exception ex)
{
PluginLog.Error(ex, "Something went wrong during calculation replacements");
}
Logger.Debug("ModdedPaths calculated, missing files: " + missingFiles.Count);
return missingFiles;
}
private void ApplyBaseData(Dictionary<string, string> moddedPaths)
{
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName!);
_ipcManager.PenumbraSetTemporaryMods(PlayerName!, moddedPaths, _cachedData.ManipulationData);
}
private unsafe void ApplyCustomizationData(ObjectKind objectKind, CancellationToken ct)
{
if (PlayerCharacter == IntPtr.Zero) return;
_cachedData.GlamourerData.TryGetValue(objectKind, out var glamourerData);
if (objectKind == ObjectKind.Player)
{
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, ct);
ct.ThrowIfCancellationRequested();
_ipcManager.HeelsSetOffsetForPlayer(_cachedData.HeelsOffset, PlayerCharacter);
_ipcManager.CustomizePlusSetBodyScale(PlayerCharacter, _cachedData.CustomizePlusData);
RequestedPenumbraRedraw = true;
RequestedPenumbraRedraw = false;
Logger.Debug(
$"Request Redraw for {PlayerName}");
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, PlayerCharacter);
$"Penumbra Redraw done for {PlayerName}");
}
else
{
_ipcManager.PenumbraRedraw(PlayerCharacter);
cts.Dispose();
});
}
}
else if (objectKind == ObjectKind.MinionOrMount)
{
var minionOrMount = ((Character*)PlayerCharacter)->CompanionObject;
if (minionOrMount != null)
{
Logger.Debug($"Request Redraw for Minion/Mount");
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " minion or mount", (IntPtr)minionOrMount, 10000, ct);
ct.ThrowIfCancellationRequested();
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, (IntPtr)minionOrMount);
}
else
{
_ipcManager.PenumbraRedraw((IntPtr)minionOrMount);
}
}
}
else if (objectKind == ObjectKind.Pet)
{
int tick = 16;
var pet = _dalamudUtil.GetPet(PlayerCharacter);
if (pet != IntPtr.Zero)
{
var totalWait = 0;
var newPet = IntPtr.Zero;
const int maxWait = 3000;
Logger.Debug($"Request Redraw for Pet, waiting {maxWait}ms");
do
private void OnPlayerChanged()
{
Thread.Sleep(tick);
totalWait += tick;
newPet = _dalamudUtil.GetPet(PlayerCharacter);
} while (newPet == pet && totalWait < maxWait);
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}");
_currentCharacterEquipment!.HasUnprocessedUpdate = false;
if (!RequestedPenumbraRedraw && PlayerCharacter != IntPtr.Zero)
{
_ipcManager.GlamourerApplyAll(glamourerData, newPet);
}
else
{
_ipcManager.PenumbraRedraw(newPet);
}
}
}
else if (objectKind == ObjectKind.Companion)
{
var companion = _dalamudUtil.GetCompanion(PlayerCharacter);
if (companion != IntPtr.Zero)
{
Logger.Debug("Request Redraw for Companion");
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName! + " companion", companion, 10000, ct);
ct.ThrowIfCancellationRequested();
if (_ipcManager.CheckGlamourerApi() && !string.IsNullOrEmpty(glamourerData))
{
_ipcManager.GlamourerApplyAll(glamourerData, companion);
}
else
{
_ipcManager.PenumbraRedraw(companion);
}
}
Logger.Debug($"Saving new Glamourer data");
_lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
}
}
@@ -404,129 +481,43 @@ public class CachedPlayer
}
}
public void DisposePlayer()
private List<FileReplacementData> TryCalculateModdedDictionary(out Dictionary<string, string> moddedDictionary)
{
if (_isDisposed) return;
if (string.IsNullOrEmpty(PlayerName)) return;
Logger.Debug("Disposing " + PlayerName + " (" + PlayerNameHash + ")");
_isDisposed = true;
List<FileReplacementData> missingFiles = new();
moddedDictionary = new Dictionary<string, string>(StringComparer.Ordinal);
try
{
Logger.Verbose("Restoring state for " + PlayerName);
_dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate;
_ipcManager.PenumbraRedrawEvent -= IpcManagerOnPenumbraRedrawEvent;
_ipcManager.PenumbraRemoveTemporaryCollection(PlayerName);
_downloadCancellationTokenSource?.Cancel();
_downloadCancellationTokenSource?.Dispose();
_downloadCancellationTokenSource = null;
if (PlayerCharacter != IntPtr.Zero)
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => string.IsNullOrEmpty(v.FileSwapPath))).ToList())
{
foreach (var item in _cachedData.FileReplacements)
foreach (var gamePath in item.GamePaths)
{
RevertCustomizationData(item.Key);
var fileCache = _fileDbManager.GetFileCacheByHash(item.Hash);
if (fileCache != null)
{
moddedDictionary[gamePath] = fileCache.ResolvedFilepath;
}
else
{
Logger.Verbose("Missing file: " + item.Hash);
missingFiles.Add(item);
}
}
}
foreach (var item in _cachedData.FileReplacements.SelectMany(k => k.Value.Where(v => !string.IsNullOrEmpty(v.FileSwapPath))).ToList())
{
foreach (var gamePath in item.GamePaths)
{
Logger.Verbose("Adding file swap for " + gamePath + ":" + item.FileSwapPath);
moddedDictionary[gamePath] = item.FileSwapPath;
}
}
}
catch (Exception ex)
{
Logger.Warn(ex.Message + Environment.NewLine + ex.StackTrace);
}
finally
{
_cachedData = new();
var tempPlayerName = PlayerName;
PlayerName = string.Empty;
PlayerCharacter = IntPtr.Zero;
IsVisible = false;
Logger.Debug("Disposing " + tempPlayerName + " complete");
}
}
public void InitializePlayer(IntPtr character, string name, CharacterCacheDto? cache, OptionalPluginWarning displayedChatWarning)
{
if (!_isDisposed) return;
IsVisible = true;
PlayerName = name;
PlayerCharacter = character;
Logger.Debug("Initializing Player " + this + " has cache: " + (cache != null));
_dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate;
_ipcManager.PenumbraRedrawEvent += IpcManagerOnPenumbraRedrawEvent;
_originalGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
_currentCharacterEquipment = new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero,
() => _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName)?.Address ?? IntPtr.Zero);
_isDisposed = false;
if (cache != null)
{
ApplyCharacterData(cache, displayedChatWarning);
}
}
private void DalamudUtilOnDelayedFrameworkUpdate()
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized || !_apiController.IsConnected) return;
var curPlayerCharacter = _dalamudUtil.GetPlayerCharacterFromObjectTableByName(PlayerName!)?.Address ?? IntPtr.Zero;
if (PlayerCharacter == IntPtr.Zero || PlayerCharacter != curPlayerCharacter)
{
DisposePlayer();
return;
}
_currentCharacterEquipment?.CheckAndUpdateObject();
if (_currentCharacterEquipment?.HasUnprocessedUpdate ?? false)
{
OnPlayerChanged();
}
IsVisible = true;
}
public override string ToString()
{
return PlayerNameHash + ":" + PlayerName + ":HasChar " + (PlayerCharacter != IntPtr.Zero);
}
private Task? _penumbraRedrawEventTask;
private void IpcManagerOnPenumbraRedrawEvent(IntPtr address, int idx)
{
var player = _dalamudUtil.GetCharacterFromObjectTableByIndex(idx);
if (player == null || !string.Equals(player.Name.ToString(), PlayerName, StringComparison.OrdinalIgnoreCase)) return;
if (!_penumbraRedrawEventTask?.IsCompleted ?? false) return;
_penumbraRedrawEventTask = Task.Run(() =>
{
PlayerCharacter = address;
var cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
_dalamudUtil.WaitWhileCharacterIsDrawing(PlayerName!, PlayerCharacter, 10000, cts.Token);
cts.Dispose();
cts = new CancellationTokenSource();
cts.CancelAfter(TimeSpan.FromSeconds(5));
if (RequestedPenumbraRedraw == false)
{
Logger.Debug("Unauthorized character change detected");
ApplyCustomizationData(ObjectKind.Player, cts.Token);
}
else
{
RequestedPenumbraRedraw = false;
Logger.Debug(
$"Penumbra Redraw done for {PlayerName}");
}
cts.Dispose();
});
}
private void OnPlayerChanged()
{
Logger.Debug($"Player {PlayerName} changed, PenumbraRedraw is {RequestedPenumbraRedraw}");
_currentCharacterEquipment!.HasUnprocessedUpdate = false;
if (!RequestedPenumbraRedraw && PlayerCharacter != IntPtr.Zero)
{
Logger.Debug($"Saving new Glamourer data");
_lastGlamourerData = _ipcManager.GlamourerGetCharacterCustomization(PlayerCharacter);
PluginLog.Error(ex, "Something went wrong during calculation replacements");
}
Logger.Debug("ModdedPaths calculated, missing files: " + missingFiles.Count);
return missingFiles;
}
}

View File

@@ -1,7 +1,5 @@
using Dalamud.Plugin;
using Dalamud.Plugin.Ipc;
using System;
using System.Collections.Generic;
using Dalamud.Game.ClientState.Objects.Types;
using MareSynchronos.Utils;
using Action = System.Action;
@@ -9,14 +7,11 @@ using System.Collections.Concurrent;
using System.Text;
using Penumbra.Api.Enums;
using Penumbra.Api.Helpers;
using System.Threading.Tasks;
using MareSynchronos.Delegates;
namespace MareSynchronos.Managers;
public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx);
public delegate void HeelsOffsetChange(float change);
public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath);
public delegate void CustomizePlusScaleChange(string? scale);
public class IpcManager : IDisposable
{
private readonly ICallGateSubscriber<int> _glamourerApiVersion;
@@ -57,10 +52,10 @@ public class IpcManager : IDisposable
private readonly ICallGateSubscriber<string?, object> _customizePlusOnScaleUpdate;
private readonly DalamudUtil _dalamudUtil;
private bool inGposeQueueMode = false;
private ConcurrentQueue<Action> actionQueue => inGposeQueueMode ? gposeActionQueue : normalQueue;
private readonly ConcurrentQueue<Action> normalQueue = new();
private readonly ConcurrentQueue<Action> gposeActionQueue = new();
private bool _inGposeQueueMode = false;
private ConcurrentQueue<Action> ActionQueue => _inGposeQueueMode ? _gposeActionQueue : _normalQueue;
private readonly ConcurrentQueue<Action> _normalQueue = new();
private readonly ConcurrentQueue<Action> _gposeActionQueue = new();
public IpcManager(DalamudPluginInterface pi, DalamudUtil dalamudUtil)
{
@@ -121,7 +116,7 @@ public class IpcManager : IDisposable
private void HandleGposeActionQueue()
{
if (gposeActionQueue.TryDequeue(out var action))
if (_gposeActionQueue.TryDequeue(out var action))
{
if (action == null) return;
Logger.Debug("Execution action in gpose queue: " + action.Method);
@@ -131,7 +126,7 @@ public class IpcManager : IDisposable
public void ToggleGposeQueueMode(bool on)
{
inGposeQueueMode = on;
_inGposeQueueMode = on;
}
private void PenumbraModSettingChangedHandler()
@@ -141,15 +136,15 @@ public class IpcManager : IDisposable
private void ClearActionQueue()
{
actionQueue.Clear();
gposeActionQueue.Clear();
ActionQueue.Clear();
_gposeActionQueue.Clear();
}
private void ResourceLoaded(IntPtr ptr, string arg1, string arg2)
{
Task.Run(() =>
{
if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, true, System.Globalization.CultureInfo.InvariantCulture) != 0)
if (ptr != IntPtr.Zero && string.Compare(arg1, arg2, ignoreCase: true, System.Globalization.CultureInfo.InvariantCulture) != 0)
{
PenumbraResourceLoadEvent?.Invoke(ptr, arg1, arg2);
}
@@ -158,7 +153,7 @@ public class IpcManager : IDisposable
private void HandleActionQueue()
{
if (actionQueue.TryDequeue(out var action))
if (ActionQueue.TryDequeue(out var action))
{
if (action == null) return;
Logger.Debug("Execution action in queue: " + action.Method);
@@ -169,10 +164,10 @@ public class IpcManager : IDisposable
public event VoidDelegate? PenumbraModSettingChanged;
public event VoidDelegate? PenumbraInitialized;
public event VoidDelegate? PenumbraDisposed;
public event PenumbraRedrawEvent? PenumbraRedrawEvent;
public event HeelsOffsetChange? HeelsOffsetChangeEvent;
public event PenumbraResourceLoadEvent? PenumbraResourceLoadEvent;
public event CustomizePlusScaleChange? CustomizePlusScaleChange;
public event DrawObjectDelegate? PenumbraRedrawEvent;
public event FloatDelegate? HeelsOffsetChangeEvent;
public event PenumbraFileResourceDelegate? PenumbraResourceLoadEvent;
public event StringDelegate? CustomizePlusScaleChange;
public bool Initialized => CheckPenumbraApi();
public bool CheckGlamourerApi()
@@ -228,11 +223,11 @@ public class IpcManager : IDisposable
Logger.Verbose("Disposing " + nameof(IpcManager));
int totalSleepTime = 0;
while (actionQueue.Count > 0 && totalSleepTime < 2000)
while (!ActionQueue.IsEmpty && totalSleepTime < 2000)
{
Logger.Verbose("Waiting for actionqueue to clear...");
HandleActionQueue();
System.Threading.Thread.Sleep(16);
Thread.Sleep(16);
totalSleepTime += 16;
}
@@ -244,7 +239,7 @@ public class IpcManager : IDisposable
_dalamudUtil.FrameworkUpdate -= HandleActionQueue;
_dalamudUtil.ZoneSwitchEnd -= ClearActionQueue;
_dalamudUtil.GposeFrameworkUpdate -= HandleGposeActionQueue;
actionQueue.Clear();
ActionQueue.Clear();
_penumbraGameObjectResourcePathResolved.Dispose();
_penumbraDispose.Dispose();
@@ -263,7 +258,7 @@ public class IpcManager : IDisposable
public void HeelsSetOffsetForPlayer(float offset, IntPtr character)
{
if (!CheckHeelsApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj != null)
@@ -277,7 +272,7 @@ public class IpcManager : IDisposable
public void HeelsRestoreOffsetForPlayer(IntPtr character)
{
if (!CheckHeelsApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj != null)
@@ -299,7 +294,7 @@ public class IpcManager : IDisposable
public void CustomizePlusSetBodyScale(IntPtr character, string scale)
{
if (!CheckCustomizePlusApi() || string.IsNullOrEmpty(scale)) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
@@ -314,7 +309,7 @@ public class IpcManager : IDisposable
public void CustomizePlusRevert(IntPtr character)
{
if (!CheckCustomizePlusApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
@@ -328,7 +323,7 @@ public class IpcManager : IDisposable
public void GlamourerApplyAll(string? customization, IntPtr obj)
{
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(obj);
if (gameObj is Character c)
@@ -342,7 +337,7 @@ public class IpcManager : IDisposable
public void GlamourerApplyOnlyEquipment(string customization, IntPtr character)
{
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
@@ -356,7 +351,7 @@ public class IpcManager : IDisposable
public void GlamourerApplyOnlyCustomization(string customization, IntPtr character)
{
if (!CheckGlamourerApi() || string.IsNullOrEmpty(customization)) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(character);
if (gameObj is Character c)
@@ -393,7 +388,7 @@ public class IpcManager : IDisposable
public void GlamourerRevertCharacterCustomization(GameObject character)
{
if (!CheckGlamourerApi()) return;
actionQueue.Enqueue(() => _glamourerRevertCustomization!.InvokeAction(character));
ActionQueue.Enqueue(() => _glamourerRevertCustomization!.InvokeAction(character));
}
public string PenumbraGetMetaManipulations()
@@ -411,7 +406,7 @@ public class IpcManager : IDisposable
public void PenumbraRedraw(IntPtr obj)
{
if (!CheckPenumbraApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var gameObj = _dalamudUtil.CreateGameObject(obj);
if (gameObj != null)
@@ -425,13 +420,13 @@ public class IpcManager : IDisposable
public void PenumbraRedraw(string actorName)
{
if (!CheckPenumbraApi()) return;
actionQueue.Enqueue(() => _penumbraRedraw!.Invoke(actorName, RedrawType.Redraw));
ActionQueue.Enqueue(() => _penumbraRedraw!.Invoke(actorName, RedrawType.Redraw));
}
public void PenumbraRemoveTemporaryCollection(string characterName)
{
if (!CheckPenumbraApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var collName = "Mare_" + characterName;
Logger.Verbose("Removing temp collection for " + collName);
@@ -464,7 +459,7 @@ public class IpcManager : IDisposable
{
if (!CheckPenumbraApi()) return;
actionQueue.Enqueue(() =>
ActionQueue.Enqueue(() =>
{
var idx = _dalamudUtil.GetIndexFromObjectTableByName(characterName);
if (idx == null)
@@ -474,7 +469,7 @@ public class IpcManager : IDisposable
var collName = "Mare_" + characterName;
var ret = _penumbraCreateNamedTemporaryCollection.Invoke(collName);
Logger.Verbose("Creating Temp Collection " + collName + ", Success: " + ret);
var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx.Value, true);
var retAssign = _penumbraAssignTemporaryCollection.Invoke(collName, idx.Value, c: true);
Logger.Verbose("Assigning Temp Collection " + collName + " to index " + idx.Value);
foreach (var mod in modPaths)
{
@@ -511,6 +506,6 @@ public class IpcManager : IDisposable
private void PenumbraDispose()
{
PenumbraDisposed?.Invoke();
actionQueue.Clear();
ActionQueue.Clear();
}
}

View File

@@ -1,15 +1,8 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.API.Data;
using MareSynchronos.API.Dto.User;
using MareSynchronos.FileCache;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using MareSynchronos.WebAPI.Utils;
namespace MareSynchronos.Managers;
@@ -17,39 +10,24 @@ public class OnlinePlayerManager : IDisposable
{
private readonly ApiController _apiController;
private readonly DalamudUtil _dalamudUtil;
private readonly IpcManager _ipcManager;
private readonly PlayerManager _playerManager;
private readonly FileCacheManager _fileDbManager;
private readonly Configuration _configuration;
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 readonly ConcurrentDictionary<string, OptionalPluginWarning> _shownWarnings = new(StringComparer.Ordinal);
private readonly PairManager _pairManager;
private List<string> OnlineVisiblePlayerHashes => _onlineCachedPlayers.Select(p => p.Value).Where(p => p.PlayerCharacter != IntPtr.Zero)
.Select(p => p.PlayerNameHash).ToList();
public OnlinePlayerManager(ApiController apiController, DalamudUtil dalamudUtil, IpcManager ipcManager, PlayerManager playerManager, FileCacheManager fileDbManager, Configuration configuration)
public OnlinePlayerManager(ApiController apiController, DalamudUtil dalamudUtil, PlayerManager playerManager, FileCacheManager fileDbManager, PairManager pairManager)
{
Logger.Verbose("Creating " + nameof(OnlinePlayerManager));
_apiController = apiController;
_dalamudUtil = dalamudUtil;
_ipcManager = ipcManager;
_playerManager = playerManager;
_fileDbManager = fileDbManager;
_configuration = configuration;
_apiController.PairedClientOnline += ApiControllerOnPairedClientOnline;
_apiController.PairedClientOffline += ApiControllerOnPairedClientOffline;
_apiController.Connected += ApiControllerOnConnected;
_apiController.Disconnected += ApiControllerOnDisconnected;
_apiController.CharacterReceived += ApiControllerOnCharacterReceived;
_pairManager = pairManager;
_ipcManager.PenumbraDisposed += IpcManagerOnPenumbraDisposed;
_playerManager.PlayerHasChanged += PlayerManagerOnPlayerHasChanged;
_dalamudUtil.LogIn += DalamudUtilOnLogIn;
_dalamudUtil.LogOut += DalamudUtilOnLogOut;
_dalamudUtil.ZoneSwitchStart += DalamudUtilOnZoneSwitched;
if (_dalamudUtil.IsLoggedIn)
{
@@ -57,49 +35,9 @@ public class OnlinePlayerManager : IDisposable
}
}
private void DalamudUtilOnZoneSwitched()
private void PlayerManagerOnPlayerHasChanged(CharacterData characterCache)
{
DisposePlayers();
}
private void ApiControllerOnCharacterReceived(object? sender, CharacterReceivedEventArgs e)
{
if (!_shownWarnings.ContainsKey(e.CharacterNameHash)) _shownWarnings[e.CharacterNameHash] = new()
{
ShownCustomizePlusWarning = _configuration.DisableOptionalPluginWarnings,
ShownHeelsWarning = _configuration.DisableOptionalPluginWarnings,
};
if (_onlineCachedPlayers.TryGetValue(e.CharacterNameHash, out var visiblePlayer) && visiblePlayer.IsVisible)
{
Logger.Debug("Received data and applying to " + e.CharacterNameHash);
visiblePlayer.ApplyCharacterData(e.CharacterData, _shownWarnings[e.CharacterNameHash]);
}
else
{
Logger.Debug("Received data but no fitting character visible for " + e.CharacterNameHash);
_temporaryStoredCharacterCache[e.CharacterNameHash] = e.CharacterData;
}
}
private void PlayerManagerOnPlayerHasChanged(CharacterCacheDto characterCache)
{
PushCharacterData(OnlineVisiblePlayerHashes);
}
private void ApiControllerOnConnected()
{
var apiTask = _apiController.UserGetOnlineCharacters();
Task.WaitAll(apiTask);
AddInitialPairs(apiTask.Result);
_playerManager.PlayerHasChanged += PlayerManagerOnPlayerHasChanged;
}
private void DalamudUtilOnLogOut()
{
_dalamudUtil.DelayedFrameworkUpdate -= FrameworkOnUpdate;
PushCharacterData(_pairManager.VisibleUsers);
}
private void DalamudUtilOnLogIn()
@@ -107,146 +45,53 @@ public class OnlinePlayerManager : IDisposable
_dalamudUtil.DelayedFrameworkUpdate += FrameworkOnUpdate;
}
private void IpcManagerOnPenumbraDisposed()
private void DalamudUtilOnLogOut()
{
DisposePlayers();
}
private void DisposePlayers()
{
foreach (var kvp in _onlineCachedPlayers)
{
kvp.Value.DisposePlayer();
}
}
private void ApiControllerOnDisconnected()
{
RestoreAllCharacters();
_playerManager.PlayerHasChanged -= PlayerManagerOnPlayerHasChanged;
}
public void AddInitialPairs(List<string> apiTaskResult)
{
_onlineCachedPlayers.Clear();
foreach (var hash in apiTaskResult)
{
_onlineCachedPlayers.TryAdd(hash, CreateCachedPlayer(hash));
}
Logger.Verbose("Online and paired users: " + string.Join(Environment.NewLine, _onlineCachedPlayers.Select(k => k.Key)));
_dalamudUtil.DelayedFrameworkUpdate -= FrameworkOnUpdate;
}
public void Dispose()
{
Logger.Verbose("Disposing " + nameof(OnlinePlayerManager));
RestoreAllCharacters();
_apiController.PairedClientOnline -= ApiControllerOnPairedClientOnline;
_apiController.PairedClientOffline -= ApiControllerOnPairedClientOffline;
_apiController.Disconnected -= ApiControllerOnDisconnected;
_apiController.Connected -= ApiControllerOnConnected;
_ipcManager.PenumbraDisposed -= ApiControllerOnDisconnected;
_playerManager.PlayerHasChanged -= PlayerManagerOnPlayerHasChanged;
_dalamudUtil.LogIn -= DalamudUtilOnLogIn;
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
_dalamudUtil.ZoneSwitchStart -= DalamudUtilOnZoneSwitched;
_dalamudUtil.DelayedFrameworkUpdate -= FrameworkOnUpdate;
}
private void RestoreAllCharacters()
{
DisposePlayers();
_onlineCachedPlayers.Clear();
}
private void ApiControllerOnPairedClientOffline(string charHash)
{
Logger.Debug("Player offline: " + charHash);
RemovePlayer(charHash);
}
private void ApiControllerOnPairedClientOnline(string charHash)
{
Logger.Debug("Player online: " + charHash);
AddPlayer(charHash);
return;
}
private void AddPlayer(string characterNameHash)
{
if (_onlineCachedPlayers.TryGetValue(characterNameHash, out var cachedPlayer))
{
PushCharacterData(new List<string>() { characterNameHash });
_playerTokenDisposal.TryGetValue(cachedPlayer, out var cancellationTokenSource);
cancellationTokenSource?.Cancel();
return;
}
_onlineCachedPlayers.TryAdd(characterNameHash, CreateCachedPlayer(characterNameHash));
}
private void RemovePlayer(string characterHash)
{
if (!_onlineCachedPlayers.TryGetValue(characterHash, out var cachedPlayer))
{
return;
}
cachedPlayer.DisposePlayer();
_onlineCachedPlayers.TryRemove(characterHash, out _);
}
private void FrameworkOnUpdate()
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized || !_apiController.IsConnected) return;
if (!_dalamudUtil.IsPlayerPresent || !_apiController.IsConnected) return;
var playerCharacters = _dalamudUtil.GetPlayerCharacters();
var onlinePairs = _pairManager.OnlineUserPairs;
foreach (var pChar in playerCharacters)
{
var hashedName = Crypto.GetHash256(pChar);
if (_onlineCachedPlayers.TryGetValue(hashedName, out var existingPlayer) && !string.IsNullOrEmpty(existingPlayer.PlayerName))
{
existingPlayer.IsVisible = true;
continue;
var pair = _pairManager.FindPair(pChar);
if (pair == null) continue;
pair.InitializePair(pChar.Address, pChar.Name.ToString());
}
if (existingPlayer != null)
{
_temporaryStoredCharacterCache.TryRemove(hashedName, out var cache);
if (!_shownWarnings.ContainsKey(hashedName)) _shownWarnings[hashedName] = new()
{
ShownCustomizePlusWarning = _configuration.DisableOptionalPluginWarnings,
ShownHeelsWarning = _configuration.DisableOptionalPluginWarnings,
};
existingPlayer.InitializePlayer(pChar.Address, pChar.Name.ToString(), cache, _shownWarnings[hashedName]);
}
}
var newlyVisiblePlayers = _onlineCachedPlayers.Select(v => v.Value)
.Where(p => p.PlayerCharacter != IntPtr.Zero && p.IsVisible && !p.WasVisible).Select(p => p.PlayerNameHash)
var newlyVisiblePlayers = onlinePairs.Select(v => v.CachedPlayer)
.Where(p => p != null && p.PlayerCharacter != IntPtr.Zero && p.IsVisible && !p.WasVisible).Select(p => (UserDto)p!.OnlineUser)
.ToList();
if (newlyVisiblePlayers.Any())
{
Logger.Verbose("Has new visible players, pushing character data");
PushCharacterData(newlyVisiblePlayers);
PushCharacterData(newlyVisiblePlayers.Select(c => c.User).ToList());
}
}
private void PushCharacterData(List<string> visiblePlayers)
private void PushCharacterData(List<UserData> visiblePlayers)
{
if (visiblePlayers.Any() && _playerManager.LastCreatedCharacterData != null)
{
Task.Run(async () =>
{
await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData,
visiblePlayers).ConfigureAwait(false);
await _apiController.PushCharacterData(_playerManager.LastCreatedCharacterData, visiblePlayers).ConfigureAwait(false);
});
}
}
private CachedPlayer CreateCachedPlayer(string hashedName)
{
return new CachedPlayer(hashedName, _ipcManager, _apiController, _dalamudUtil, _fileDbManager);
}
}

View File

@@ -0,0 +1,329 @@
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Interface;
using Dalamud.Utility;
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Comparer;
using MareSynchronos.API.Data.Extensions;
using MareSynchronos.API.Dto.Group;
using MareSynchronos.API.Dto.User;
using MareSynchronos.Factories;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using System.Collections.Concurrent;
namespace MareSynchronos.Managers;
public class PairManager : IDisposable
{
private readonly ConcurrentDictionary<UserData, Pair> _allClientPairs = new(UserDataComparer.Instance);
private readonly ConcurrentDictionary<GroupData, GroupFullInfoDto> _allGroups = new(GroupDataComparer.Instance);
private readonly CachedPlayerFactory _cachedPlayerFactory;
private readonly DalamudUtil _dalamudUtil;
private readonly PairFactory _pairFactory;
private readonly UiBuilder _uiBuilder;
private readonly ConfigurationService _configurationService;
public PairManager(CachedPlayerFactory cachedPlayerFactory, DalamudUtil dalamudUtil, PairFactory pairFactory, UiBuilder uiBuilder, ConfigurationService configurationService)
{
_cachedPlayerFactory = cachedPlayerFactory;
_dalamudUtil = dalamudUtil;
_pairFactory = pairFactory;
_uiBuilder = uiBuilder;
_configurationService = configurationService;
_dalamudUtil.ZoneSwitchStart += DalamudUtilOnZoneSwitched;
_dalamudUtil.DelayedFrameworkUpdate += DalamudUtilOnDelayedFrameworkUpdate;
_directPairsInternal = DirectPairsLazy();
_groupPairsInternal = GroupPairsLazy();
}
private void RecreateLazy()
{
_directPairsInternal = DirectPairsLazy();
_groupPairsInternal = GroupPairsLazy();
}
private Lazy<List<Pair>> _directPairsInternal;
private Lazy<Dictionary<GroupFullInfoDto, List<Pair>>> _groupPairsInternal;
public Dictionary<GroupFullInfoDto, List<Pair>> GroupPairs => _groupPairsInternal.Value;
public List<Pair> DirectPairs => _directPairsInternal.Value;
private Lazy<List<Pair>> DirectPairsLazy() => new(() => _allClientPairs.Select(k => k.Value).Where(k => k.UserPair != null).ToList());
private Lazy<Dictionary<GroupFullInfoDto, List<Pair>>> GroupPairsLazy()
{
return new Lazy<Dictionary<GroupFullInfoDto, List<Pair>>>(() =>
{
Dictionary<GroupFullInfoDto, List<Pair>> outDict = new();
foreach (var group in _allGroups)
{
outDict[group.Value] = _allClientPairs.Select(p => p.Value).Where(p => p.GroupPair.Any(g => GroupDataComparer.Instance.Equals(group.Key, g.Key.Group))).ToList();
}
return outDict;
});
}
public List<Pair> OnlineUserPairs => _allClientPairs.Where(p => !string.IsNullOrEmpty(p.Value.PlayerNameHash)).Select(p => p.Value).ToList();
public List<UserData> VisibleUsers => _allClientPairs.Where(p => p.Value.CachedPlayer != null && p.Value.CachedPlayer.IsVisible).Select(p => p.Key).ToList();
public void AddGroup(GroupFullInfoDto dto)
{
_allGroups[dto.Group] = dto;
RecreateLazy();
}
public void RemoveGroup(GroupData data)
{
_allGroups.TryRemove(data, out _);
foreach (var item in _allClientPairs.ToList())
{
foreach (var grpPair in item.Value.GroupPair.Select(k => k.Key).ToList())
{
if (GroupDataComparer.Instance.Equals(grpPair.Group, data))
{
_allClientPairs[item.Key].GroupPair.Remove(grpPair);
}
}
if (!_allClientPairs[item.Key].HasAnyConnection())
{
_allClientPairs.TryRemove(item.Key, out _);
}
}
RecreateLazy();
}
public void AddGroupPair(GroupPairFullInfoDto dto)
{
if (!_allClientPairs.ContainsKey(dto.User)) _allClientPairs[dto.User] = _pairFactory.Create();
var group = _allGroups[dto.Group];
_allClientPairs[dto.User].GroupPair[group] = dto;
RecreateLazy();
}
public void AddUserPair(UserPairDto dto)
{
if (!_allClientPairs.ContainsKey(dto.User)) _allClientPairs[dto.User] = _pairFactory.Create();
_allClientPairs[dto.User].UserPair = dto;
_allClientPairs[dto.User].ApplyLastReceivedData();
RecreateLazy();
}
public void ClearPairs()
{
Logger.Debug("Clearing all Pairs");
DisposePairs();
_allClientPairs.Clear();
_allGroups.Clear();
RecreateLazy();
}
public void Dispose()
{
_dalamudUtil.DelayedFrameworkUpdate -= DalamudUtilOnDelayedFrameworkUpdate;
_dalamudUtil.ZoneSwitchStart -= DalamudUtilOnZoneSwitched;
DisposePairs();
}
public void DisposePairs()
{
Logger.Debug("Disposing all Pairs");
foreach (var item in _allClientPairs)
{
item.Value.CachedPlayer?.Dispose();
}
}
public Pair? FindPair(PlayerCharacter? pChar)
{
if (pChar == null) return null;
var hash = pChar.GetHash256();
return OnlineUserPairs.Find(p => string.Equals(p.PlayerNameHash, hash, StringComparison.Ordinal));
}
public void MarkPairOffline(UserData user)
{
if (_allClientPairs.TryGetValue(user, out var pair))
{
pair.CachedPlayer?.Dispose();
pair.CachedPlayer = null;
RecreateLazy();
}
}
public void MarkPairOnline(OnlineUserIdentDto dto, ApiController controller)
{
if (!_allClientPairs.ContainsKey(dto.User)) throw new InvalidOperationException("No user found for " + dto);
if (_allClientPairs[dto.User].CachedPlayer != null) return;
if (_configurationService.Current.ShowOnlineNotifications)
{
var pair = _allClientPairs[dto.User];
if (_configurationService.Current.ShowOnlineNotificationsOnlyForIndividualPairs && pair.UserPair != null || !_configurationService.Current.ShowOnlineNotificationsOnlyForIndividualPairs)
{
_uiBuilder.AddNotification(string.Empty, "[Mare Synchronos] " + (pair.GetNote() ?? pair.UserData.AliasOrUID) + " is now online", Dalamud.Interface.Internal.Notifications.NotificationType.Info, 5000);
}
}
_allClientPairs[dto.User].CachedPlayer?.Dispose();
_allClientPairs[dto.User].CachedPlayer = _cachedPlayerFactory.Create(dto, controller);
RecreateLazy();
}
public void ReceiveCharaData(OnlineUserCharaDataDto dto)
{
if (!_allClientPairs.ContainsKey(dto.User)) throw new InvalidOperationException("No user found for " + dto.User);
var pair = _allClientPairs[dto.User];
if (!pair.PlayerName.IsNullOrEmpty())
{
pair.ApplyData(dto);
}
else
{
_allClientPairs[dto.User].LastReceivedCharacterData = dto.CharaData;
}
}
public void RemoveGroupPair(GroupPairDto dto)
{
if (_allClientPairs.TryGetValue(dto.User, out var pair))
{
var group = _allGroups[dto.Group];
pair.GroupPair.Remove(group);
if (!pair.HasAnyConnection())
{
_allClientPairs.TryRemove(dto.User, out _);
}
RecreateLazy();
}
}
public void RemoveUserPair(UserDto dto)
{
if (_allClientPairs.TryGetValue(dto.User, out var pair))
{
pair.UserPair = null;
if (!pair.HasAnyConnection())
{
_allClientPairs.TryRemove(dto.User, out _);
}
else
{
pair.ApplyLastReceivedData();
}
RecreateLazy();
}
}
public void UpdatePairPermissions(UserPermissionsDto dto)
{
if (!_allClientPairs.TryGetValue(dto.User, out var pair))
{
throw new InvalidOperationException("No such pair for " + dto);
}
if (pair.UserPair == null) throw new InvalidOperationException("No direct pair for " + dto);
pair.UserPair.OtherPermissions = dto.Permissions;
if (!pair.UserPair.OtherPermissions.IsPaired())
{
pair.ApplyLastReceivedData();
}
}
public void UpdateSelfPairPermissions(UserPermissionsDto dto)
{
if (!_allClientPairs.TryGetValue(dto.User, out var pair))
{
throw new InvalidOperationException("No such pair for " + dto);
}
if (pair.UserPair == null) throw new InvalidOperationException("No direct pair for " + dto);
pair.UserPair.OwnPermissions = dto.Permissions;
}
private void DalamudUtilOnDelayedFrameworkUpdate()
{
foreach (var player in _allClientPairs.Select(p => p.Value).Where(p => p.CachedPlayer != null && p.CachedPlayer.IsVisible).ToList())
{
if (!player.CachedPlayer!.CheckExistence())
{
player.CachedPlayer.Dispose();
}
}
}
private void DalamudUtilOnZoneSwitched()
{
DisposePairs();
}
public void SetGroupInfo(GroupInfoDto dto)
{
_allGroups[dto.Group].Group = dto.Group;
_allGroups[dto.Group].Owner = dto.Owner;
_allGroups[dto.Group].GroupPermissions = dto.GroupPermissions;
RecreateLazy();
}
internal void SetGroupPermissions(GroupPermissionDto dto)
{
var prevPermissions = _allGroups[dto.Group].GroupPermissions;
_allGroups[dto.Group].GroupPermissions = dto.Permissions;
if (prevPermissions.IsDisableAnimations() != dto.Permissions.IsDisableAnimations()
|| prevPermissions.IsDisableSounds() != dto.Permissions.IsDisableSounds())
{
RecreateLazy();
var group = _allGroups[dto.Group];
GroupPairs[group].ForEach(p => p.ApplyLastReceivedData());
}
RecreateLazy();
}
internal void SetGroupPairUserPermissions(GroupPairUserPermissionDto dto)
{
var group = _allGroups[dto.Group];
var prevPermissions = _allClientPairs[dto.User].GroupPair[group].GroupUserPermissions;
_allClientPairs[dto.User].GroupPair[group].GroupUserPermissions = dto.GroupPairPermissions;
if (prevPermissions.IsDisableAnimations() != dto.GroupPairPermissions.IsDisableAnimations()
|| prevPermissions.IsDisableSounds() != dto.GroupPairPermissions.IsDisableSounds())
{
_allClientPairs[dto.User].ApplyLastReceivedData();
}
RecreateLazy();
}
internal void SetGroupUserPermissions(GroupPairUserPermissionDto dto)
{
var prevPermissions = _allGroups[dto.Group].GroupUserPermissions;
_allGroups[dto.Group].GroupUserPermissions = dto.GroupPairPermissions;
if (prevPermissions.IsDisableAnimations() != dto.GroupPairPermissions.IsDisableAnimations()
|| prevPermissions.IsDisableSounds() != dto.GroupPairPermissions.IsDisableSounds())
{
RecreateLazy();
var group = _allGroups[dto.Group];
GroupPairs[group].ForEach(p => p.ApplyLastReceivedData());
}
RecreateLazy();
}
internal void SetGroupStatusInfo(GroupPairUserInfoDto dto)
{
_allGroups[dto.Group].GroupUserInfo = dto.GroupUserInfo;
}
internal void SetGroupPairStatusInfo(GroupPairUserInfoDto dto)
{
var group = _allGroups[dto.Group];
_allClientPairs[dto.User].GroupPair[group].GroupPairStatusInfo = dto.GroupUserInfo;
RecreateLazy();
}
}

View File

@@ -1,23 +1,17 @@
using MareSynchronos.Factories;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
using System;
using System.Threading;
using System.Threading.Tasks;
using MareSynchronos.API;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using System.Collections.Generic;
using System.Linq;
using MareSynchronos.Models;
using MareSynchronos.FileCache;
using MareSynchronos.UI;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.Delegates;
#if DEBUG
using Newtonsoft.Json;
#endif
namespace MareSynchronos.Managers;
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
public class PlayerManager : IDisposable
{
@@ -28,15 +22,15 @@ public class PlayerManager : IDisposable
private readonly PeriodicFileScanner _periodicFileScanner;
private readonly SettingsUi _settingsUi;
private readonly IpcManager _ipcManager;
public event PlayerHasChanged? PlayerHasChanged;
public CharacterCacheDto? LastCreatedCharacterData { get; private set; }
public CharacterData PermanentDataCache { get; private set; } = new();
private readonly Dictionary<ObjectKind, Func<bool>> objectKindsToUpdate = new();
public event CharacterDataDelegate? PlayerHasChanged;
public API.Data.CharacterData? LastCreatedCharacterData { get; private set; }
public Models.CharacterData PermanentDataCache { get; private set; } = new();
private readonly Dictionary<ObjectKind, Func<bool>> _objectKindsToUpdate = new();
private CancellationTokenSource? _playerChangedCts = new();
private CancellationTokenSource _transientUpdateCts = new();
private List<PlayerRelatedObject> playerRelatedObjects = new();
private readonly List<PlayerRelatedObject> _playerRelatedObjects = new();
public unsafe PlayerManager(ApiController apiController, IpcManager ipcManager,
CharacterDataFactory characterDataFactory, DalamudUtil dalamudUtil, TransientResourceManager transientResourceManager,
@@ -66,7 +60,7 @@ public class PlayerManager : IDisposable
ApiControllerOnConnected();
}
playerRelatedObjects = new List<PlayerRelatedObject>()
_playerRelatedObjects = new List<PlayerRelatedObject>()
{
new PlayerRelatedObject(ObjectKind.Player, IntPtr.Zero, IntPtr.Zero, () => _dalamudUtil.PlayerPointer),
new PlayerRelatedObject(ObjectKind.MinionOrMount, IntPtr.Zero, IntPtr.Zero, () => (IntPtr)((Character*)_dalamudUtil.PlayerPointer)->CompanionObject),
@@ -77,12 +71,12 @@ public class PlayerManager : IDisposable
private void DalamudUtilOnFrameworkUpdate()
{
_transientResourceManager.PlayerRelatedPointers = playerRelatedObjects.Select(f => f.CurrentAddress).ToArray();
_transientResourceManager.PlayerRelatedPointers = _playerRelatedObjects.Select(f => f.CurrentAddress).ToArray();
}
public void HandleTransientResourceLoad(IntPtr gameObj)
public void HandleTransientResourceLoad(IntPtr gameObj, int idx)
{
foreach (var obj in playerRelatedObjects)
foreach (var obj in _playerRelatedObjects)
{
if (obj.Address == gameObj && !obj.HasUnprocessedUpdate)
{
@@ -105,7 +99,7 @@ public class PlayerManager : IDisposable
private void HeelsOffsetChanged(float change)
{
var player = playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
if (LastCreatedCharacterData != null && LastCreatedCharacterData.HeelsOffset != change && !player.IsProcessing)
{
Logger.Debug("Heels offset changed to " + change);
@@ -116,8 +110,8 @@ public class PlayerManager : IDisposable
private void CustomizePlusChanged(string? change)
{
change ??= string.Empty;
var player = playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
if (LastCreatedCharacterData != null && LastCreatedCharacterData.CustomizePlusData != change && !player.IsProcessing)
var player = _playerRelatedObjects.First(f => f.ObjectKind == ObjectKind.Player);
if (LastCreatedCharacterData != null && !string.Equals(LastCreatedCharacterData.CustomizePlusData, change, StringComparison.Ordinal) && !player.IsProcessing)
{
Logger.Debug("CustomizePlus data changed to " + change);
player.HasTransientsUpdate = true;
@@ -146,8 +140,8 @@ public class PlayerManager : IDisposable
{
if (!_dalamudUtil.IsPlayerPresent || !_ipcManager.Initialized) return;
playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject());
if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && !c.IsProcessing))
_playerRelatedObjects.ForEach(k => k.CheckAndUpdateObject());
if (_playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && !c.IsProcessing))
{
OnPlayerOrAttachedObjectsChanged();
}
@@ -167,9 +161,9 @@ public class PlayerManager : IDisposable
_ipcManager.PenumbraRedrawEvent -= IpcManager_PenumbraRedrawEvent;
}
private async Task<CharacterCacheDto?> CreateFullCharacterCacheDto(CancellationToken token)
private async Task<API.Data.CharacterData?> CreateFullCharacterCacheDto(CancellationToken token)
{
foreach (var unprocessedObject in playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList())
foreach (var unprocessedObject in _playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList())
{
Logger.Verbose("Building Cache for " + unprocessedObject.ObjectKind);
PermanentDataCache = _characterDataFactory.BuildCharacterData(PermanentDataCache, unprocessedObject, token);
@@ -194,7 +188,7 @@ public class PlayerManager : IDisposable
Logger.Verbose("Cache creation complete");
var cache = PermanentDataCache.ToCharacterCacheDto();
var cache = PermanentDataCache.ToAPI();
//Logger.Verbose(JsonConvert.SerializeObject(cache, Formatting.Indented));
return cache;
}
@@ -203,7 +197,7 @@ public class PlayerManager : IDisposable
{
Logger.Verbose("RedrawEvent for addr " + address);
foreach (var item in playerRelatedObjects)
foreach (var item in _playerRelatedObjects)
{
if (address == item.Address)
{
@@ -212,7 +206,7 @@ public class PlayerManager : IDisposable
}
}
if (playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && (!c.IsProcessing || (c.IsProcessing && c.DoNotSendUpdate))))
if (_playerRelatedObjects.Any(c => (c.HasUnprocessedUpdate || c.HasTransientsUpdate) && (!c.IsProcessing || (c.IsProcessing && c.DoNotSendUpdate))))
{
OnPlayerOrAttachedObjectsChanged();
}
@@ -220,7 +214,7 @@ public class PlayerManager : IDisposable
private void OnPlayerOrAttachedObjectsChanged()
{
var unprocessedObjects = playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList();
var unprocessedObjects = _playerRelatedObjects.Where(c => c.HasUnprocessedUpdate || c.HasTransientsUpdate).ToList();
foreach (var unprocessedObject in unprocessedObjects)
{
unprocessedObject.IsProcessing = true;
@@ -253,7 +247,7 @@ public class PlayerManager : IDisposable
Task.Run(async () =>
{
CharacterCacheDto? cacheDto = null;
API.Data.CharacterData? cacheDto = null;
try
{
_periodicFileScanner.HaltScan("Character creation");
@@ -278,15 +272,13 @@ public class PlayerManager : IDisposable
//Logger.Verbose(json);
#endif
if ((LastCreatedCharacterData?.GetHashCode() ?? 0) == cacheDto.GetHashCode())
if (string.Equals(LastCreatedCharacterData?.DataHash.Value ?? string.Empty, cacheDto.DataHash.Value, StringComparison.Ordinal))
{
Logger.Debug("Not sending data, already sent");
return;
}
else
{
LastCreatedCharacterData = cacheDto;
}
if (_apiController.IsConnected && !token.IsCancellationRequested && !doNotSendUpdate)
{

View File

@@ -0,0 +1,168 @@
using MareSynchronos.MareConfiguration;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.Managers;
public class ServerConfigurationManager
{
private readonly Dictionary<JwtCache, string> _tokenDictionary = new();
private readonly ConfigurationService _configService;
private readonly DalamudUtil _dalamudUtil;
public string CurrentApiUrl => string.IsNullOrEmpty(_configService.Current.CurrentServer) ? ApiController.MainServiceUri : _configService.Current.CurrentServer;
public ServerStorage? CurrentServer => (_configService.Current.ServerStorage.ContainsKey(CurrentApiUrl) ? _configService.Current.ServerStorage[CurrentApiUrl] : null);
public ServerConfigurationManager(ConfigurationService configService, DalamudUtil dalamudUtil)
{
_configService = configService;
_dalamudUtil = dalamudUtil;
}
public bool HasValidConfig()
{
return CurrentServer != null && (CurrentServer?.Authentications.Any() ?? false);
}
public string[] GetServerApiUrls()
{
return _configService.Current.ServerStorage.Keys.ToArray();
}
public string[] GetServerNames()
{
return _configService.Current.ServerStorage.Values.Select(v => v.ServerName).ToArray();
}
public ServerStorage GetServerByIndex(int idx)
{
try
{
return _configService.Current.ServerStorage.ElementAt(idx).Value;
}
catch
{
_configService.Current.CurrentServer = ApiController.MainServiceUri;
if (!_configService.Current.ServerStorage.ContainsKey(ApiController.MainServer))
{
_configService.Current.ServerStorage.Add(_configService.Current.CurrentServer, new ServerStorage() { ServerUri = ApiController.MainServiceUri, ServerName = ApiController.MainServer });
}
_configService.Save();
return CurrentServer!;
}
}
public int GetCurrentServerIndex()
{
return Array.IndexOf(_configService.Current.ServerStorage.Keys.ToArray(), CurrentApiUrl);
}
public void Save()
{
_configService.Save();
}
public void SelectServer(int idx)
{
_configService.Current.CurrentServer = GetServerByIndex(idx).ServerUri;
CurrentServer!.FullPause = false;
Save();
}
public string? GetSecretKey(int serverIdx = -1)
{
ServerStorage? currentServer;
currentServer = serverIdx == -1 ? CurrentServer : GetServerByIndex(serverIdx);
Save();
if (currentServer == null)
{
currentServer = new();
Save();
}
var charaName = _dalamudUtil.PlayerName;
var worldId = _dalamudUtil.WorldId;
if (!currentServer.Authentications.Any() && currentServer.SecretKeys.Any())
{
currentServer.Authentications.Add(new Authentication()
{
CharacterName = charaName,
WorldId = worldId,
SecretKeyIdx = currentServer.SecretKeys.Last().Key,
});
Save();
}
var auth = currentServer.Authentications.Find(f => string.Equals(f.CharacterName, charaName, StringComparison.Ordinal) && f.WorldId == worldId);
if (auth == null) return null;
if (currentServer.SecretKeys.TryGetValue(auth.SecretKeyIdx, out var secretKey))
{
return secretKey.Key;
}
return null;
}
public string? GetToken()
{
var charaName = _dalamudUtil.PlayerName;
var worldId = _dalamudUtil.WorldId;
var secretKey = GetSecretKey();
if (secretKey == null) return null;
if (_tokenDictionary.TryGetValue(new JwtCache(CurrentApiUrl, charaName, worldId, secretKey), out var token))
{
return token;
}
return null;
}
public void SaveToken(string token)
{
var charaName = _dalamudUtil.PlayerName;
var worldId = _dalamudUtil.WorldId;
var secretKey = GetSecretKey();
if (string.IsNullOrEmpty(secretKey)) throw new InvalidOperationException("No secret key set");
_tokenDictionary[new JwtCache(CurrentApiUrl, charaName, worldId, secretKey)] = token;
}
internal void AddCurrentCharacterToServer(int serverSelectionIndex = -1, bool addLastSecretKey = false)
{
if (serverSelectionIndex == -1) serverSelectionIndex = GetCurrentServerIndex();
var server = GetServerByIndex(serverSelectionIndex);
server.Authentications.Add(new Authentication()
{
CharacterName = _dalamudUtil.PlayerName,
WorldId = _dalamudUtil.WorldId,
SecretKeyIdx = addLastSecretKey ? server.SecretKeys.Last().Key : -1,
});
_configService.Save();
}
internal void AddEmptyCharacterToServer(int serverSelectionIndex)
{
var server = GetServerByIndex(serverSelectionIndex);
server.Authentications.Add(new Authentication());
_configService.Save();
}
internal void RemoveCharacterFromServer(int serverSelectionIndex, Authentication item)
{
var server = GetServerByIndex(serverSelectionIndex);
server.Authentications.Remove(item);
}
internal void AddServer(ServerStorage serverStorage)
{
_configService.Current.ServerStorage[serverStorage.ServerUri] = serverStorage;
_configService.Save();
}
internal void DeleteServer(ServerStorage selectedServer)
{
_configService.Current.ServerStorage.Remove(selectedServer.ServerUri);
}
}

View File

@@ -1,65 +1,70 @@
using MareSynchronos.API;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.Delegates;
using MareSynchronos.Factories;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Threading.Tasks;
namespace MareSynchronos.Managers;
public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
public class TransientResourceManager : IDisposable
{
private readonly IpcManager manager;
private readonly DalamudUtil dalamudUtil;
private readonly string configurationDirectory;
public event TransientResourceLoadedEvent? TransientResourceLoaded;
private readonly IpcManager _ipcManager;
private readonly ConfigurationService _configurationService;
private readonly DalamudUtil _dalamudUtil;
public event DrawObjectDelegate? TransientResourceLoaded;
public IntPtr[] PlayerRelatedPointers = Array.Empty<IntPtr>();
private readonly string[] FileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk" };
private string PersistentDataCache => Path.Combine(configurationDirectory, "PersistentTransientData.lst");
private readonly string[] _fileTypesToHandle = new[] { "tmb", "pap", "avfx", "atex", "sklb", "eid", "phyb", "scd", "skp", "shpk" };
[Obsolete]
private string PersistentDataCache => Path.Combine(_configurationService.ConfigurationDirectory, "PersistentTransientData.lst");
private string PlayerPersistentDataKey => _dalamudUtil.PlayerName + "_" + _dalamudUtil.WorldId;
private ConcurrentDictionary<IntPtr, HashSet<string>> TransientResources { get; } = new();
private ConcurrentDictionary<ObjectKind, HashSet<FileReplacement>> SemiTransientResources { get; } = new();
public TransientResourceManager(IpcManager manager, DalamudUtil dalamudUtil, FileReplacementFactory fileReplacementFactory, string configurationDirectory)
public TransientResourceManager(IpcManager manager, ConfigurationService configurationService, DalamudUtil dalamudUtil, FileReplacementFactory fileReplacementFactory)
{
manager.PenumbraResourceLoadEvent += Manager_PenumbraResourceLoadEvent;
manager.PenumbraModSettingChanged += Manager_PenumbraModSettingChanged;
this.manager = manager;
this.dalamudUtil = dalamudUtil;
this.configurationDirectory = configurationDirectory;
_ipcManager = manager;
_configurationService = configurationService;
_dalamudUtil = dalamudUtil;
dalamudUtil.FrameworkUpdate += DalamudUtil_FrameworkUpdate;
dalamudUtil.ClassJobChanged += DalamudUtil_ClassJobChanged;
// migrate obsolete data to new format
if (File.Exists(PersistentDataCache))
{
var persistentEntities = File.ReadAllLines(PersistentDataCache);
var persistentEntities = File.ReadAllLines(PersistentDataCache).ToHashSet(StringComparer.OrdinalIgnoreCase);
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey] = persistentEntities;
_configurationService.Save();
File.Delete(PersistentDataCache);
}
SemiTransientResources.TryAdd(ObjectKind.Player, new HashSet<FileReplacement>());
if (_configurationService.Current.PlayerPersistentTransientCache.TryGetValue(PlayerPersistentDataKey, out var linesInConfig))
{
int restored = 0;
foreach (var line in persistentEntities)
foreach (var file in linesInConfig)
{
try
{
var fileReplacement = fileReplacementFactory.Create();
fileReplacement.ResolvePath(line);
fileReplacement.ResolvePath(file);
if (fileReplacement.HasFileReplacement)
{
Logger.Debug("Loaded persistent transient resource " + line);
Logger.Debug("Loaded persistent transient resource " + file);
SemiTransientResources[ObjectKind.Player].Add(fileReplacement);
restored++;
}
}
catch (Exception ex)
{
Logger.Warn("Error during loading persistent transient resource " + line, ex);
}
Logger.Warn("Error during loading persistent transient resource " + file, ex);
}
Logger.Debug($"Restored {restored}/{persistentEntities.Count()} semi persistent resources");
}
Logger.Debug($"Restored {restored}/{linesInConfig.Count()} semi persistent resources");
}
}
@@ -78,7 +83,7 @@ public class TransientResourceManager : IDisposable
return !verified;
});
if (!successfulValidation)
TransientResourceLoaded?.Invoke(dalamudUtil.PlayerPointer);
TransientResourceLoaded?.Invoke(_dalamudUtil.PlayerPointer, -1);
}
});
}
@@ -95,7 +100,7 @@ public class TransientResourceManager : IDisposable
{
foreach (var item in TransientResources.ToList())
{
if (!dalamudUtil.IsGameObjectPresent(item.Key))
if (!_dalamudUtil.IsGameObjectPresent(item.Key))
{
Logger.Debug("Object not present anymore: " + item.Key.ToString("X"));
TransientResources.TryRemove(item.Key, out _);
@@ -133,7 +138,7 @@ public class TransientResourceManager : IDisposable
private void Manager_PenumbraResourceLoadEvent(IntPtr gameObject, string gamePath, string filePath)
{
if (!FileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
if (!_fileTypesToHandle.Any(type => gamePath.EndsWith(type, StringComparison.OrdinalIgnoreCase)))
{
return;
}
@@ -171,7 +176,7 @@ public class TransientResourceManager : IDisposable
{
TransientResources[gameObject].Add(replacedGamePath);
Logger.Debug($"Adding {replacedGamePath} for {gameObject} ({filePath})");
TransientResourceLoaded?.Invoke(gameObject);
TransientResourceLoaded?.Invoke(gameObject, -1);
}
}
@@ -210,9 +215,9 @@ public class TransientResourceManager : IDisposable
try
{
var fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), true);
var fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), arg2: true);
if (!fileReplacement.HasFileReplacement)
fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), false);
fileReplacement = createFileReplacement(gamePath.ToLowerInvariant(), arg2: false);
if (fileReplacement.HasFileReplacement)
{
Logger.Debug("Persisting " + gamePath.ToLowerInvariant());
@@ -234,22 +239,26 @@ public class TransientResourceManager : IDisposable
if (objectKind == ObjectKind.Player && SemiTransientResources.TryGetValue(ObjectKind.Player, out var fileReplacements))
{
File.WriteAllLines(PersistentDataCache, fileReplacements.SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase));
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
= fileReplacements.SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
_configurationService.Save();
}
TransientResources[gameObject].Clear();
}
public void Dispose()
{
dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate;
manager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent;
dalamudUtil.ClassJobChanged -= DalamudUtil_ClassJobChanged;
manager.PenumbraModSettingChanged -= Manager_PenumbraModSettingChanged;
_dalamudUtil.FrameworkUpdate -= DalamudUtil_FrameworkUpdate;
_ipcManager.PenumbraResourceLoadEvent -= Manager_PenumbraResourceLoadEvent;
_dalamudUtil.ClassJobChanged -= DalamudUtil_ClassJobChanged;
_ipcManager.PenumbraModSettingChanged -= Manager_PenumbraModSettingChanged;
TransientResources.Clear();
SemiTransientResources.Clear();
if (SemiTransientResources.ContainsKey(ObjectKind.Player))
{
File.WriteAllLines(PersistentDataCache, SemiTransientResources[ObjectKind.Player].SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase));
_configurationService.Current.PlayerPersistentTransientCache[PlayerPersistentDataKey]
= SemiTransientResources[ObjectKind.Player].SelectMany(p => p.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToHashSet(StringComparer.OrdinalIgnoreCase);
_configurationService.Save();
}
}

View File

@@ -0,0 +1,9 @@
namespace MareSynchronos.MareConfiguration;
[Serializable]
public class Authentication
{
public string CharacterName { get; set; } = string.Empty;
public uint WorldId { get; set; } = 0;
public int SecretKeyIdx { get; set; } = -1;
}

View File

@@ -0,0 +1,149 @@
using Dalamud.Configuration;
using Dalamud.Plugin;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.MareConfiguration;
[Serializable]
[Obsolete("Migrated to MareConfig")]
public class Configuration : IPluginConfiguration
{
public int Version { get; set; } = 6;
[NonSerialized]
private DalamudPluginInterface? _pluginInterface;
public Dictionary<string, ServerStorage> ServerStorage { get; set; } = new(StringComparer.OrdinalIgnoreCase)
{
{ ApiController.MainServiceUri, new ServerStorage() { ServerName = ApiController.MainServer, ServerUri = ApiController.MainServiceUri } },
};
public bool AcceptedAgreement { get; set; } = false;
public string CacheFolder { get; set; } = string.Empty;
public double MaxLocalCacheInGiB { get; set; } = 20;
public bool ReverseUserSort { get; set; } = false;
public int TimeSpanBetweenScansInSeconds { get; set; } = 30;
public bool FileScanPaused { get; set; } = false;
public bool InitialScanComplete { get; set; } = false;
public bool FullPause { get; set; } = false;
public bool HideInfoMessages { get; set; } = false;
public bool DisableOptionalPluginWarnings { get; set; } = false;
public bool OpenGposeImportOnGposeStart { get; set; } = false;
public bool ShowTransferWindow { get; set; } = true;
public bool OpenPopupOnAdd { get; set; } = true;
public string CurrentServer { get; set; } = string.Empty;
private string _apiUri = string.Empty;
public string ApiUri
{
get => string.IsNullOrEmpty(_apiUri) ? ApiController.MainServiceUri : _apiUri;
set => _apiUri = value;
}
public Dictionary<string, string> ClientSecret { get; set; } = new(StringComparer.Ordinal);
public Dictionary<string, string> CustomServerList { get; set; } = new(StringComparer.Ordinal);
public Dictionary<string, Dictionary<string, string>> UidServerComments { get; set; } = new(StringComparer.Ordinal);
public Dictionary<string, Dictionary<string, string>> GidServerComments { get; set; } = new(StringComparer.Ordinal);
/// <summary>
/// Each paired user can have multiple tags. Each tag will create a category, and the user will
/// be displayed into that category.
/// The dictionary first maps a server URL to a dictionary, and that
/// dictionary maps the OtherUID of the <see cref="ClientPairDto"/> to a list of tags.
/// </summary>
public Dictionary<string, Dictionary<string, List<string>>> UidServerPairedUserTags = new(StringComparer.Ordinal);
/// <summary>
/// A dictionary that maps a server URL to the tags the user has added for that server.
/// </summary>
public Dictionary<string, HashSet<string>> ServerAvailablePairTags = new(StringComparer.Ordinal);
public HashSet<string> OpenPairTags = new(StringComparer.Ordinal);
// the below exist just to make saving less cumbersome
public void Initialize(DalamudPluginInterface pluginInterface)
{
_pluginInterface = pluginInterface;
if (!Directory.Exists(CacheFolder))
{
InitialScanComplete = false;
}
Save();
}
public void Save()
{
_pluginInterface!.SavePluginConfig(this);
}
public MareConfig ToMareConfig()
{
MareConfig newConfig = new();
Logger.Info("Migrating Config to MareConfig");
newConfig.AcceptedAgreement = AcceptedAgreement;
newConfig.CacheFolder = CacheFolder;
newConfig.MaxLocalCacheInGiB = MaxLocalCacheInGiB;
newConfig.ReverseUserSort = ReverseUserSort;
newConfig.TimeSpanBetweenScansInSeconds = TimeSpanBetweenScansInSeconds;
newConfig.FileScanPaused = FileScanPaused;
newConfig.InitialScanComplete = InitialScanComplete;
newConfig.HideInfoMessages = HideInfoMessages;
newConfig.DisableOptionalPluginWarnings = DisableOptionalPluginWarnings;
newConfig.OpenGposeImportOnGposeStart = OpenGposeImportOnGposeStart;
newConfig.ShowTransferWindow = ShowTransferWindow;
newConfig.OpenPopupOnAdd = OpenPopupOnAdd;
newConfig.CurrentServer = ApiUri;
// create all server storage based on current clientsecret
foreach (var secret in ClientSecret)
{
Logger.Debug("Migrating " + secret.Key);
var apiuri = secret.Key;
var secretkey = secret.Value;
ServerStorage toAdd = new();
if (string.Equals(apiuri, ApiController.MainServiceUri, StringComparison.OrdinalIgnoreCase))
{
toAdd.ServerUri = ApiController.MainServiceUri;
toAdd.ServerName = ApiController.MainServer;
}
else
{
toAdd.ServerUri = apiuri;
if (!CustomServerList.TryGetValue(apiuri, out var serverName)) serverName = apiuri;
toAdd.ServerName = serverName;
}
toAdd.SecretKeys[0] = new SecretKey()
{
FriendlyName = "Auto Migrated Secret Key (" + DateTime.Now.ToString("yyyy-MM-dd") + ")",
Key = secretkey,
};
if (GidServerComments.TryGetValue(apiuri, out var gids))
{
toAdd.GidServerComments = gids;
}
if (UidServerComments.TryGetValue(apiuri, out var uids))
{
toAdd.UidServerComments = uids;
}
if (UidServerPairedUserTags.TryGetValue(apiuri, out var uidtag))
{
toAdd.UidServerPairedUserTags = uidtag;
}
if (ServerAvailablePairTags.TryGetValue(apiuri, out var servertag))
{
toAdd.ServerAvailablePairTags = servertag;
}
toAdd.OpenPairTags = OpenPairTags;
toAdd.FullPause = FullPause;
newConfig.ServerStorage[apiuri] = toAdd;
}
return newConfig;
}
public void Migrate()
{
}
}

View File

@@ -0,0 +1,11 @@
namespace MareSynchronos.MareConfiguration;
public static class ConfigurationExtensions
{
public static bool HasValidSetup(this MareConfig configuration)
{
return configuration.AcceptedAgreement && configuration.InitialScanComplete
&& !string.IsNullOrEmpty(configuration.CacheFolder)
&& Directory.Exists(configuration.CacheFolder);
}
}

View File

@@ -0,0 +1,81 @@
using Dalamud.Plugin;
using MareSynchronos.Utils;
using Newtonsoft.Json;
namespace MareSynchronos.MareConfiguration;
public class ConfigurationService : IDisposable
{
private const string _configurationName = "Config.json";
private string ConfigurationPath => Path.Combine(_pluginInterface.ConfigDirectory.FullName, _configurationName);
public string ConfigurationDirectory => _pluginInterface.ConfigDirectory.FullName;
private readonly DalamudPluginInterface _pluginInterface;
private readonly CancellationTokenSource _periodicCheckCts = new();
private DateTime _configLastWriteTime;
public MareConfig Current { get; private set; }
public ConfigurationService(DalamudPluginInterface pluginInterface)
{
_pluginInterface = pluginInterface;
if (pluginInterface.GetPluginConfig() is Configuration oldConfig)
{
Current = oldConfig.ToMareConfig();
File.Move(pluginInterface.ConfigFile.FullName, pluginInterface.ConfigFile.FullName + ".old", overwrite: true);
}
else
{
Current = LoadConfig();
}
Save();
Task.Run(CheckForConfigUpdatesInternal, _periodicCheckCts.Token);
}
private async Task CheckForConfigUpdatesInternal()
{
while (!_periodicCheckCts.IsCancellationRequested)
{
var lastWriteTime = GetConfigLastWriteTime();
if (lastWriteTime != _configLastWriteTime)
{
Logger.Debug("Config changed, reloading config");
Current = LoadConfig();
}
await Task.Delay(TimeSpan.FromSeconds(5), _periodicCheckCts.Token).ConfigureAwait(false);
}
}
private MareConfig LoadConfig()
{
MareConfig config;
if (!File.Exists(ConfigurationPath))
{
config = new();
}
else
{
config = JsonConvert.DeserializeObject<MareConfig>(File.ReadAllText(ConfigurationPath)) ?? new MareConfig();
}
_configLastWriteTime = GetConfigLastWriteTime();
return config;
}
private DateTime GetConfigLastWriteTime() => new FileInfo(ConfigurationPath).LastWriteTimeUtc;
public void Save()
{
File.WriteAllText(ConfigurationPath, JsonConvert.SerializeObject(Current, Formatting.Indented));
_configLastWriteTime = new FileInfo(ConfigurationPath).LastWriteTimeUtc;
}
public void Dispose()
{
Save();
_periodicCheckCts.Cancel();
}
}

View File

@@ -0,0 +1,30 @@
using Dalamud.Configuration;
using MareSynchronos.WebAPI;
namespace MareSynchronos.MareConfiguration;
[Serializable]
public class MareConfig : IPluginConfiguration
{
public int Version { get; set; } = 0;
public Dictionary<string, ServerStorage> ServerStorage { get; set; } = new(StringComparer.OrdinalIgnoreCase)
{
{ ApiController.MainServiceUri, new ServerStorage() { ServerName = ApiController.MainServer, ServerUri = ApiController.MainServiceUri } },
};
public Dictionary<string, HashSet<string>> PlayerPersistentTransientCache { get; set; } = new(StringComparer.Ordinal);
public bool AcceptedAgreement { get; set; } = false;
public string CacheFolder { get; set; } = string.Empty;
public double MaxLocalCacheInGiB { get; set; } = 20;
public bool ReverseUserSort { get; set; } = false;
public int TimeSpanBetweenScansInSeconds { get; set; } = 30;
public bool FileScanPaused { get; set; } = false;
public bool InitialScanComplete { get; set; } = false;
public bool HideInfoMessages { get; set; } = false;
public bool DisableOptionalPluginWarnings { get; set; } = false;
public bool OpenGposeImportOnGposeStart { get; set; } = false;
public bool ShowTransferWindow { get; set; } = true;
public bool OpenPopupOnAdd { get; set; } = true;
public string CurrentServer { get; set; } = string.Empty;
public bool ShowOnlineNotifications { get; set; } = false;
public bool ShowOnlineNotificationsOnlyForIndividualPairs { get; set; } = true;
}

View File

@@ -0,0 +1,8 @@
namespace MareSynchronos.MareConfiguration;
[Serializable]
public class SecretKey
{
public string Key { get; set; } = string.Empty;
public string FriendlyName { get; set; } = string.Empty;
}

View File

@@ -0,0 +1,16 @@
namespace MareSynchronos.MareConfiguration;
[Serializable]
public class ServerStorage
{
public string ServerUri { get; set; } = string.Empty;
public string ServerName { get; set; } = string.Empty;
public List<Authentication> Authentications { get; set; } = new();
public Dictionary<string, string> UidServerComments { get; set; } = new(StringComparer.Ordinal);
public Dictionary<string, string> GidServerComments { get; set; } = new(StringComparer.Ordinal);
public Dictionary<string, List<string>> UidServerPairedUserTags = new(StringComparer.Ordinal);
public HashSet<string> ServerAvailablePairTags { get; set; } = new(StringComparer.Ordinal);
public HashSet<string> OpenPairTags { get; set; } = new(StringComparer.Ordinal);
public Dictionary<int, SecretKey> SecretKeys { get; set; } = new();
public bool FullPause { get; set; } = false;
}

View File

@@ -28,17 +28,18 @@
<ItemGroup>
<PackageReference Include="DalamudPackager" Version="2.1.10" />
<PackageReference Include="lz4net" Version="1.0.15.93" />
<PackageReference Include="Meziantou.Analyzer" Version="2.0.4">
<PackageReference Include="Meziantou.Analyzer" Version="2.0.13">
<PrivateAssets>all</PrivateAssets>
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.1" />
<PackageReference Include="Penumbra.Api" Version="1.0.5" />
<PackageReference Include="Microsoft.AspNetCore.SignalR.Client" Version="7.0.2" />
<PackageReference Include="Penumbra.Api" Version="1.0.6" />
<PackageReference Include="Penumbra.String" Version="1.0.1" />
</ItemGroup>
<PropertyGroup>
<SourceRevisionId>build$([System.DateTime]::UtcNow.ToString("yyyy-MM-ddTHH:mm:ss:fffZ"))</SourceRevisionId>
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
@@ -81,9 +82,6 @@
</ItemGroup>
<ItemGroup>
<None Update="FileCache.db">
<CopyToOutputDirectory>Never</CopyToOutputDirectory>
</None>
<None Update="images\icon.png">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>

View File

@@ -1,10 +1,8 @@
using Newtonsoft.Json;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using MareSynchronos.API;
using MareSynchronos.Utils;
using Lumina.Excel.GeneratedSheets;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.API.Data;
namespace MareSynchronos.Models;
@@ -34,10 +32,10 @@ public class CharacterData
if (!FileReplacements.ContainsKey(objectKind)) FileReplacements.Add(objectKind, new List<FileReplacement>());
var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => string.Equals(f.ResolvedPath, fileReplacement.ResolvedPath, System.StringComparison.OrdinalIgnoreCase));
var existingReplacement = FileReplacements[objectKind].SingleOrDefault(f => string.Equals(f.ResolvedPath, fileReplacement.ResolvedPath, StringComparison.OrdinalIgnoreCase));
if (existingReplacement != null)
{
existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e, System.StringComparer.OrdinalIgnoreCase)));
existingReplacement.GamePaths.AddRange(fileReplacement.GamePaths.Where(e => !existingReplacement.GamePaths.Contains(e, StringComparer.OrdinalIgnoreCase)));
}
else
{
@@ -45,13 +43,13 @@ public class CharacterData
}
}
public CharacterCacheDto ToCharacterCacheDto()
public API.Data.CharacterData ToAPI()
{
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 =>
var fileReplacements = FileReplacements.ToDictionary(k => k.Key, k => k.Value.Where(f => f.HasFileReplacement && !f.IsFileSwap).GroupBy(f => f.Hash, StringComparer.OrdinalIgnoreCase).Select(g =>
{
return new FileReplacementDto()
return new FileReplacementData()
{
GamePaths = g.SelectMany(f => f.GamePaths).Distinct(System.StringComparer.OrdinalIgnoreCase).ToArray(),
GamePaths = g.SelectMany(f => f.GamePaths).Distinct(StringComparer.OrdinalIgnoreCase).ToArray(),
Hash = g.First().Hash,
};
}).ToList());
@@ -69,20 +67,20 @@ public class CharacterData
fileReplacements[item.Key].AddRange(fileSwapsToAdd);
}
return new CharacterCacheDto()
return new API.Data.CharacterData()
{
FileReplacements = fileReplacements,
GlamourerData = GlamourerString.ToDictionary(d => d.Key, d => d.Value),
ManipulationData = ManipulationString,
HeelsOffset = HeelsOffset,
CustomizePlusData = CustomizePlusScale
CustomizePlusData = CustomizePlusScale,
};
}
public override string ToString()
{
StringBuilder stringBuilder = new();
foreach (var fileReplacement in FileReplacements.SelectMany(k => k.Value).OrderBy(a => a.GamePaths[0]))
foreach (var fileReplacement in FileReplacements.SelectMany(k => k.Value).OrderBy(a => a.GamePaths[0], StringComparer.Ordinal))
{
stringBuilder.AppendLine(fileReplacement.ToString());
}

View File

@@ -1,34 +1,30 @@
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using MareSynchronos.API;
using System.Text;
using System.Text.RegularExpressions;
using MareSynchronos.FileCache;
using MareSynchronos.Managers;
using MareSynchronos.Utils;
using System;
using MareSynchronos.API.Data;
namespace MareSynchronos.Models;
public class FileReplacement
{
private readonly FileCacheManager fileDbManager;
private readonly IpcManager ipcManager;
private readonly FileCacheManager _fileDbManager;
private readonly IpcManager _ipcManager;
public FileReplacement(FileCacheManager fileDbManager, IpcManager ipcManager)
{
this.fileDbManager = fileDbManager;
this.ipcManager = ipcManager;
_fileDbManager = fileDbManager;
_ipcManager = ipcManager;
}
public bool Computed => IsFileSwap || !HasFileReplacement || !string.IsNullOrEmpty(Hash);
public List<string> GamePaths { get; set; } = new();
public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => !string.Equals(p, ResolvedPath, System.StringComparison.Ordinal));
public bool HasFileReplacement => GamePaths.Count >= 1 && GamePaths.Any(p => !string.Equals(p, ResolvedPath, StringComparison.Ordinal));
public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && !string.Equals(GamePaths.First(), ResolvedPath, System.StringComparison.Ordinal);
public bool IsFileSwap => !Regex.IsMatch(ResolvedPath, @"^[a-zA-Z]:(/|\\)", RegexOptions.ECMAScript) && !string.Equals(GamePaths[0], ResolvedPath, StringComparison.Ordinal);
public string Hash { get; private set; } = string.Empty;
@@ -43,13 +39,13 @@ public class FileReplacement
{
try
{
var cache = fileDbManager.GetFileCacheByPath(ResolvedPath)!;
var cache = _fileDbManager.GetFileCacheByPath(ResolvedPath)!;
Hash = cache.Hash;
}
catch (Exception ex)
{
Logger.Warn("Could not set Hash for " + ResolvedPath + ", resetting to original");
ResolvedPath = GamePaths.First();
Logger.Warn("Could not set Hash for " + ResolvedPath + ", resetting to original", ex);
ResolvedPath = GamePaths[0];
}
});
}
@@ -58,34 +54,34 @@ public class FileReplacement
{
if (!IsFileSwap)
{
var cache = fileDbManager.GetFileCacheByPath(ResolvedPath);
var cache = _fileDbManager.GetFileCacheByPath(ResolvedPath);
if (cache == null)
{
Logger.Warn("Replacement Failed verification: " + GamePaths.First());
Logger.Warn("Replacement Failed verification: " + GamePaths[0]);
return false;
}
Hash = cache.Hash;
return true;
}
ResolvePath(GamePaths.First());
ResolvePath(GamePaths[0]);
var success = IsFileSwap;
if (!success)
{
Logger.Warn("FileSwap Failed verification: " + GamePaths.First());
Logger.Warn("FileSwap Failed verification: " + GamePaths[0]);
}
return success;
}
public FileReplacementDto ToFileReplacementDto()
public FileReplacementData ToFileReplacementDto()
{
return new FileReplacementDto
return new FileReplacementData
{
GamePaths = GamePaths.ToArray(),
Hash = Hash,
FileSwapPath = IsFileSwap ? ResolvedPath : string.Empty
FileSwapPath = IsFileSwap ? ResolvedPath : string.Empty,
};
}
@@ -98,13 +94,13 @@ public class FileReplacement
internal void ReverseResolvePath(string path)
{
GamePaths = ipcManager.PenumbraReverseResolvePlayer(path).ToList();
GamePaths = _ipcManager.PenumbraReverseResolvePlayer(path).ToList();
SetResolvedPath(path);
}
internal void ResolvePath(string path)
{
GamePaths = new List<string> { path };
SetResolvedPath(ipcManager.PenumbraResolvePath(path));
SetResolvedPath(_ipcManager.PenumbraResolvePath(path));
}
}

View File

@@ -0,0 +1,3 @@
namespace MareSynchronos.Models;
public record JwtCache(string ApiUrl, string PlayerName, uint WorldId, string SecretKey);

View File

@@ -0,0 +1,135 @@
using Dalamud.Utility;
using MareSynchronos.API.Data;
using MareSynchronos.API.Data.Comparer;
using MareSynchronos.API.Data.Extensions;
using MareSynchronos.API.Dto.Group;
using MareSynchronos.API.Dto.User;
using MareSynchronos.Managers;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Utils;
namespace MareSynchronos.Models;
public class Pair
{
private readonly ConfigurationService _configService;
private readonly ServerConfigurationManager _serverConfigurationManager;
private OptionalPluginWarning? _pluginWarnings;
public Pair(ConfigurationService configService, ServerConfigurationManager serverConfigurationManager)
{
_configService = configService;
_serverConfigurationManager = serverConfigurationManager;
}
public UserPairDto? UserPair { get; set; }
public CachedPlayer? CachedPlayer { get; set; }
public API.Data.CharacterData? LastReceivedCharacterData { get; set; }
public Dictionary<GroupFullInfoDto, GroupPairFullInfoDto> GroupPair { get; set; } = new(GroupDtoComparer.Instance);
public string PlayerNameHash => CachedPlayer?.PlayerNameHash ?? string.Empty;
public string? PlayerName => CachedPlayer?.PlayerName ?? string.Empty;
public UserData UserData => UserPair?.User ?? GroupPair.First().Value.User;
public bool IsOnline => CachedPlayer != null;
public bool IsVisible => CachedPlayer != null && CachedPlayer.IsVisible;
public bool IsPaused => UserPair != null && UserPair.OtherPermissions.IsPaired() ? (UserPair.OtherPermissions.IsPaused() || UserPair.OwnPermissions.IsPaused())
: GroupPair.All(p => p.Key.GroupUserPermissions.IsPaused() || p.Value.GroupUserPermissions.IsPaused());
public string? GetNote()
{
if (_serverConfigurationManager.CurrentServer!.UidServerComments.TryGetValue(UserData.UID, out string? note))
{
return string.IsNullOrEmpty(note) ? null : note;
}
return null;
}
public void SetNote(string note)
{
_serverConfigurationManager.CurrentServer!.UidServerComments[UserData.UID] = note;
_serverConfigurationManager.Save();
}
public bool HasAnyConnection()
{
return UserPair != null || GroupPair.Any();
}
public void InitializePair(nint address, string name)
{
if (!PlayerName.IsNullOrEmpty()) return;
if (CachedPlayer == null) throw new InvalidOperationException("CachedPlayer not initialized");
_pluginWarnings ??= new()
{
ShownCustomizePlusWarning = _configService.Current.DisableOptionalPluginWarnings,
ShownHeelsWarning = _configService.Current.DisableOptionalPluginWarnings,
};
CachedPlayer.Initialize(address, name);
ApplyLastReceivedData();
}
public void ApplyData(OnlineUserCharaDataDto data)
{
if (CachedPlayer == null) throw new InvalidOperationException("CachedPlayer not initialized");
if (string.Equals(LastReceivedCharacterData?.DataHash.Value, data.CharaData.DataHash.Value, StringComparison.Ordinal)) return;
LastReceivedCharacterData = data.CharaData;
ApplyLastReceivedData();
}
public void ApplyLastReceivedData()
{
if (CachedPlayer == null) return;
if (LastReceivedCharacterData == null) return;
_pluginWarnings ??= new()
{
ShownCustomizePlusWarning = _configService.Current.DisableOptionalPluginWarnings,
ShownHeelsWarning = _configService.Current.DisableOptionalPluginWarnings,
};
CachedPlayer.ApplyCharacterData(RemoveNotSyncedFiles(LastReceivedCharacterData.DeepClone())!, _pluginWarnings);
}
private API.Data.CharacterData? RemoveNotSyncedFiles(API.Data.CharacterData? data)
{
Logger.Verbose("Removing not synced files");
if (data == null || (UserPair != null && UserPair.OtherPermissions.IsPaired()))
{
Logger.Verbose("Nothing to remove or user is paired directly");
return data;
}
bool disableAnimations = GroupPair.All(pair =>
{
return pair.Value.GroupUserPermissions.IsDisableAnimations() || pair.Key.GroupPermissions.IsDisableAnimations() || pair.Key.GroupUserPermissions.IsDisableAnimations();
});
bool disableSounds = GroupPair.All(pair =>
{
return pair.Value.GroupUserPermissions.IsDisableSounds() || pair.Key.GroupPermissions.IsDisableSounds() || pair.Key.GroupUserPermissions.IsDisableSounds();
});
if (disableAnimations || disableSounds)
{
Logger.Verbose($"Data cleaned up: Animations disabled: {disableAnimations}, Sounds disabled: {disableSounds}");
foreach (var kvp in data.FileReplacements)
{
if (disableSounds)
data.FileReplacements[kvp.Key] = data.FileReplacements[kvp.Key]
.Where(f => !f.GamePaths.Any(p => p.EndsWith("scd", StringComparison.OrdinalIgnoreCase)))
.ToList();
if (disableAnimations)
data.FileReplacements[kvp.Key] = data.FileReplacements[kvp.Key]
.Where(f => !f.GamePaths.Any(p => p.EndsWith("tmb", StringComparison.OrdinalIgnoreCase) || p.EndsWith("pap", StringComparison.OrdinalIgnoreCase)))
.ToList();
}
}
return data;
}
}

View File

@@ -1,9 +1,8 @@
using System;
using MareSynchronos.API;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using System.Runtime.InteropServices;
using MareSynchronos.Utils;
using Penumbra.String;
using MareSynchronos.API.Data.Enum;
namespace MareSynchronos.Models;

View File

@@ -1,11 +1,9 @@
using Dalamud.Game.Command;
using Dalamud.Plugin;
using MareSynchronos.Factories;
using System.Threading.Tasks;
using Dalamud.Game;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState;
using System;
using Dalamud.Interface.ImGuiFileDialog;
using MareSynchronos.Managers;
using MareSynchronos.WebAPI;
@@ -16,16 +14,16 @@ using Dalamud.Game.ClientState.Conditions;
using MareSynchronos.FileCache;
using Dalamud.Game.Gui;
using MareSynchronos.Export;
using Dalamud.Data;
using MareSynchronos.MareConfiguration;
namespace MareSynchronos;
public sealed class Plugin : IDalamudPlugin
{
private const string CommandName = "/mare";
private const string _commandName = "/mare";
private readonly ApiController _apiController;
private readonly CommandManager _commandManager;
private readonly ChatGui _chatGui;
private readonly Configuration _configuration;
private readonly PeriodicFileScanner _periodicFileScanner;
private readonly IntroUi _introUi;
private readonly IpcManager _ipcManager;
@@ -39,49 +37,52 @@ public sealed class Plugin : IDalamudPlugin
private readonly DownloadUi _downloadUi;
private readonly FileDialogManager _fileDialogManager;
private readonly FileCacheManager _fileCacheManager;
private readonly PairManager _pairManager;
private readonly CompactUi _compactUi;
private readonly UiShared _uiSharedComponent;
private readonly Dalamud.Localization _localization;
private readonly FileReplacementFactory _fileReplacementFactory;
private readonly MareCharaFileManager _mareCharaFileManager;
private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly GposeUi _gposeUi;
private readonly ConfigurationService _configurationService;
public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager,
Framework framework, ObjectTable objectTable, ClientState clientState, Condition condition,
ChatGui chatGui)
public Plugin(DalamudPluginInterface pluginInterface, CommandManager commandManager, DataManager gameData,
Framework framework, ObjectTable objectTable, ClientState clientState, Condition condition, ChatGui chatGui)
{
Logger.Debug("Launching " + Name);
_pluginInterface = pluginInterface;
_pluginInterface.UiBuilder.DisableGposeUiHide = true;
_commandManager = commandManager;
_configuration = _pluginInterface.GetPluginConfig() as Configuration ?? new Configuration();
_configuration.Initialize(_pluginInterface);
_configuration.Migrate();
_configurationService = new(_pluginInterface);
_localization = new Dalamud.Localization("MareSynchronos.Localization.", "", true);
_localization = new Dalamud.Localization("MareSynchronos.Localization.", "", useEmbedded: true);
_localization.SetupWithLangCode("en");
_windowSystem = new WindowSystem("MareSynchronos");
// those can be initialized outside of game login
_dalamudUtil = new DalamudUtil(clientState, objectTable, framework, condition, chatGui);
_dalamudUtil = new DalamudUtil(clientState, objectTable, framework, condition, chatGui, gameData);
_ipcManager = new IpcManager(_pluginInterface, _dalamudUtil);
_fileDialogManager = new FileDialogManager();
_fileCacheManager = new FileCacheManager(_ipcManager, _configuration, _pluginInterface.ConfigDirectory.FullName);
_apiController = new ApiController(_configuration, _dalamudUtil, _fileCacheManager);
_periodicFileScanner = new PeriodicFileScanner(_ipcManager, _configuration, _fileCacheManager, _apiController, _dalamudUtil);
_fileCacheManager = new FileCacheManager(_ipcManager, _configurationService);
_serverConfigurationManager = new ServerConfigurationManager(_configurationService, _dalamudUtil);
_pairManager = new PairManager(new CachedPlayerFactory(_ipcManager, _dalamudUtil, _fileCacheManager), _dalamudUtil,
new PairFactory(_configurationService, _serverConfigurationManager), _pluginInterface.UiBuilder, _configurationService);
_apiController = new ApiController(_configurationService, _dalamudUtil, _fileCacheManager, _pairManager, _serverConfigurationManager);
_periodicFileScanner = new PeriodicFileScanner(_ipcManager, _configurationService, _fileCacheManager, _apiController, _dalamudUtil);
_fileReplacementFactory = new FileReplacementFactory(_fileCacheManager, _ipcManager);
_mareCharaFileManager = new(_fileCacheManager, _ipcManager, _configuration, _dalamudUtil);
_mareCharaFileManager = new(_fileCacheManager, _ipcManager, _configurationService, _dalamudUtil);
_uiSharedComponent =
new UiShared(_ipcManager, _apiController, _periodicFileScanner, _fileDialogManager, _configuration, _dalamudUtil, _pluginInterface, _localization);
_settingsUi = new SettingsUi(_windowSystem, _uiSharedComponent, _configuration, _apiController, _mareCharaFileManager);
_compactUi = new CompactUi(_windowSystem, _uiSharedComponent, _configuration, _apiController);
_gposeUi = new GposeUi(_windowSystem, _mareCharaFileManager, _dalamudUtil, _fileDialogManager, _configuration);
new UiShared(_ipcManager, _apiController, _periodicFileScanner, _fileDialogManager, _configurationService, _dalamudUtil, _pluginInterface, _localization, _serverConfigurationManager);
_settingsUi = new SettingsUi(_windowSystem, _uiSharedComponent, _configurationService, _mareCharaFileManager, _pairManager, _serverConfigurationManager);
_compactUi = new CompactUi(_windowSystem, _uiSharedComponent, _configurationService, _apiController, _pairManager, _serverConfigurationManager);
_gposeUi = new GposeUi(_windowSystem, _mareCharaFileManager, _dalamudUtil, _fileDialogManager, _configurationService);
_introUi = new IntroUi(_windowSystem, _uiSharedComponent, _configuration, _periodicFileScanner);
_introUi = new IntroUi(_windowSystem, _uiSharedComponent, _configurationService, _periodicFileScanner, _serverConfigurationManager);
_settingsUi.SwitchToIntroUi += () =>
{
_introUi.IsOpen = true;
@@ -99,8 +100,7 @@ public sealed class Plugin : IDalamudPlugin
{
_settingsUi.Toggle();
};
_downloadUi = new DownloadUi(_windowSystem, _configuration, _apiController, _uiSharedComponent);
_downloadUi = new DownloadUi(_windowSystem, _configurationService, _apiController, _uiSharedComponent);
_dalamudUtil.LogIn += DalamudUtilOnLogIn;
_dalamudUtil.LogOut += DalamudUtilOnLogOut;
@@ -112,12 +112,13 @@ public sealed class Plugin : IDalamudPlugin
}
public string Name => "Mare Synchronos";
public void Dispose()
{
Logger.Verbose("Disposing " + Name);
_apiController?.Dispose();
_commandManager.RemoveHandler(CommandName);
_commandManager.RemoveHandler(_commandName);
_dalamudUtil.LogIn -= DalamudUtilOnLogIn;
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
@@ -128,6 +129,7 @@ public sealed class Plugin : IDalamudPlugin
_compactUi?.Dispose();
_gposeUi?.Dispose();
_pairManager.Dispose();
_periodicFileScanner?.Dispose();
_fileCacheManager?.Dispose();
_playerManager?.Dispose();
@@ -135,6 +137,7 @@ public sealed class Plugin : IDalamudPlugin
_ipcManager?.Dispose();
_transientResourceManager?.Dispose();
_dalamudUtil.Dispose();
_configurationService?.Dispose();
Logger.Debug("Shut down");
}
@@ -145,16 +148,15 @@ public sealed class Plugin : IDalamudPlugin
_pluginInterface.UiBuilder.Draw += Draw;
_pluginInterface.UiBuilder.OpenConfigUi += OpenUi;
_commandManager.AddHandler(CommandName, new CommandInfo(OnCommand)
_commandManager.AddHandler(_commandName, new CommandInfo(OnCommand)
{
HelpMessage = "Opens the Mare Synchronos UI"
HelpMessage = "Opens the Mare Synchronos UI",
});
if (!_configuration.HasValidSetup())
if (!_configurationService.Current.HasValidSetup() || !_serverConfigurationManager.HasValidConfig())
{
_introUi.IsOpen = true;
_configuration.FullPause = false;
_configuration.Save();
_compactUi.IsOpen = false;
return;
}
@@ -170,7 +172,7 @@ public sealed class Plugin : IDalamudPlugin
_transientResourceManager?.Dispose();
_pluginInterface.UiBuilder.Draw -= Draw;
_pluginInterface.UiBuilder.OpenConfigUi -= OpenUi;
_commandManager.RemoveHandler(CommandName);
_commandManager.RemoveHandler(_commandName);
}
public void ReLaunchCharacterManager()
@@ -191,17 +193,18 @@ public sealed class Plugin : IDalamudPlugin
try
{
_transientResourceManager = new TransientResourceManager(_ipcManager, _dalamudUtil, _fileReplacementFactory, _pluginInterface.ConfigDirectory.FullName);
Logger.Debug("Launching Managers");
_transientResourceManager = new TransientResourceManager(_ipcManager, _configurationService, _dalamudUtil, _fileReplacementFactory);
var characterCacheFactory =
new CharacterDataFactory(_dalamudUtil, _ipcManager, _transientResourceManager, _fileReplacementFactory);
_playerManager = new PlayerManager(_apiController, _ipcManager,
characterCacheFactory, _dalamudUtil, _transientResourceManager, _periodicFileScanner, _settingsUi);
_characterCacheManager = new OnlinePlayerManager(_apiController,
_dalamudUtil, _ipcManager, _playerManager, _fileCacheManager, _configuration);
_dalamudUtil, _playerManager, _fileCacheManager, _pairManager);
}
catch (Exception ex)
{
Logger.Debug(ex.Message);
Logger.Warn(ex.Message);
}
}
@@ -222,23 +225,24 @@ public sealed class Plugin : IDalamudPlugin
return;
}
if (splitArgs[0] == "toggle")
if (string.Equals(splitArgs[0], "toggle", StringComparison.OrdinalIgnoreCase))
{
if (_serverConfigurationManager.CurrentServer == null) return;
var fullPause = splitArgs.Length > 1 ? splitArgs[1] switch
{
"on" => false,
"off" => true,
_ => !_configuration.FullPause,
} : !_configuration.FullPause;
_ => !_serverConfigurationManager.CurrentServer.FullPause,
} : !_serverConfigurationManager.CurrentServer.FullPause;
if (fullPause != _configuration.FullPause)
if (fullPause != _serverConfigurationManager.CurrentServer.FullPause)
{
_configuration.FullPause = fullPause;
_configuration.Save();
_serverConfigurationManager.CurrentServer.FullPause = fullPause;
_serverConfigurationManager.Save();
_ = _apiController.CreateConnections();
}
}
else if (splitArgs[0] == "gpose")
else if (string.Equals(splitArgs[0], "gpose", StringComparison.OrdinalIgnoreCase))
{
_gposeUi.Toggle();
}
@@ -246,7 +250,7 @@ public sealed class Plugin : IDalamudPlugin
private void OpenUi()
{
if (_configuration.HasValidSetup())
if (_configurationService.Current.HasValidSetup())
_compactUi.Toggle();
else
_introUi.Toggle();

View File

@@ -1,8 +1,5 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Diagnostics;
using System.Globalization;
using System.Linq;
using System.Numerics;
using System.Reflection;
using Dalamud.Interface;
@@ -11,7 +8,12 @@ using Dalamud.Interface.Components;
using Dalamud.Interface.Windowing;
using Dalamud.Utility;
using ImGuiNET;
using MareSynchronos.API;
using MareSynchronos.API.Data.Extensions;
using MareSynchronos.API.Dto.User;
using MareSynchronos.Delegates;
using MareSynchronos.Managers;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Models;
using MareSynchronos.UI.Components;
using MareSynchronos.UI.Handlers;
using MareSynchronos.Utils;
@@ -22,7 +24,9 @@ namespace MareSynchronos.UI;
public class CompactUi : Window, IDisposable
{
private readonly ApiController _apiController;
private readonly Configuration _configuration;
private readonly PairManager _pairManager;
private readonly ServerConfigurationManager _serverManager;
private readonly ConfigurationService _configService;
private readonly TagHandler _tagHandler;
public readonly Dictionary<string, bool> ShowUidForEntry = new(StringComparer.Ordinal);
private readonly UiShared _uiShared;
@@ -37,14 +41,14 @@ public class CompactUi : Window, IDisposable
private readonly Stopwatch _timeout = new();
private bool _buttonState;
public float TransferPartHeight = 0;
public float _windowContentWidth = 0;
private bool _showModalForUserAddition = false;
private bool _wasOpen = false;
public float TransferPartHeight;
public float WindowContentWidth;
private bool _showModalForUserAddition;
private bool _wasOpen;
private bool showSyncShells = false;
private GroupPanel groupPanel;
private ClientPairDto? _lastAddedUser;
private bool _showSyncShells;
private readonly GroupPanel _groupPanel;
private UserPairDto? _lastAddedUser;
private string _lastAddedUserComment = string.Empty;
private readonly SelectGroupForPairUi _selectGroupForPairUi;
@@ -52,7 +56,8 @@ public class CompactUi : Window, IDisposable
private readonly PairGroupsUi _pairGroupsUi;
public CompactUi(WindowSystem windowSystem,
UiShared uiShared, Configuration configuration, ApiController apiController) : base("###MareSynchronosMainUI")
UiShared uiShared, ConfigurationService configService, ApiController apiController, PairManager pairManager,
ServerConfigurationManager serverManager) : base("###MareSynchronosMainUI")
{
#if DEBUG
@@ -77,13 +82,15 @@ public class CompactUi : Window, IDisposable
_windowSystem = windowSystem;
_uiShared = uiShared;
_configuration = configuration;
_configService = configService;
_apiController = apiController;
_tagHandler = new(_configuration);
_pairManager = pairManager;
_serverManager = serverManager;
_tagHandler = new(_serverManager);
groupPanel = new(this, uiShared, configuration, apiController);
_selectGroupForPairUi = new(_tagHandler, configuration);
_selectPairsForGroupUi = new(_tagHandler, configuration);
_groupPanel = new(this, uiShared, _pairManager, _serverManager);
_selectGroupForPairUi = new(_tagHandler);
_selectPairsForGroupUi = new(_tagHandler);
_pairGroupsUi = new(_tagHandler, DrawPairedClient, apiController, _selectPairsForGroupUi);
_uiShared.GposeStart += UiShared_GposeStart;
@@ -109,7 +116,7 @@ public class CompactUi : Window, IDisposable
IsOpen = false;
}
public event SwitchUi? OpenSettingsUi;
public event VoidDelegate? OpenSettingsUi;
public void Dispose()
{
Logger.Verbose("Disposing " + nameof(CompactUi));
@@ -120,14 +127,14 @@ public class CompactUi : Window, IDisposable
public override void Draw()
{
_windowContentWidth = UiShared.GetWindowContentRegionWidth();
WindowContentWidth = UiShared.GetWindowContentRegionWidth();
UiShared.DrawWithID("header", DrawUIDHeader);
ImGui.Separator();
UiShared.DrawWithID("serverstatus", DrawServerStatus);
if (_apiController.ServerState is ServerState.Connected)
{
var hasShownSyncShells = showSyncShells;
var hasShownSyncShells = _showSyncShells;
ImGui.PushFont(UiBuilder.IconFont);
if (!hasShownSyncShells)
@@ -136,7 +143,7 @@ public class CompactUi : Window, IDisposable
}
if (ImGui.Button(FontAwesomeIcon.User.ToIconString(), new Vector2((UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X) / 2, 30 * ImGuiHelpers.GlobalScale)))
{
showSyncShells = false;
_showSyncShells = false;
}
if (!hasShownSyncShells)
{
@@ -154,7 +161,7 @@ public class CompactUi : Window, IDisposable
}
if (ImGui.Button(FontAwesomeIcon.UserFriends.ToIconString(), new Vector2((UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X) / 2, 30 * ImGuiHelpers.GlobalScale)))
{
showSyncShells = true;
_showSyncShells = true;
}
if (hasShownSyncShells)
{
@@ -171,17 +178,17 @@ public class CompactUi : Window, IDisposable
}
else
{
UiShared.DrawWithID("syncshells", groupPanel.DrawSyncshells);
UiShared.DrawWithID("syncshells", _groupPanel.DrawSyncshells);
}
ImGui.Separator();
UiShared.DrawWithID("transfers", DrawTransfers);
TransferPartHeight = ImGui.GetCursorPosY() - TransferPartHeight;
UiShared.DrawWithID("group-user-popup", () => _selectPairsForGroupUi.Draw(_apiController.PairedClients, ShowUidForEntry));
UiShared.DrawWithID("group-user-popup", () => _selectPairsForGroupUi.Draw(_pairManager.DirectPairs, ShowUidForEntry));
UiShared.DrawWithID("grouping-popup", () => _selectGroupForPairUi.Draw(ShowUidForEntry));
}
if (_configuration.OpenPopupOnAdd && _apiController.LastAddedUser != null)
if (_configService.Current.OpenPopupOnAdd && _apiController.LastAddedUser != null)
{
_lastAddedUser = _apiController.LastAddedUser;
_apiController.LastAddedUser = null;
@@ -198,15 +205,14 @@ public class CompactUi : Window, IDisposable
}
else
{
var uid = string.IsNullOrEmpty(_lastAddedUser!.VanityUID) ? _lastAddedUser.OtherUID : _lastAddedUser.VanityUID;
UiShared.TextWrapped($"You have successfully added {uid}. Set a local note for the user in the field below:");
ImGui.InputTextWithHint("##noteforuser", $"Note for {uid}", ref _lastAddedUserComment, 100);
UiShared.TextWrapped($"You have successfully added {_lastAddedUser.User.AliasOrUID}. Set a local note for the user in the field below:");
ImGui.InputTextWithHint("##noteforuser", $"Note for {_lastAddedUser.User.AliasOrUID}", ref _lastAddedUserComment, 100);
if (UiShared.IconTextButton(FontAwesomeIcon.Save, "Save Note"))
{
_configuration.SetCurrentServerUidComment(_lastAddedUser.OtherUID, _lastAddedUserComment);
_serverManager.CurrentServer!.UidServerComments[_lastAddedUser.User.UID] = _lastAddedUserComment;
_serverManager.Save();
_lastAddedUser = null;
_lastAddedUserComment = string.Empty;
_configuration.Save();
_showModalForUserAddition = false;
}
}
@@ -221,21 +227,27 @@ public class CompactUi : Window, IDisposable
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/Alias", ref _pairToAdd, 20);
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - buttonSize.X);
var canAdd = !_pairManager.DirectPairs.Any(p => string.Equals(p.UserData.UID, _pairToAdd, StringComparison.Ordinal) || string.Equals(p.UserData.Alias, _pairToAdd, StringComparison.Ordinal));
if (!canAdd)
{
ImGuiComponents.DisabledButton(FontAwesomeIcon.Plus);
}
else
{
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
{
if (_apiController.PairedClients.All(w => !string.Equals(w.OtherUID, _pairToAdd, StringComparison.Ordinal)))
{
_ = _apiController.UserAddPair(_pairToAdd);
_ = _apiController.UserAddPair(new(new(_pairToAdd)));
_pairToAdd = string.Empty;
}
}
UiShared.AttachToolTip("Pair with " + (_pairToAdd.IsNullOrEmpty() ? "other user" : _pairToAdd));
}
ImGuiHelpers.ScaledDummy(2);
}
@@ -244,12 +256,12 @@ public class CompactUi : Window, IDisposable
{
var buttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.ArrowUp);
var playButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Play);
if (!_configuration.ReverseUserSort)
if (!_configService.Current.ReverseUserSort)
{
if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowDown))
{
_configuration.ReverseUserSort = true;
_configuration.Save();
_configService.Current.ReverseUserSort = true;
_configService.Save();
}
UiShared.AttachToolTip("Sort by name descending");
}
@@ -257,28 +269,30 @@ public class CompactUi : Window, IDisposable
{
if (ImGuiComponents.IconButton(FontAwesomeIcon.ArrowUp))
{
_configuration.ReverseUserSort = false;
_configuration.Save();
_configService.Current.ReverseUserSort = false;
_configService.Save();
}
UiShared.AttachToolTip("Sort by name ascending");
}
ImGui.SameLine();
var users = GetFilteredUsers().ToList();
var users = GetFilteredUsers();
var userCount = users.Count;
var spacing = userCount > 0
? playButtonSize.X + ImGui.GetStyle().ItemSpacing.X * 2
: ImGui.GetStyle().ItemSpacing.X;
ImGui.SetNextItemWidth(_windowContentWidth - buttonSize.X - spacing);
ImGui.SetNextItemWidth(WindowContentWidth - buttonSize.X - spacing);
ImGui.InputTextWithHint("##filter", "Filter for UID/notes", ref _characterOrCommentFilter, 255);
if (userCount == 0) return;
ImGui.SameLine();
var pausedUsers = users.Where(u => u.IsPaused).ToList();
var resumedUsers = users.Where(u => !u.IsPaused).ToList();
var pausedUsers = users.Where(u => u.UserPair!.OwnPermissions.IsPaused() && u.UserPair.OtherPermissions.IsPaired()).ToList();
var resumedUsers = users.Where(u => !u.UserPair!.OwnPermissions.IsPaused() && u.UserPair.OtherPermissions.IsPaired()).ToList();
if (!pausedUsers.Any() && !resumedUsers.Any()) return;
ImGui.SameLine();
switch (_buttonState)
{
@@ -306,10 +320,11 @@ public class CompactUi : Window, IDisposable
{
if (UiShared.CtrlPressed())
{
Logger.Debug(users.Count.ToString());
foreach (var entry in users)
{
_ = _apiController.UserChangePairPauseStatus(entry.OtherUID, !entry.IsPaused);
var perm = entry.UserPair!.OwnPermissions;
perm.SetPaused(!perm.IsPaused());
_ = _apiController.UserSetPairPermissions(new UserPermissionsDto(entry.UserData, perm));
}
_timeout.Start();
@@ -326,49 +341,72 @@ public class CompactUi : Window, IDisposable
}
}
private void DrawPairedClient(ClientPairDto entry)
private void DrawPairedClient(Pair entry)
{
var pauseIcon = entry.IsPaused ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
if (entry.UserPair == null) return;
var pauseIcon = entry.UserPair!.OwnPermissions.IsPaused() ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
var pauseIconSize = UiShared.GetIconButtonSize(pauseIcon);
var barButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Bars);
var entryUID = string.IsNullOrEmpty(entry.VanityUID) ? entry.OtherUID : entry.VanityUID;
var entryUID = entry.UserData.AliasOrUID;
var textSize = ImGui.CalcTextSize(entryUID);
var originalY = ImGui.GetCursorPosY();
var buttonSizes = pauseIconSize.Y + barButtonSize.Y;
var spacingX = ImGui.GetStyle().ItemSpacing.X;
var windowEndX = ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth();
var textPos = originalY + pauseIconSize.Y / 2 - textSize.Y / 2;
ImGui.SetCursorPosY(textPos);
if (!entry.IsSynced)
FontAwesomeIcon presenceIcon;
FontAwesomeIcon connectionIcon;
string connectionText = string.Empty;
string presenceText = string.Empty;
Vector4 presenceColor;
Vector4 connectionColor;
if (!(entry.UserPair!.OwnPermissions.IsPaired() && entry.UserPair!.OtherPermissions.IsPaired()))
{
ImGui.PushFont(UiBuilder.IconFont);
UiShared.ColorText(FontAwesomeIcon.ArrowUp.ToIconString(), ImGuiColors.DalamudRed);
ImGui.PopFont();
UiShared.AttachToolTip(entryUID + " has not added you back");
connectionIcon = FontAwesomeIcon.ArrowUp;
connectionText = entryUID + " has not added you back";
connectionColor = ImGuiColors.DalamudRed;
presenceIcon = FontAwesomeIcon.Question;
presenceColor = ImGuiColors.DalamudGrey;
presenceText = entryUID + " online status is unknown (not paired)";
}
else if (entry.IsPaused || entry.IsPausedFromOthers)
else if (entry.UserPair!.OwnPermissions.IsPaused() || entry.UserPair!.OtherPermissions.IsPaused())
{
ImGui.PushFont(UiBuilder.IconFont);
UiShared.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow);
ImGui.PopFont();
UiShared.AttachToolTip("Pairing status with " + entryUID + " is paused");
connectionIcon = FontAwesomeIcon.PauseCircle;
connectionText = "Pairing status with " + entryUID + " is paused";
connectionColor = ImGuiColors.DalamudYellow;
presenceIcon = FontAwesomeIcon.Question;
presenceColor = ImGuiColors.DalamudGrey;
presenceText = entryUID + " online status is unknown (paused)";
}
else
{
ImGui.PushFont(UiBuilder.IconFont);
UiShared.ColorText(FontAwesomeIcon.Check.ToIconString(), ImGuiColors.ParsedGreen);
ImGui.PopFont();
UiShared.AttachToolTip("You are paired with " + entryUID);
connectionIcon = FontAwesomeIcon.Check;
connectionText = "You are paired with " + entryUID;
connectionColor = ImGuiColors.ParsedGreen;
presenceIcon = entry.IsVisible ? FontAwesomeIcon.Eye : (entry.IsOnline ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink);
presenceColor = (entry.IsOnline || entry.IsVisible) ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed;
presenceText = entryUID + " is offline";
if (entry.IsOnline && !entry.IsVisible) presenceText = entryUID + " is online";
else if (entry.IsOnline && entry.IsVisible) presenceText = entryUID + " is visible: " + entry.PlayerName;
}
ImGui.PushFont(UiBuilder.IconFont);
UiShared.ColorText(connectionIcon.ToIconString(), connectionColor);
ImGui.PopFont();
UiShared.AttachToolTip(connectionText);
ImGui.SameLine();
ImGui.SetCursorPosY(textPos);
ImGui.PushFont(UiBuilder.IconFont);
UiShared.ColorText(presenceIcon.ToIconString(), presenceColor);
ImGui.PopFont();
UiShared.AttachToolTip(presenceText);
var textIsUid = true;
ShowUidForEntry.TryGetValue(entry.OtherUID, out var showUidInsteadOfName);
if (!showUidInsteadOfName && _configuration.GetCurrentServerUidComments().TryGetValue(entry.OtherUID, out var playerText))
ShowUidForEntry.TryGetValue(entry.UserPair!.User.UID, out var showUidInsteadOfName);
if (!showUidInsteadOfName && _serverManager.CurrentServer!.UidServerComments.TryGetValue(entry.UserPair!.User.UID, out var playerText))
{
if (string.IsNullOrEmpty(playerText))
{
@@ -385,7 +423,7 @@ public class CompactUi : Window, IDisposable
}
ImGui.SameLine();
if (!string.Equals(EditNickEntry, entry.OtherUID, StringComparison.Ordinal))
if (!string.Equals(EditNickEntry, entry.UserData.UID, StringComparison.Ordinal))
{
ImGui.SetCursorPosY(textPos);
if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont);
@@ -396,22 +434,20 @@ public class CompactUi : Window, IDisposable
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{
var prevState = textIsUid;
if (ShowUidForEntry.ContainsKey(entry.OtherUID))
if (ShowUidForEntry.ContainsKey(entry.UserPair!.User.UID))
{
prevState = ShowUidForEntry[entry.OtherUID];
prevState = ShowUidForEntry[entry.UserPair!.User.UID];
}
ShowUidForEntry[entry.OtherUID] = !prevState;
ShowUidForEntry[entry.UserPair!.User.UID] = !prevState;
}
if (ImGui.IsItemClicked(ImGuiMouseButton.Right))
{
_configuration.SetCurrentServerUidComment(EditNickEntry, EditUserComment);
_configuration.Save();
EditUserComment = _configuration.GetCurrentServerUidComments().ContainsKey(entry.OtherUID)
? _configuration.GetCurrentServerUidComments()[entry.OtherUID]
: string.Empty;
EditNickEntry = entry.OtherUID;
entry.SetNote(EditUserComment);
_configService.Save();
EditUserComment = entry.GetNote() ?? string.Empty;
EditNickEntry = entry.UserPair!.User.UID;
}
}
else
@@ -421,8 +457,8 @@ public class CompactUi : Window, IDisposable
ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * 2);
if (ImGui.InputTextWithHint("", "Nick/Notes", ref EditUserComment, 255, ImGuiInputTextFlags.EnterReturnsTrue))
{
_configuration.SetCurrentServerUidComment(entry.OtherUID, EditUserComment);
_configuration.Save();
_serverManager.CurrentServer!.UidServerComments[entry.UserPair!.User.UID] = EditUserComment;
_serverManager.Save();
EditNickEntry = string.Empty;
}
@@ -434,15 +470,17 @@ public class CompactUi : Window, IDisposable
}
// Pause Button
if (entry.IsSynced)
if (entry.UserPair!.OwnPermissions.IsPaired() && entry.UserPair!.OtherPermissions.IsPaired())
{
ImGui.SameLine(windowEndX - barButtonSize.X - spacingX - pauseIconSize.X);
ImGui.SetCursorPosY(originalY);
if (ImGuiComponents.IconButton(pauseIcon))
{
_ = _apiController.UserChangePairPauseStatus(entry.OtherUID, !entry.IsPaused);
var perm = entry.UserPair!.OwnPermissions;
perm.SetPaused(!perm.IsPaused());
_ = _apiController.UserSetPairPermissions(new(entry.UserData, perm));
}
UiShared.AttachToolTip(!entry.IsPaused
UiShared.AttachToolTip(!entry.UserPair!.OwnPermissions.IsPaused()
? "Pause pairing with " + entryUID
: "Resume pairing with " + entryUID);
}
@@ -457,14 +495,14 @@ public class CompactUi : Window, IDisposable
}
if (ImGui.BeginPopup("User Flyout Menu"))
{
UiShared.DrawWithID($"buttons-{entry.OtherUID}", () => DrawPairedClientMenu(entry));
UiShared.DrawWithID($"buttons-{entry.UserPair!.User.UID}", () => DrawPairedClientMenu(entry));
ImGui.EndPopup();
}
}
private void DrawPairedClientMenu(ClientPairDto entry)
private void DrawPairedClientMenu(Pair entry)
{
var entryUID = string.IsNullOrEmpty(entry.VanityUID) ? entry.OtherUID : entry.VanityUID;
var entryUID = entry.UserData.AliasOrUID;
if (UiShared.IconTextButton(FontAwesomeIcon.Folder, "Pair Groups"))
{
_selectGroupForPairUi.Open(entry);
@@ -475,7 +513,7 @@ public class CompactUi : Window, IDisposable
{
if (UiShared.CtrlPressed())
{
_ = _apiController.UserRemovePair(entry.OtherUID);
_ = _apiController.UserRemovePair(new(entry.UserData));
}
}
UiShared.AttachToolTip("Hold CTRL and click to unpair permanently from " + entryUID);
@@ -496,32 +534,65 @@ public class CompactUi : Window, IDisposable
: (ImGui.GetWindowContentRegionMax().Y - ImGui.GetWindowContentRegionMin().Y) - TransferPartHeight - ImGui.GetCursorPosY();
var users = GetFilteredUsers();
users = users.OrderBy(u => _configuration.GetCurrentServerUidComments().ContainsKey(u.OtherUID) ? _configuration.GetCurrentServerUidComments()[u.OtherUID] : !string.IsNullOrEmpty(u.VanityUID) ? u.VanityUID : u.OtherUID);
if (_configuration.ReverseUserSort) users = users.Reverse();
ImGui.BeginChild("list", new Vector2(WindowContentWidth, ySize), border: false);
var visibleUsers = users.Where(u => u.IsVisible && u.UserPair!.OtherPermissions.IsPaired()).OrderBy(u => u.GetNote() ?? u.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase).ToList();
var onlineUsers = users.Where(u => u.IsOnline && !u.IsVisible && u.UserPair!.OtherPermissions.IsPaired()).OrderBy(u => u.GetNote() ?? u.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase).ToList();
var offlineUsers = users.Where(u => !u.IsOnline && !u.IsVisible || !u.UserPair!.OtherPermissions.IsPaired()).OrderBy(u => u.GetNote() ?? u.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase).ToList();
ImGui.BeginChild("list", new Vector2(_windowContentWidth, ySize), false);
var allAvailablePairs = users.ToList();
var pairsWithoutTags = allAvailablePairs.Where(pair => !_tagHandler.HasAnyTag(pair));
_pairGroupsUi.Draw(allAvailablePairs);
foreach (var entry in pairsWithoutTags)
if (_configService.Current.ReverseUserSort)
{
UiShared.DrawWithID(entry.OtherUID, () => DrawPairedClient(entry));
visibleUsers.Reverse();
onlineUsers.Reverse();
offlineUsers.Reverse();
}
_pairGroupsUi.Draw(visibleUsers, onlineUsers, offlineUsers);
visibleUsers = visibleUsers.Where(pair => !_tagHandler.HasAnyTag(pair.UserPair!)).ToList();
onlineUsers = onlineUsers.Where(pair => !_tagHandler.HasAnyTag(pair.UserPair!)).ToList();
offlineUsers = offlineUsers.Where(pair => !_tagHandler.HasAnyTag(pair.UserPair!)).ToList();
if (visibleUsers.Any())
{
ImGui.Text("Visible");
ImGui.Separator();
foreach (var entry in visibleUsers)
{
UiShared.DrawWithID(entry.UserData.UID, () => DrawPairedClient(entry));
}
}
if (onlineUsers.Any())
{
ImGui.Text("Online");
ImGui.Separator();
foreach (var entry in onlineUsers)
{
UiShared.DrawWithID(entry.UserData.UID, () => DrawPairedClient(entry));
}
}
if (offlineUsers.Any())
{
ImGui.Text("Offline/Unknown");
ImGui.Separator();
foreach (var entry in offlineUsers)
{
UiShared.DrawWithID(entry.UserData.UID, () => DrawPairedClient(entry));
}
}
ImGui.EndChild();
}
private IEnumerable<ClientPairDto> GetFilteredUsers()
private List<Pair> GetFilteredUsers()
{
return _apiController.PairedClients.Where(p =>
return _pairManager.DirectPairs.Where(p =>
{
if (_characterOrCommentFilter.IsNullOrEmpty()) return true;
_configuration.GetCurrentServerUidComments().TryGetValue(p.OtherUID, out var comment);
var uid = p.VanityUID.IsNullOrEmpty() ? p.OtherUID : p.VanityUID;
return uid.Contains(_characterOrCommentFilter, StringComparison.OrdinalIgnoreCase) ||
(comment?.Contains(_characterOrCommentFilter, StringComparison.OrdinalIgnoreCase) ?? false);
});
return p.UserData.AliasOrUID.Contains(_characterOrCommentFilter, StringComparison.OrdinalIgnoreCase) ||
(p.GetNote()?.Contains(_characterOrCommentFilter, StringComparison.OrdinalIgnoreCase) ?? false);
}).ToList();
}
private void DrawServerStatus()
@@ -565,18 +636,18 @@ public class CompactUi : Window, IDisposable
{
ImGui.SetCursorPosY(ImGui.GetCursorPosY() - ((userSize.Y + textSize.Y) / 2 + shardTextSize.Y) / 2 - ImGui.GetStyle().ItemSpacing.Y + buttonSize.Y / 2);
}
var color = UiShared.GetBoolColor(!_configuration.FullPause);
var connectedIcon = !_configuration.FullPause ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink;
var color = UiShared.GetBoolColor(!_serverManager.CurrentServer!.FullPause);
var connectedIcon = !_serverManager.CurrentServer.FullPause ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink;
ImGui.PushStyleColor(ImGuiCol.Text, color);
if (ImGuiComponents.IconButton(connectedIcon))
{
_configuration.FullPause = !_configuration.FullPause;
_configuration.Save();
_serverManager.CurrentServer.FullPause = !_serverManager.CurrentServer.FullPause;
_serverManager.Save();
_ = _apiController.CreateConnections();
}
ImGui.PopStyleColor();
UiShared.AttachToolTip(!_configuration.FullPause ? "Disconnect from " + _apiController.ServerDictionary[_configuration.ApiUri] : "Connect to " + _apiController.ServerDictionary[_configuration.ApiUri]);
UiShared.AttachToolTip(!_serverManager.CurrentServer.FullPause ? "Disconnect from " + _serverManager.CurrentServer.ServerName : "Connect to " + _serverManager.CurrentServer.ServerName);
}
private void DrawTransfers()
@@ -598,7 +669,7 @@ public class CompactUi : Window, IDisposable
ImGui.Text($"{doneUploads}/{totalUploads}");
var uploadText = $"({UiShared.ByteToString(totalUploaded)}/{UiShared.ByteToString(totalToUpload)})";
var textSize = ImGui.CalcTextSize(uploadText);
ImGui.SameLine(_windowContentWidth - textSize.X);
ImGui.SameLine(WindowContentWidth - textSize.X);
ImGui.Text(uploadText);
}
else
@@ -623,7 +694,7 @@ public class CompactUi : Window, IDisposable
var downloadText =
$"({UiShared.ByteToString(totalDownloaded)}/{UiShared.ByteToString(totalToDownload)})";
var textSize = ImGui.CalcTextSize(downloadText);
ImGui.SameLine(_windowContentWidth - textSize.X);
ImGui.SameLine(WindowContentWidth - textSize.X);
ImGui.Text(downloadText);
}
else
@@ -688,6 +759,8 @@ public class CompactUi : Window, IDisposable
{
return _apiController.ServerState switch
{
ServerState.Connecting => "Attempting to connect to the server.",
ServerState.Reconnecting => "Connection to server interrupted, attempting to reconnect to the server.",
ServerState.Disconnected => "You are currently disconnected from the Mare Synchronos server.",
ServerState.Unauthorized => "Server Response: " + _apiController.AuthFailureMessage,
ServerState.Offline => "Your selected Mare Synchronos server is currently offline.",
@@ -695,6 +768,7 @@ public class CompactUi : Window, IDisposable
"Your plugin or the server you are connecting to is out of date. Please update your plugin now. If you already did so, contact the server provider to update their server to the latest version.",
ServerState.RateLimited => "You are rate limited for (re)connecting too often. Disconnect, wait 10 minutes and try again.",
ServerState.Connected => string.Empty,
ServerState.NoSecretKey => "You have no secret key set for this current character. Open the settings and set a secret key. You can reuse one secret key for different characters.",
_ => string.Empty
};
}
@@ -703,12 +777,15 @@ public class CompactUi : Window, IDisposable
{
return _apiController.ServerState switch
{
ServerState.Connecting => ImGuiColors.DalamudYellow,
ServerState.Reconnecting => ImGuiColors.DalamudRed,
ServerState.Connected => ImGuiColors.ParsedGreen,
ServerState.Disconnected => ImGuiColors.DalamudYellow,
ServerState.Unauthorized => ImGuiColors.DalamudRed,
ServerState.VersionMisMatch => ImGuiColors.DalamudRed,
ServerState.Offline => ImGuiColors.DalamudRed,
ServerState.RateLimited => ImGuiColors.DalamudYellow,
ServerState.NoSecretKey => ImGuiColors.DalamudYellow,
_ => ImGuiColors.DalamudRed
};
}
@@ -717,11 +794,14 @@ public class CompactUi : Window, IDisposable
{
return _apiController.ServerState switch
{
ServerState.Reconnecting => "Reconnecting",
ServerState.Connecting => "Connecting",
ServerState.Disconnected => "Disconnected",
ServerState.Unauthorized => "Unauthorized",
ServerState.VersionMisMatch => "Version mismatch",
ServerState.Offline => "Unavailable",
ServerState.RateLimited => "Rate Limited",
ServerState.NoSecretKey => "No Secret Key",
ServerState.Connected => _apiController.UID,
_ => string.Empty
};

View File

@@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Linq;
using Dalamud.Interface;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using ImGuiNET;
using MareSynchronos.API;
using MareSynchronos.API.Data.Extensions;
using MareSynchronos.Models;
using MareSynchronos.UI.Handlers;
using MareSynchronos.WebAPI;
@@ -12,12 +10,12 @@ namespace MareSynchronos.UI.Components
{
public class PairGroupsUi
{
private readonly Action<ClientPairDto> _clientRenderFn;
private readonly Action<Pair> _clientRenderFn;
private readonly TagHandler _tagHandler;
private readonly ApiController _apiController;
private readonly SelectPairForGroupUi _selectGroupForPairUi;
public PairGroupsUi(TagHandler tagHandler, Action<ClientPairDto> clientRenderFn, ApiController apiController, SelectPairForGroupUi selectGroupForPairUi)
public PairGroupsUi(TagHandler tagHandler, Action<Pair> clientRenderFn, ApiController apiController, SelectPairForGroupUi selectGroupForPairUi)
{
_clientRenderFn = clientRenderFn;
_tagHandler = tagHandler;
@@ -25,30 +23,57 @@ namespace MareSynchronos.UI.Components
_selectGroupForPairUi = selectGroupForPairUi;
}
public void Draw(List<ClientPairDto> availablePairs)
public void Draw(List<Pair> visibleUsers, List<Pair> onlineUsers, List<Pair> offlineUsers)
{
// Only render those tags that actually have pairs in them, otherwise
// we can end up with a bunch of useless pair groups
var tagsWithPairsInThem = _tagHandler.GetAllTagsSorted();
foreach (var tag in tagsWithPairsInThem)
{
UiShared.DrawWithID($"group-{tag}", () => DrawCategory(tag, availablePairs));
UiShared.DrawWithID($"group-{tag}", () => DrawCategory(tag, visibleUsers, onlineUsers, offlineUsers));
}
}
public void DrawCategory(string tag, List<ClientPairDto> availablePairs)
public void DrawCategory(string tag, List<Pair> visibleUsers, List<Pair> onlineUsers, List<Pair> offlineUsers)
{
var otherUidsTaggedWithTag = _tagHandler.GetOtherUidsForTag(tag);
var availablePairsInThisTag = availablePairs
.Where(pair => otherUidsTaggedWithTag.Contains(pair.OtherUID))
var visiblePairsInThisTag = visibleUsers
.Where(pair => otherUidsTaggedWithTag.Contains(pair.UserData.UID))
.ToList();
if (availablePairsInThisTag.Any())
var onlinePairsInThisTag = onlineUsers
.Where(pair => otherUidsTaggedWithTag.Contains(pair.UserData.UID))
.ToList();
var offlinePairsInThisTag = offlineUsers
.Where(pair => otherUidsTaggedWithTag.Contains(pair.UserData.UID))
.ToList();
if (visiblePairsInThisTag.Any() || onlinePairsInThisTag.Any() || offlinePairsInThisTag.Any())
{
DrawName(tag);
UiShared.DrawWithID($"group-{tag}-buttons", () => DrawButtons(tag, availablePairsInThisTag));
UiShared.DrawWithID($"group-{tag}-buttons", () => DrawButtons(tag, visiblePairsInThisTag));
if (_tagHandler.IsTagOpen(tag))
{
DrawPairs(tag, availablePairsInThisTag);
ImGui.Indent(20);
if (visiblePairsInThisTag.Any())
{
ImGui.Text("Visible");
ImGui.Separator();
DrawPairs(tag, visiblePairsInThisTag);
}
if (onlinePairsInThisTag.Any())
{
ImGui.Text("Online");
ImGui.Separator();
DrawPairs(tag, onlinePairsInThisTag);
}
if (offlinePairsInThisTag.Any())
{
ImGui.Text("Offline/Unknown");
ImGui.Separator();
DrawPairs(tag, offlinePairsInThisTag);
}
ImGui.Unindent(20);
}
}
}
@@ -72,9 +97,9 @@ namespace MareSynchronos.UI.Components
}
}
private void DrawButtons(string tag, List<ClientPairDto> availablePairsInThisTag)
private void DrawButtons(string tag, List<Pair> availablePairsInThisTag)
{
var allArePaused = availablePairsInThisTag.All(pair => pair.IsPaused);
var allArePaused = availablePairsInThisTag.All(pair => pair.UserPair!.OwnPermissions.IsPaused());
var pauseButton = allArePaused ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
var flyoutMenuX = UiShared.GetIconButtonSize(FontAwesomeIcon.Bars).X;
var pauseButtonX = UiShared.GetIconButtonSize(pauseButton).X;
@@ -142,43 +167,37 @@ namespace MareSynchronos.UI.Components
UiShared.AttachToolTip($"Delete Group {tag} (Will not delete the pairs)" + Environment.NewLine + "Hold CTRL to delete");
}
private void DrawPairs(string tag, List<ClientPairDto> availablePairsInThisCategory)
private void DrawPairs(string tag, List<Pair> availablePairsInThisCategory)
{
ImGui.Separator();
// These are all the OtherUIDs that are tagged with this tag
availablePairsInThisCategory
.ForEach(pair => UiShared.DrawWithID($"tag-{tag}-pair-${pair.OtherUID}", () => DrawPair(pair)));
.ForEach(pair => UiShared.DrawWithID($"tag-{tag}-pair-${pair.UserData.UID}", () => _clientRenderFn(pair)));
ImGui.Separator();
}
private void DrawPair(ClientPairDto pair)
{
// This is probably just dumb. Somehow, just setting the cursor position to the icon lenght
// does not really push the child rendering further. So we'll just add two whitespaces and call it a day?
UiShared.FontText(" ", UiBuilder.DefaultFont);
ImGui.SameLine();
_clientRenderFn(pair);
}
private void ToggleTagOpen(string tag)
{
bool open = !_tagHandler.IsTagOpen(tag);
_tagHandler.SetTagOpen(tag, open);
}
private void PauseRemainingPairs(List<ClientPairDto> availablePairs)
private void PauseRemainingPairs(List<Pair> availablePairs)
{
foreach (var pairToPause in availablePairs.Where(pair => !pair.IsPaused))
foreach (var pairToPause in availablePairs.Where(pair => !pair.UserPair!.OwnPermissions.IsPaused()))
{
_ = _apiController.UserChangePairPauseStatus(pairToPause.OtherUID, paused: true);
var perm = pairToPause.UserPair!.OwnPermissions;
perm.SetPaused(paused: true);
_ = _apiController.UserSetPairPermissions(new(pairToPause.UserData, perm));
}
}
private void ResumeAllPairs(List<ClientPairDto> availablePairs)
private void ResumeAllPairs(List<Pair> availablePairs)
{
foreach (var pairToPause in availablePairs)
{
_ = _apiController.UserChangePairPauseStatus(pairToPause.OtherUID, paused: false);
var perm = pairToPause.UserPair!.OwnPermissions;
perm.SetPaused(paused: false);
_ = _apiController.UserSetPairPermissions(new(pairToPause.UserData, perm));
}
}
}

View File

@@ -1,10 +1,9 @@
using System.Collections.Generic;
using System.Numerics;
using System.Numerics;
using Dalamud.Interface;
using Dalamud.Interface.Components;
using Dalamud.Utility;
using ImGuiNET;
using MareSynchronos.API;
using MareSynchronos.Models;
using MareSynchronos.UI.Handlers;
namespace MareSynchronos.UI.Components;
@@ -20,7 +19,7 @@ public class SelectGroupForPairUi
/// The group UI is always open for a specific pair. This defines which pair the UI is open for.
/// </summary>
/// <returns></returns>
private ClientPairDto? _pair;
private Pair? _pair;
/// <summary>
/// For the add category option, this stores the currently typed in tag name
@@ -28,17 +27,15 @@ public class SelectGroupForPairUi
private string _tagNameToAdd = "";
private readonly TagHandler _tagHandler;
private readonly Configuration _configuration;
public SelectGroupForPairUi(TagHandler tagHandler, Configuration configuration)
public SelectGroupForPairUi(TagHandler tagHandler)
{
_show = false;
_pair = null;
_tagHandler = tagHandler;
_configuration = configuration;
}
public void Open(ClientPairDto pair)
public void Open(Pair pair)
{
_pair = pair;
// Using "_show" here to de-couple the opening of the popup
@@ -56,7 +53,7 @@ public class SelectGroupForPairUi
return;
}
var name = PairName(showUidForEntry, _pair.OtherUID, _pair.VanityUID);
var name = PairName(showUidForEntry, _pair);
var popupName = $"Choose Groups for {name}";
// Is the popup supposed to show but did not open yet? Open it
if (_show)
@@ -76,7 +73,7 @@ public class SelectGroupForPairUi
{
foreach (var tag in tags)
{
UiShared.DrawWithID($"groups-pair-{_pair.OtherUID}-{tag}", () => DrawGroupName(_pair, tag));
UiShared.DrawWithID($"groups-pair-{_pair.UserData.UID}-{tag}", () => DrawGroupName(_pair, tag));
}
ImGui.EndChild();
}
@@ -99,19 +96,19 @@ public class SelectGroupForPairUi
}
}
private void DrawGroupName(ClientPairDto pair, string name)
private void DrawGroupName(Pair pair, string name)
{
var hasTagBefore = _tagHandler.HasTag(pair, name);
var hasTagBefore = _tagHandler.HasTag(pair.UserPair!, name);
var hasTag = hasTagBefore;
if (ImGui.Checkbox(name, ref hasTag))
{
if (hasTag)
{
_tagHandler.AddTagToPairedUid(pair, name);
_tagHandler.AddTagToPairedUid(pair.UserPair!, name);
}
else
{
_tagHandler.RemoveTagFromPairedUid(pair, name);
_tagHandler.RemoveTagFromPairedUid(pair.UserPair!, name);
}
}
}
@@ -123,19 +120,19 @@ public class SelectGroupForPairUi
_tagHandler.AddTag(_tagNameToAdd);
if (_pair != null)
{
_tagHandler.AddTagToPairedUid(_pair, _tagNameToAdd);
_tagHandler.AddTagToPairedUid(_pair.UserPair!, _tagNameToAdd);
}
_tagNameToAdd = string.Empty;
}
}
private string PairName(Dictionary<string, bool> showUidForEntry, string otherUid, string vanityUid)
private string PairName(Dictionary<string, bool> showUidForEntry, Pair pair)
{
showUidForEntry.TryGetValue(otherUid, out var showUidInsteadOfName);
_configuration.GetCurrentServerUidComments().TryGetValue(otherUid, out var playerText);
showUidForEntry.TryGetValue(pair.UserData.UID, out var showUidInsteadOfName);
var playerText = pair.GetNote();
if (showUidInsteadOfName || string.IsNullOrEmpty(playerText))
{
playerText = string.IsNullOrEmpty(vanityUid) ? otherUid : vanityUid;
playerText = pair.UserData.AliasOrUID;
}
return playerText;
}

View File

@@ -1,9 +1,7 @@
using System.Collections.Generic;
using System.Linq;
using System.Numerics;
using Dalamud.Interface;
using FFXIVClientStructs.FFXIV.Common.Math;
using ImGuiNET;
using MareSynchronos.API;
using MareSynchronos.Models;
using MareSynchronos.UI.Handlers;
namespace MareSynchronos.UI.Components;
@@ -12,16 +10,14 @@ public class SelectPairForGroupUi
{
private bool _show = false;
private bool _opened = false;
private HashSet<string> _peopleInGroup = new(System.StringComparer.Ordinal);
private HashSet<string> _peopleInGroup = new(StringComparer.Ordinal);
private string _tag = string.Empty;
private readonly TagHandler _tagHandler;
private readonly Configuration _configuration;
private string _filter = string.Empty;
public SelectPairForGroupUi(TagHandler tagHandler, Configuration configuration)
public SelectPairForGroupUi(TagHandler tagHandler)
{
_tagHandler = tagHandler;
_configuration = configuration;
}
public void Open(string tag)
@@ -31,7 +27,7 @@ public class SelectPairForGroupUi
_show = true;
}
public void Draw(List<ClientPairDto> pairs, Dictionary<string, bool> showUidForEntry)
public void Draw(List<Pair> pairs, Dictionary<string, bool> showUidForEntry)
{
var workHeight = ImGui.GetMainViewport().WorkSize.Y / ImGuiHelpers.GlobalScale;
var minSize = new Vector2(300, workHeight < 400 ? workHeight : 400) * ImGuiHelpers.GlobalScale;
@@ -57,21 +53,21 @@ public class SelectPairForGroupUi
{
UiShared.FontText($"Select users for group {_tag}", UiBuilder.DefaultFont);
ImGui.InputTextWithHint("##filter", "Filter", ref _filter, 255, ImGuiInputTextFlags.None);
foreach (var item in pairs.OrderBy(p => PairName(showUidForEntry, p.OtherUID, p.VanityUID), System.StringComparer.OrdinalIgnoreCase)
.Where(p => string.IsNullOrEmpty(_filter) || PairName(showUidForEntry, p.OtherUID, p.VanityUID).Contains(_filter, System.StringComparison.OrdinalIgnoreCase)).ToList())
foreach (var item in pairs.OrderBy(p => PairName(showUidForEntry, p), StringComparer.OrdinalIgnoreCase)
.Where(p => string.IsNullOrEmpty(_filter) || PairName(showUidForEntry, p).Contains(_filter, StringComparison.OrdinalIgnoreCase)).ToList())
{
var isInGroup = _peopleInGroup.Contains(item.OtherUID);
if (ImGui.Checkbox(PairName(showUidForEntry, item.OtherUID, item.VanityUID), ref isInGroup))
var isInGroup = _peopleInGroup.Contains(item.UserData.UID);
if (ImGui.Checkbox(PairName(showUidForEntry, item), ref isInGroup))
{
if (isInGroup)
{
_tagHandler.AddTagToPairedUid(item, _tag);
_peopleInGroup.Add(item.OtherUID);
_tagHandler.AddTagToPairedUid(item.UserPair!, _tag);
_peopleInGroup.Add(item.UserData.UID);
}
else
{
_tagHandler.RemoveTagFromPairedUid(item, _tag);
_peopleInGroup.Remove(item.OtherUID);
_tagHandler.RemoveTagFromPairedUid(item.UserPair!, _tag);
_peopleInGroup.Remove(item.UserData.UID);
}
}
}
@@ -84,13 +80,13 @@ public class SelectPairForGroupUi
}
}
private string PairName(Dictionary<string, bool> showUidForEntry, string otherUid, string vanityUid)
private string PairName(Dictionary<string, bool> showUidForEntry, Pair pair)
{
showUidForEntry.TryGetValue(otherUid, out var showUidInsteadOfName);
_configuration.GetCurrentServerUidComments().TryGetValue(otherUid, out var playerText);
showUidForEntry.TryGetValue(pair.UserData.UID, out var showUidInsteadOfName);
var playerText = pair.GetNote();
if (showUidInsteadOfName || string.IsNullOrEmpty(playerText))
{
playerText = string.IsNullOrEmpty(vanityUid) ? otherUid : vanityUid;
playerText = pair.UserData.AliasOrUID;
}
return playerText;
}

View File

@@ -1,8 +1,7 @@
using System;
using System.Linq;
using System.Numerics;
using System.Numerics;
using Dalamud.Interface.Windowing;
using ImGuiNET;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
@@ -11,7 +10,7 @@ namespace MareSynchronos.UI;
public class DownloadUi : Window, IDisposable
{
private readonly WindowSystem _windowSystem;
private readonly Configuration _pluginConfiguration;
private readonly ConfigurationService _configService;
private readonly ApiController _apiController;
private readonly UiShared _uiShared;
private bool _wasOpen = false;
@@ -22,18 +21,18 @@ public class DownloadUi : Window, IDisposable
_windowSystem.RemoveWindow(this);
}
public DownloadUi(WindowSystem windowSystem, Configuration pluginConfiguration, ApiController apiController, UiShared uiShared) : base("Mare Synchronos Downloads")
public DownloadUi(WindowSystem windowSystem, ConfigurationService configService, ApiController apiController, UiShared uiShared) : base("Mare Synchronos Downloads")
{
Logger.Verbose("Creating " + nameof(DownloadUi));
_windowSystem = windowSystem;
_pluginConfiguration = pluginConfiguration;
_configService = configService;
_apiController = apiController;
_uiShared = uiShared;
SizeConstraints = new WindowSizeConstraints()
{
MaximumSize = new Vector2(300, 90),
MinimumSize = new Vector2(300, 90)
MinimumSize = new Vector2(300, 90),
};
Flags |= ImGuiWindowFlags.NoMove;
@@ -79,7 +78,7 @@ public class DownloadUi : Window, IDisposable
public override void Draw()
{
if (!_pluginConfiguration.ShowTransferWindow) return;
if (!_configService.Current.ShowTransferWindow) return;
if (!_apiController.IsDownloading && !_apiController.IsUploading) return;
var drawList = ImGui.GetWindowDrawList();

View File

@@ -4,9 +4,8 @@ using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Interface.Windowing;
using ImGuiNET;
using MareSynchronos.Export;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Utils;
using System;
using System.Threading.Tasks;
namespace MareSynchronos.UI;
@@ -16,15 +15,16 @@ public class GposeUi : Window, IDisposable
private readonly MareCharaFileManager _mareCharaFileManager;
private readonly DalamudUtil _dalamudUtil;
private readonly FileDialogManager _fileDialogManager;
private readonly Configuration _configuration;
private readonly ConfigurationService _configService;
public GposeUi(WindowSystem windowSystem, MareCharaFileManager mareCharaFileManager, DalamudUtil dalamudUtil, FileDialogManager fileDialogManager, Configuration configuration) : base("Mare Synchronos Gpose Import UI###MareSynchronosGposeUI")
public GposeUi(WindowSystem windowSystem, MareCharaFileManager mareCharaFileManager,
DalamudUtil dalamudUtil, FileDialogManager fileDialogManager, ConfigurationService configService) : base("Mare Synchronos Gpose Import UI###MareSynchronosGposeUI")
{
_windowSystem = windowSystem;
_mareCharaFileManager = mareCharaFileManager;
_dalamudUtil = dalamudUtil;
_fileDialogManager = fileDialogManager;
_configuration = configuration;
_configService = configService;
_dalamudUtil.GposeStart += StartGpose;
_dalamudUtil.GposeEnd += EndGpose;
IsOpen = _dalamudUtil.IsInGpose;
@@ -40,7 +40,7 @@ public class GposeUi : Window, IDisposable
private void StartGpose()
{
IsOpen = _configuration.OpenGposeImportOnGposeStart;
IsOpen = _configService.Current.OpenGposeImportOnGposeStart;
}
public void Dispose()

View File

@@ -3,24 +3,27 @@ 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;
using System.Text;
using System.Globalization;
using MareSynchronos.API.Data;
using MareSynchronos.API.Dto.Group;
using MareSynchronos.API.Dto.User;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.API.Data.Extensions;
using MareSynchronos.Managers;
using MareSynchronos.Models;
using MareSynchronos.API.Data.Comparer;
namespace MareSynchronos.UI
{
internal class GroupPanel
{
private readonly CompactUi _mainUi;
private UiShared _uiShared;
private Configuration _configuration;
private ApiController _apiController;
private readonly UiShared _uiShared;
private ApiController ApiController => _uiShared.ApiController;
private readonly PairManager _pairManager;
private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly Dictionary<string, bool> _showGidForEntry = new(StringComparer.Ordinal);
private string _editGroupEntry = string.Empty;
private string _editGroupComment = string.Empty;
@@ -38,8 +41,8 @@ namespace MareSynchronos.UI
private bool _isPasswordValid;
private bool _errorGroupJoin;
private bool _errorGroupCreate = false;
private GroupCreatedDto? _lastCreatedGroup = null;
private readonly Dictionary<string, bool> ExpandedGroupState = new(StringComparer.Ordinal);
private GroupPasswordDto? _lastCreatedGroup = null;
private readonly Dictionary<string, bool> _expandedGroupState = new(StringComparer.Ordinal);
private List<BannedGroupUserDto> _bannedUsers = new();
private List<string> _bulkOneTimeInvites = new();
private bool _modalBanListOpened;
@@ -48,12 +51,12 @@ namespace MareSynchronos.UI
private bool _modalChangePwOpened;
private int _bulkInviteCount = 10;
public GroupPanel(CompactUi mainUi, UiShared uiShared, Configuration configuration, ApiController apiController)
public GroupPanel(CompactUi mainUi, UiShared uiShared, PairManager pairManager, ServerConfigurationManager serverConfigurationManager)
{
_mainUi = mainUi;
_uiShared = uiShared;
_configuration = configuration;
_apiController = apiController;
_pairManager = pairManager;
_serverConfigurationManager = serverConfigurationManager;
}
public void DrawSyncshells()
@@ -70,12 +73,13 @@ namespace MareSynchronos.UI
ImGui.InputTextWithHint("##syncshellid", "Syncshell GID/Alias (leave empty to create)", 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;
bool userCanJoinMoreGroups = _pairManager.GroupPairs.Count < ApiController.ServerInfo.MaxGroupsJoinedByUser;
bool userCanCreateMoreGroups = _pairManager.GroupPairs.Count(u => string.Equals(u.Key.Owner.UID, 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 (_pairManager.GroupPairs.All(w => !string.Equals(w.Key.Group.GID, _syncShellToJoin, StringComparison.Ordinal) && !string.Equals(w.Key.Group.Alias, _syncShellToJoin, StringComparison.Ordinal))
&& !string.IsNullOrEmpty(_syncShellToJoin))
{
if (userCanJoinMoreGroups)
{
@@ -96,8 +100,8 @@ namespace MareSynchronos.UI
}
}
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"));
? (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, UiShared.PopupWindowFlags))
{
@@ -108,15 +112,15 @@ namespace MareSynchronos.UI
ImGui.InputTextWithHint("##password", _syncShellToJoin + " Password", ref _syncShellPassword, 255, ImGuiInputTextFlags.Password);
if (_errorGroupJoin)
{
UiShared.ColorTextWrapped($"An error occured during joining of this Syncshell: you either have joined the maximum amount of Syncshells ({_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.",
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.GroupJoin(shell, pw).Result;
_errorGroupJoin = !ApiController.GroupJoin(new(new GroupData(shell), pw)).Result;
if (!_errorGroupJoin)
{
_syncShellToJoin = string.Empty;
@@ -136,7 +140,7 @@ namespace MareSynchronos.UI
{
try
{
_lastCreatedGroup = _apiController.GroupCreate().Result;
_lastCreatedGroup = ApiController.GroupCreate().Result;
}
catch
{
@@ -149,7 +153,7 @@ namespace MareSynchronos.UI
{
ImGui.Separator();
_errorGroupCreate = false;
ImGui.TextUnformatted("Syncshell ID: " + _lastCreatedGroup.GID);
ImGui.TextUnformatted("Syncshell ID: " + _lastCreatedGroup.Group.GID);
ImGui.AlignTextToFramePadding();
ImGui.TextUnformatted("Syncshell Password: " + _lastCreatedGroup.Password);
ImGui.SameLine();
@@ -178,22 +182,21 @@ namespace MareSynchronos.UI
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())
ImGui.BeginChild("list", new Vector2(_mainUi.WindowContentWidth, ySize), border: false);
foreach (var entry in _pairManager.GroupPairs.OrderBy(g => g.Key.Group.AliasOrGID, StringComparer.OrdinalIgnoreCase).ToList())
{
UiShared.DrawWithID(entry.GID, () => DrawSyncshell(entry));
UiShared.DrawWithID(entry.Key.Group.GID, () => DrawSyncshell(entry.Key, entry.Value));
}
ImGui.EndChild();
}
private void DrawSyncshell(GroupDto group)
private void DrawSyncshell(GroupFullInfoDto groupDto, List<Pair> pairsInGroup)
{
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))
var name = groupDto.Group.Alias ?? groupDto.GID;
if (!_expandedGroupState.TryGetValue(groupDto.GID, out bool isExpanded))
{
isExpanded = false;
ExpandedGroupState.Add(group.GID, isExpanded);
_expandedGroupState.Add(groupDto.GID, isExpanded);
}
var icon = isExpanded ? FontAwesomeIcon.CaretSquareDown : FontAwesomeIcon.CaretSquareRight;
var collapseButton = UiShared.GetIconButtonSize(icon);
@@ -201,22 +204,23 @@ namespace MareSynchronos.UI
ImGui.PushStyleColor(ImGuiCol.ButtonHovered, new Vector4(0, 0, 0, 0));
if (ImGuiComponents.IconButton(icon))
{
ExpandedGroupState[group.GID] = !ExpandedGroupState[group.GID];
_expandedGroupState[groupDto.GID] = !_expandedGroupState[groupDto.GID];
}
ImGui.PopStyleColor(2);
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + collapseButton.X);
var pauseIcon = (group.IsPaused ?? false) ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
var pauseIcon = groupDto.GroupUserPermissions.IsPaused() ? FontAwesomeIcon.Play : FontAwesomeIcon.Pause;
if (ImGuiComponents.IconButton(pauseIcon))
{
_ = _apiController.GroupChangePauseState(group.GID, !group.IsPaused ?? false);
var userPerm = groupDto.GroupUserPermissions ^ GroupUserPermissions.Paused;
_ = ApiController.GroupChangeIndividualPermissionState(new GroupPairUserPermissionDto(groupDto.Group, new UserData(ApiController.UID), userPerm));
}
UiShared.AttachToolTip(((group.IsPaused ?? false) ? "Resume" : "Pause") + " pairing with all users in this Syncshell");
UiShared.AttachToolTip((groupDto.GroupUserPermissions.IsPaused() ? "Resume" : "Pause") + " pairing with all users in this Syncshell");
ImGui.SameLine();
var groupName = string.IsNullOrEmpty(group.Alias) ? group.GID : group.Alias;
var textIsGid = true;
string groupName = groupDto.GroupAliasOrGID;
if (string.Equals(group.OwnedBy, _apiController.UID, StringComparison.Ordinal))
if (string.Equals(groupDto.OwnerUID, ApiController.UID, StringComparison.Ordinal))
{
ImGui.PushFont(UiBuilder.IconFont);
ImGui.Text(FontAwesomeIcon.Crown.ToIconString());
@@ -224,7 +228,7 @@ namespace MareSynchronos.UI
UiShared.AttachToolTip("You are the owner of Syncshell " + groupName);
ImGui.SameLine();
}
else if (group.IsModerator ?? false)
else if (groupDto.GroupUserInfo.IsModerator())
{
ImGui.PushFont(UiBuilder.IconFont);
ImGui.Text(FontAwesomeIcon.UserShield.ToIconString());
@@ -233,8 +237,8 @@ namespace MareSynchronos.UI
ImGui.SameLine();
}
_showGidForEntry.TryGetValue(group.GID, out var showGidInsteadOfName);
if (!showGidInsteadOfName && _configuration.GetCurrentServerGidComments().TryGetValue(group.GID, out var groupComment))
_showGidForEntry.TryGetValue(groupDto.GID, out var showGidInsteadOfName);
if (!showGidInsteadOfName && _serverConfigurationManager.CurrentServer!.GidServerComments.TryGetValue(groupDto.GID, out var groupComment))
{
if (!string.IsNullOrEmpty(groupComment))
{
@@ -243,33 +247,31 @@ namespace MareSynchronos.UI
}
}
if (!string.Equals(_editGroupEntry, group.GID, StringComparison.Ordinal))
if (!string.Equals(_editGroupEntry, groupDto.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);
+ "Users: " + (pairsInGroup.Count + 1) + ", Owner: " + groupDto.OwnerAliasOrUID);
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{
var prevState = textIsGid;
if (_showGidForEntry.ContainsKey(group.GID))
if (_showGidForEntry.ContainsKey(groupDto.GID))
{
prevState = _showGidForEntry[group.GID];
prevState = _showGidForEntry[groupDto.GID];
}
_showGidForEntry[group.GID] = !prevState;
_showGidForEntry[groupDto.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;
_serverConfigurationManager.CurrentServer!.GidServerComments[_editGroupEntry] = _editGroupComment;
_serverConfigurationManager.Save();
_editGroupComment = _serverConfigurationManager.CurrentServer!.GidServerComments.TryGetValue(groupDto.GID, out string? value) ? value : string.Empty;
_editGroupEntry = groupDto.GID;
}
}
else
@@ -278,8 +280,8 @@ namespace MareSynchronos.UI
ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * 2);
if (ImGui.InputTextWithHint("", "Comment/Notes", ref _editGroupComment, 255, ImGuiInputTextFlags.EnterReturnsTrue))
{
_configuration.SetCurrentServerGidComment(group.GID, _editGroupComment);
_configuration.Save();
_serverConfigurationManager.CurrentServer!.GidServerComments[groupDto.GID] = _editGroupComment;
_serverConfigurationManager.Save();
_editGroupEntry = string.Empty;
}
@@ -290,26 +292,27 @@ namespace MareSynchronos.UI
UiShared.AttachToolTip("Hit ENTER to save\nRight click to cancel");
}
UiShared.DrawWithID(group.GID + "settings", () => DrawSyncShellButtons(group, name));
UiShared.DrawWithID(groupDto.GID + "settings", () => DrawSyncShellButtons(groupDto, pairsInGroup));
if (_showModalBanList && !_modalBanListOpened)
{
_modalBanListOpened = true;
ImGui.OpenPopup("Manage Banlist for " + group.GID);
ImGui.OpenPopup("Manage Banlist for " + groupDto.GID);
}
if (!_showModalBanList) _modalBanListOpened = false;
if (ImGui.BeginPopupModal("Manage Banlist for " + group.GID, ref _showModalBanList, UiShared.PopupWindowFlags))
if (ImGui.BeginPopupModal("Manage Banlist for " + groupDto.GID, ref _showModalBanList, UiShared.PopupWindowFlags))
{
if (UiShared.IconTextButton(FontAwesomeIcon.Retweet, "Refresh Banlist from Server"))
{
_bannedUsers = _apiController.GroupGetBannedUsers(group.GID).Result;
_bannedUsers = ApiController.GroupGetBannedUsers(groupDto).Result;
}
if (ImGui.BeginTable("bannedusertable" + group.GID, 5, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.ScrollY))
if (ImGui.BeginTable("bannedusertable" + groupDto.GID, 6, ImGuiTableFlags.RowBg | ImGuiTableFlags.SizingStretchProp | ImGuiTableFlags.ScrollY))
{
ImGui.TableSetupColumn("UID", ImGuiTableColumnFlags.None, 1);
ImGui.TableSetupColumn("Alias", ImGuiTableColumnFlags.None, 1);
ImGui.TableSetupColumn("By", ImGuiTableColumnFlags.None, 1);
ImGui.TableSetupColumn("Date", ImGuiTableColumnFlags.None, 2);
ImGui.TableSetupColumn("Reason", ImGuiTableColumnFlags.None, 3);
@@ -322,6 +325,8 @@ namespace MareSynchronos.UI
ImGui.TableNextColumn();
ImGui.TextUnformatted(bannedUser.UID);
ImGui.TableNextColumn();
ImGui.TextUnformatted(bannedUser.UserAlias ?? string.Empty);
ImGui.TableNextColumn();
ImGui.TextUnformatted(bannedUser.BannedBy);
ImGui.TableNextColumn();
ImGui.TextUnformatted(bannedUser.BannedOn.ToLocalTime().ToString(CultureInfo.CurrentCulture));
@@ -330,7 +335,7 @@ namespace MareSynchronos.UI
ImGui.TableNextColumn();
if (UiShared.IconTextButton(FontAwesomeIcon.Check, "Unban"))
{
_ = _apiController.GroupUnbanUser(group.GID, bannedUser.UID);
_ = ApiController.GroupUnbanUser(bannedUser);
_bannedUsers.RemoveAll(b => string.Equals(b.UID, bannedUser.UID, StringComparison.Ordinal));
}
}
@@ -349,7 +354,6 @@ namespace MareSynchronos.UI
if (!_showModalChangePassword) _modalChangePwOpened = false;
if (ImGui.BeginPopupModal("Change Syncshell Password", ref _showModalChangePassword, UiShared.PopupWindowFlags))
{
UiShared.TextWrapped("Enter the new Syncshell password for Syncshell " + name + " here.");
@@ -359,7 +363,7 @@ namespace MareSynchronos.UI
if (ImGui.Button("Change password"))
{
var pw = _newSyncShellPassword;
_isPasswordValid = _apiController.GroupChangePassword(group.GID, pw).Result;
_isPasswordValid = ApiController.GroupChangePassword(new(groupDto.Group, pw)).Result;
_newSyncShellPassword = string.Empty;
if (_isPasswordValid) _showModalChangePassword = false;
}
@@ -392,7 +396,7 @@ namespace MareSynchronos.UI
ImGui.SliderInt("Amount##bulkinvites", ref _bulkInviteCount, 1, 100);
if (UiShared.IconTextButton(FontAwesomeIcon.MailBulk, "Create invites"))
{
_bulkOneTimeInvites = _apiController.GroupCreateTempInvite(group.GID, _bulkInviteCount).Result;
_bulkOneTimeInvites = ApiController.GroupCreateTempInvite(groupDto, _bulkInviteCount).Result;
}
}
else
@@ -409,22 +413,55 @@ namespace MareSynchronos.UI
}
ImGui.Indent(collapseButton.X);
if (ExpandedGroupState[group.GID])
if (_expandedGroupState[groupDto.GID])
{
pairsInGroup = pairsInGroup.OrderBy(p => string.Equals(p.UserUID, group.OwnedBy, StringComparison.Ordinal) ? 0 : 1)
.ThenBy(p => p.IsModerator ?? false ? 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,
group.OwnedBy!,
string.Equals(group.OwnedBy, _apiController.UID, StringComparison.Ordinal),
group.IsModerator ?? false,
group?.IsPaused ?? false));
var visibleUsers = pairsInGroup.Where(u => u.IsVisible).OrderBy(u => u.GetNote() ?? u.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase).ToList();
var onlineUsers = pairsInGroup.Where(u => u.IsOnline && !u.IsVisible).OrderBy(u => u.GetNote() ?? u.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase).ToList();
var offlineUsers = pairsInGroup.Where(u => !u.IsOnline && !u.IsVisible).OrderBy(u => u.GetNote() ?? u.UserData.AliasOrUID, StringComparer.OrdinalIgnoreCase).ToList();
if (visibleUsers.Any())
{
ImGui.Text("Visible");
ImGui.Separator();
foreach (var entry in visibleUsers)
{
UiShared.DrawWithID(groupDto.GID + entry.UserData.UID, () => DrawSyncshellPairedClient(
entry,
entry.GroupPair.Single(g => GroupDataComparer.Instance.Equals(g.Key.Group, groupDto.Group)).Value,
groupDto.OwnerUID,
string.Equals(groupDto.OwnerUID, ApiController.UID, StringComparison.Ordinal),
groupDto.GroupUserInfo.IsModerator()));
}
}
if (onlineUsers.Any())
{
ImGui.Text("Online");
ImGui.Separator();
foreach (var entry in onlineUsers)
{
UiShared.DrawWithID(groupDto.GID + entry.UserData.UID, () => DrawSyncshellPairedClient(
entry,
entry.GroupPair.Single(g => GroupDataComparer.Instance.Equals(g.Key.Group, groupDto.Group)).Value,
groupDto.OwnerUID,
string.Equals(groupDto.OwnerUID, ApiController.UID, StringComparison.Ordinal),
groupDto.GroupUserInfo.IsModerator()));
}
}
if (offlineUsers.Any())
{
ImGui.Text("Offline/Unknown");
ImGui.Separator();
foreach (var entry in offlineUsers)
{
UiShared.DrawWithID(groupDto.GID + entry.UserData.UID, () => DrawSyncshellPairedClient(
entry,
entry.GroupPair.Single(g => GroupDataComparer.Instance.Equals(g.Key.Group, groupDto.Group)).Value,
groupDto.OwnerUID,
string.Equals(groupDto.OwnerUID, ApiController.UID, StringComparison.Ordinal),
groupDto.GroupUserInfo.IsModerator()));
}
}
ImGui.Separator();
@@ -433,21 +470,97 @@ namespace MareSynchronos.UI
ImGui.Unindent(collapseButton.X);
}
private void DrawSyncShellButtons(GroupDto entry, string name)
private void DrawSyncShellButtons(GroupFullInfoDto groupDto, List<Pair> groupPairs)
{
bool invitesEnabled = entry.InvitesEnabled ?? true;
var lockedIcon = invitesEnabled ? FontAwesomeIcon.LockOpen : FontAwesomeIcon.Lock;
var iconSize = UiShared.GetIconSize(lockedIcon);
var diffLockUnlockIcons = invitesEnabled ? 0 : (UiShared.GetIconSize(FontAwesomeIcon.LockOpen).X - iconSize.X) / 2;
var barbuttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Bars);
var isOwner = string.Equals(entry.OwnedBy, _apiController.UID, StringComparison.Ordinal);
var infoIcon = FontAwesomeIcon.InfoCircle;
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - barbuttonSize.X - iconSize.X - diffLockUnlockIcons - 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");
bool invitesEnabled = !groupDto.GroupPermissions.IsDisableInvites();
var soundsDisabled = groupDto.GroupPermissions.IsDisableSounds();
var animDisabled = groupDto.GroupPermissions.IsDisableAnimations();
var userSoundsDisabled = groupDto.GroupUserPermissions.IsDisableSounds();
var userAnimDisabled = groupDto.GroupUserPermissions.IsDisableAnimations();
bool showInfoIcon = !invitesEnabled || soundsDisabled || animDisabled || userSoundsDisabled || userAnimDisabled;
var lockedIcon = invitesEnabled ? FontAwesomeIcon.LockOpen : FontAwesomeIcon.Lock;
var animIcon = animDisabled ? FontAwesomeIcon.Stop : FontAwesomeIcon.Running;
var soundsIcon = soundsDisabled ? FontAwesomeIcon.VolumeOff : FontAwesomeIcon.VolumeUp;
var userAnimIcon = userAnimDisabled ? FontAwesomeIcon.Stop : FontAwesomeIcon.Running;
var userSoundsIcon = userSoundsDisabled ? FontAwesomeIcon.VolumeOff : FontAwesomeIcon.VolumeUp;
var iconSize = UiShared.GetIconSize(infoIcon);
var diffLockUnlockIcons = showInfoIcon ? (UiShared.GetIconSize(infoIcon).X - iconSize.X) / 2 : 0;
var barbuttonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Bars);
var isOwner = string.Equals(groupDto.OwnerUID, ApiController.UID, StringComparison.Ordinal);
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - barbuttonSize.X - (showInfoIcon ? iconSize.X : 0) - diffLockUnlockIcons - (showInfoIcon ? ImGui.GetStyle().ItemSpacing.X : 0));
if (showInfoIcon)
{
UiShared.FontText(infoIcon.ToIconString(), UiBuilder.IconFont);
if (ImGui.IsItemHovered())
{
ImGui.BeginTooltip();
if (!invitesEnabled || soundsDisabled || animDisabled)
{
ImGui.Text("Syncshell permissions");
if (!invitesEnabled)
{
var lockedText = "Syncshell is closed for joining";
UiShared.FontText(lockedIcon.ToIconString(), UiBuilder.IconFont);
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
ImGui.Text(lockedText);
}
if (soundsDisabled)
{
var soundsText = "Sound sync disabled through owner";
UiShared.FontText(soundsIcon.ToIconString(), UiBuilder.IconFont);
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
ImGui.Text(soundsText);
}
if (animDisabled)
{
var animText = "Animation sync disabled through owner";
UiShared.FontText(animIcon.ToIconString(), UiBuilder.IconFont);
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
ImGui.Text(animText);
}
}
if (userSoundsDisabled || userAnimDisabled)
{
if (!invitesEnabled || soundsDisabled || animDisabled)
ImGui.Separator();
ImGui.Text("Your permissions");
if (userSoundsDisabled)
{
var userSoundsText = "Sound sync disabled through you";
UiShared.FontText(userSoundsIcon.ToIconString(), UiBuilder.IconFont);
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
ImGui.Text(userSoundsText);
}
if (userAnimDisabled)
{
var userAnimText = "Animation sync disabled through you";
UiShared.FontText(userAnimIcon.ToIconString(), UiBuilder.IconFont);
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
ImGui.Text(userAnimText);
}
if (!invitesEnabled || soundsDisabled || animDisabled)
UiShared.TextWrapped("Note that syncshell permissions for disabling take precedence over your own set permissions");
}
ImGui.EndTooltip();
}
ImGui.SameLine();
}
ImGui.SetCursorPosX(ImGui.GetCursorPosX() + diffLockUnlockIcons);
if (ImGuiComponents.IconButton(FontAwesomeIcon.Bars))
{
@@ -460,35 +573,65 @@ namespace MareSynchronos.UI
{
if (UiShared.CtrlPressed())
{
_ = _apiController.GroupLeave(entry.GID);
_ = ApiController.GroupLeave(groupDto);
}
}
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."));
UiShared.AttachToolTip("Hold CTRL and click to leave this Syncshell" + (!string.Equals(groupDto.OwnerUID, ApiController.UID, StringComparison.Ordinal) ? string.Empty : Environment.NewLine
+ "WARNING: This action is irreversible" + 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.CloseCurrentPopup();
ImGui.SetClipboardText(string.IsNullOrEmpty(entry.Alias) ? entry.GID : entry.Alias);
ImGui.SetClipboardText(groupDto.GroupAliasOrGID);
}
UiShared.AttachToolTip("Copy Syncshell ID to Clipboard");
if (UiShared.IconTextButton(FontAwesomeIcon.StickyNote, "Copy Notes"))
{
ImGui.CloseCurrentPopup();
ImGui.SetClipboardText(_uiShared.GetNotes(entry.GID));
ImGui.SetClipboardText(UiShared.GetNotes(groupPairs));
}
UiShared.AttachToolTip("Copies all your notes for all users in this Syncshell to the clipboard." + Environment.NewLine + "They can be imported via Settings -> Privacy -> Import Notes from Clipboard");
if (isOwner || (entry.IsModerator ?? false))
var soundsText = userSoundsDisabled ? "Enable sound sync" : "Disable sound sync";
if (UiShared.IconTextButton(userSoundsIcon, soundsText))
{
ImGui.CloseCurrentPopup();
var perm = groupDto.GroupUserPermissions;
perm.SetDisableSounds(!perm.IsDisableSounds());
_ = ApiController.GroupChangeIndividualPermissionState(new(groupDto.Group, new UserData(ApiController.UID), perm));
}
UiShared.AttachToolTip("Sets your allowance for sound synchronization for users of this syncshell."
+ Environment.NewLine + "Disabling the synchronization will stop applying sound modifications for users of this syncshell."
+ Environment.NewLine + "Note: this setting can be forcefully overridden to 'disabled' through the syncshell owner."
+ Environment.NewLine + "Note: this setting does not apply to individual pairs that are also in the syncshell.");
var animText = userAnimDisabled ? "Enable animations sync" : "Disable animations sync";
if (UiShared.IconTextButton(userAnimIcon, animText))
{
ImGui.CloseCurrentPopup();
var perm = groupDto.GroupUserPermissions;
perm.SetDisableAnimations(!perm.IsDisableAnimations());
_ = ApiController.GroupChangeIndividualPermissionState(new(groupDto.Group, new UserData(ApiController.UID), perm));
}
UiShared.AttachToolTip("Sets your allowance for animations synchronization for users of this syncshell."
+ Environment.NewLine + "Disabling the synchronization will stop applying animations modifications for users of this syncshell."
+ Environment.NewLine + "Note: this setting might also affect sound synchronization"
+ Environment.NewLine + "Note: this setting can be forcefully overridden to 'disabled' through the syncshell owner."
+ Environment.NewLine + "Note: this setting does not apply to individual pairs that are also in the syncshell.");
if (isOwner || groupDto.GroupUserInfo.IsModerator())
{
ImGui.Separator();
var changedToIcon = !invitesEnabled ? FontAwesomeIcon.LockOpen : FontAwesomeIcon.Lock;
var changedToIcon = invitesEnabled ? FontAwesomeIcon.LockOpen : FontAwesomeIcon.Lock;
if (UiShared.IconTextButton(changedToIcon, invitesEnabled ? "Lock Syncshell" : "Unlock Syncshell"))
{
ImGui.CloseCurrentPopup();
_ = _apiController.GroupChangeInviteState(entry.GID, !entry.InvitesEnabled ?? true);
var groupPerm = groupDto.GroupPermissions;
groupPerm.SetDisableInvites(invitesEnabled);
_ = ApiController.GroupChangeGroupPermissionState(new GroupPermissionDto(groupDto.Group, groupPerm));
}
UiShared.AttachToolTip("Change Syncshell joining permissions" + Environment.NewLine + "Syncshell is currently " + (invitesEnabled ? "open" : "closed") + " for people to join");
@@ -508,16 +651,40 @@ namespace MareSynchronos.UI
if (UiShared.CtrlPressed())
{
ImGui.CloseCurrentPopup();
_ = _apiController.GroupClear(entry.GID);
_ = ApiController.GroupClear(groupDto);
}
}
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.");
var groupSoundsText = soundsDisabled ? "Enable syncshell sound sync" : "Disable syncshell sound sync";
if (UiShared.IconTextButton(soundsIcon, groupSoundsText))
{
ImGui.CloseCurrentPopup();
var perm = groupDto.GroupPermissions;
perm.SetDisableSounds(!perm.IsDisableSounds());
_ = ApiController.GroupChangeGroupPermissionState(new(groupDto.Group, perm));
}
UiShared.AttachToolTip("Sets syncshell-wide allowance for sound synchronization for all users." + Environment.NewLine
+ "Note: users that are individually paired with others in the syncshell will ignore this setting." + Environment.NewLine
+ "Note: if the synchronization is enabled, users can individually override this setting to disabled.");
var groupAnimText = animDisabled ? "Enable syncshell animations sync" : "Disable syncshell animations sync";
if (UiShared.IconTextButton(animIcon, groupAnimText))
{
ImGui.CloseCurrentPopup();
var perm = groupDto.GroupPermissions;
perm.SetDisableAnimations(!perm.IsDisableAnimations());
_ = ApiController.GroupChangeGroupPermissionState(new(groupDto.Group, perm));
}
UiShared.AttachToolTip("Sets syncshell-wide allowance for animations synchronization for all users." + Environment.NewLine
+ "Note: users that are individually paired with others in the syncshell will ignore this setting." + Environment.NewLine
+ "Note: if the synchronization is enabled, users can individually override this setting to disabled.");
if (UiShared.IconTextButton(FontAwesomeIcon.Envelope, "Single one-time invite"))
{
ImGui.CloseCurrentPopup();
ImGui.SetClipboardText(_apiController.GroupCreateTempInvite(entry.GID, 1).Result.FirstOrDefault() ?? string.Empty);
ImGui.SetClipboardText(ApiController.GroupCreateTempInvite(groupDto, 1).Result.FirstOrDefault() ?? string.Empty);
}
UiShared.AttachToolTip("Creates a single-use password for joining the syncshell which is valid for 24h and copies it to the clipboard.");
@@ -533,7 +700,7 @@ namespace MareSynchronos.UI
{
ImGui.CloseCurrentPopup();
_showModalBanList = true;
_bannedUsers = _apiController.GroupGetBannedUsers(entry.GID).Result;
_bannedUsers = ApiController.GroupGetBannedUsers(groupDto).Result;
}
if (isOwner)
@@ -543,7 +710,7 @@ namespace MareSynchronos.UI
if (UiShared.CtrlPressed() && UiShared.ShiftPressed())
{
ImGui.CloseCurrentPopup();
_ = _apiController.GroupDelete(entry.GID);
_ = ApiController.GroupDelete(groupDto);
}
}
UiShared.AttachToolTip("Hold CTRL and Shift and click to delete this Syncshell." + Environment.NewLine + "WARNING: this action is irreversible.");
@@ -554,20 +721,32 @@ namespace MareSynchronos.UI
}
}
private void DrawSyncshellPairedClient(GroupPairDto entry, string ownerUid, bool isOwner, bool isModerator, bool isPausedByYou)
private void DrawSyncshellPairedClient(Pair pair, GroupPairFullInfoDto entry, string ownerUid, bool isOwner, bool isModerator)
{
var plusButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Plus);
var barButtonSize = UiShared.GetIconButtonSize(FontAwesomeIcon.Bars);
var entryUID = string.IsNullOrEmpty(entry.UserAlias) ? entry.UserUID : entry.UserAlias;
var entryUID = entry.UserAliasOrUID;
var textSize = ImGui.CalcTextSize(entryUID);
var originalY = ImGui.GetCursorPosY();
var userIsMod = entry.IsModerator ?? false;
var userIsMod = entry.GroupPairStatusInfo.IsModerator();
var userIsOwner = string.Equals(entryUID, ownerUid, StringComparison.Ordinal);
var isPinned = entry.GroupPairStatusInfo.IsPinned();
var isPaused = pair.IsPaused;
var presenceIcon = pair.IsVisible ? FontAwesomeIcon.Eye : (pair.IsOnline ? FontAwesomeIcon.Link : FontAwesomeIcon.Unlink);
var presenceColor = (pair.IsOnline || pair.IsVisible) ? ImGuiColors.ParsedGreen : ImGuiColors.DalamudRed;
var presenceText = entryUID + " is offline";
var soundsDisabled = entry.GroupUserPermissions.IsDisableSounds();
var animDisabled = entry.GroupUserPermissions.IsDisableAnimations();
var textPos = originalY + barButtonSize.Y / 2 - textSize.Y / 2;
ImGui.SetCursorPosY(textPos);
if (isPausedByYou || (entry.IsPaused ?? false))
if (pair.IsPaused)
{
presenceIcon = FontAwesomeIcon.Question;
presenceColor = ImGuiColors.DalamudGrey;
presenceText = entryUID + " online status is unknown (paused)";
ImGui.PushFont(UiBuilder.IconFont);
UiShared.ColorText(FontAwesomeIcon.PauseCircle.ToIconString(), ImGuiColors.DalamudYellow);
ImGui.PopFont();
@@ -583,6 +762,16 @@ namespace MareSynchronos.UI
UiShared.AttachToolTip("You are paired with " + entryUID);
}
if (pair.IsOnline && !pair.IsVisible) presenceText = entryUID + " is online";
else if (pair.IsOnline && pair.IsVisible) presenceText = entryUID + " is visible: " + pair.PlayerName;
ImGui.SameLine();
ImGui.SetCursorPosY(textPos);
ImGui.PushFont(UiBuilder.IconFont);
UiShared.ColorText(presenceIcon.ToIconString(), presenceColor);
ImGui.PopFont();
UiShared.AttachToolTip(presenceText);
if (userIsOwner)
{
ImGui.SameLine();
@@ -601,7 +790,7 @@ namespace MareSynchronos.UI
ImGui.PopFont();
UiShared.AttachToolTip("User is moderator of this Syncshell");
}
else if (entry.IsPinned ?? false)
else if (isPinned)
{
ImGui.SameLine();
ImGui.SetCursorPosY(textPos);
@@ -612,8 +801,8 @@ namespace MareSynchronos.UI
}
var textIsUid = true;
_mainUi.ShowUidForEntry.TryGetValue(entry.UserUID, out var showUidInsteadOfName);
if (!showUidInsteadOfName && _configuration.GetCurrentServerUidComments().TryGetValue(entry.UserUID, out var playerText))
_mainUi.ShowUidForEntry.TryGetValue(entry.UID, out var showUidInsteadOfName);
if (!showUidInsteadOfName && _serverConfigurationManager.CurrentServer!.UidServerComments.TryGetValue(entry.UID, out var playerText))
{
if (string.IsNullOrEmpty(playerText))
{
@@ -629,10 +818,10 @@ namespace MareSynchronos.UI
playerText = entryUID;
}
bool plusButtonShown = !_apiController.PairedClients.Any(p => string.Equals(p.OtherUID, entry.UserUID, StringComparison.Ordinal));
bool plusButtonShown = !_pairManager.DirectPairs.Any(p => string.Equals(p.UserData.UID, entry.UID, StringComparison.Ordinal));
ImGui.SameLine();
if (!string.Equals(_mainUi.EditNickEntry, entry.UserUID, StringComparison.Ordinal))
if (!string.Equals(_mainUi.EditNickEntry, entry.UID, StringComparison.Ordinal))
{
ImGui.SetCursorPosY(textPos);
if (textIsUid) ImGui.PushFont(UiBuilder.MonoFont);
@@ -643,22 +832,20 @@ namespace MareSynchronos.UI
if (ImGui.IsItemClicked(ImGuiMouseButton.Left))
{
var prevState = textIsUid;
if (_mainUi.ShowUidForEntry.ContainsKey(entry.UserUID))
if (_mainUi.ShowUidForEntry.ContainsKey(entry.UID))
{
prevState = _mainUi.ShowUidForEntry[entry.UserUID];
prevState = _mainUi.ShowUidForEntry[entry.UID];
}
_mainUi.ShowUidForEntry[entry.UserUID] = !prevState;
_mainUi.ShowUidForEntry[entry.UID] = !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;
_serverConfigurationManager.CurrentServer!.UidServerComments[_mainUi.EditNickEntry] = _mainUi.EditUserComment;
_serverConfigurationManager.Save();
_mainUi.EditUserComment = _serverConfigurationManager.CurrentServer.UidServerComments.TryGetValue(entry.UID, out string? value) ? value : string.Empty;
_mainUi.EditNickEntry = entry.UID;
}
}
else
@@ -670,8 +857,8 @@ namespace MareSynchronos.UI
ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetCursorPosX() - buttonSizes - ImGui.GetStyle().ItemSpacing.X * buttons);
if (ImGui.InputTextWithHint("", "Nick/Notes", ref _mainUi.EditUserComment, 255, ImGuiInputTextFlags.EnterReturnsTrue))
{
_configuration.SetCurrentServerUidComment(entry.UserUID, _mainUi.EditUserComment);
_configuration.Save();
_serverConfigurationManager.CurrentServer!.UidServerComments[entry.UID] = _mainUi.EditUserComment;
_serverConfigurationManager.Save();
_mainUi.EditNickEntry = string.Empty;
}
@@ -692,7 +879,7 @@ namespace MareSynchronos.UI
if (ImGuiComponents.IconButton(FontAwesomeIcon.Plus))
{
_ = _apiController.UserAddPair(entry.UserUID);
_ = ApiController.UserAddPair(new UserDto(entry.User));
}
UiShared.AttachToolTip("Pair with " + entryUID + " individually");
}
@@ -705,18 +892,64 @@ namespace MareSynchronos.UI
if (ImGuiComponents.IconButton(FontAwesomeIcon.Bars))
{
ImGui.OpenPopup("Popup");
}
}
if ((animDisabled || soundsDisabled) && pair.UserPair == null)
{
var infoIconPosDist = (plusButtonShown ? plusButtonSize.X + ImGui.GetStyle().ItemSpacing.X : 0)
+ ((isOwner || (isModerator && !userIsMod && !userIsOwner)) ? barButtonSize.X + ImGui.GetStyle().ItemSpacing.X : 0);
var icon = FontAwesomeIcon.InfoCircle;
var iconwidth = UiShared.GetIconSize(icon);
ImGui.SameLine(ImGui.GetWindowContentRegionMin().X + UiShared.GetWindowContentRegionWidth() - infoIconPosDist - iconwidth.X);
ImGui.SetCursorPosY(originalY);
UiShared.FontText(icon.ToIconString(), UiBuilder.IconFont);
if (ImGui.IsItemHovered())
{
ImGui.BeginTooltip();
ImGui.Text("User permissions");
if (soundsDisabled)
{
var userSoundsText = "Sound sync disabled by " + pair.UserData.AliasOrUID;
UiShared.FontText(FontAwesomeIcon.VolumeOff.ToIconString(), UiBuilder.IconFont);
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
ImGui.Text(userSoundsText);
}
if (animDisabled)
{
var userAnimText = "Animation sync disabled by " + pair.UserData.AliasOrUID;
UiShared.FontText(FontAwesomeIcon.Stop.ToIconString(), UiBuilder.IconFont);
ImGui.SameLine(40 * ImGuiHelpers.GlobalScale);
ImGui.Text(userAnimText);
}
ImGui.EndTooltip();
}
}
if (!plusButtonShown && !(isOwner || (isModerator && !userIsMod && !userIsOwner)))
{
ImGui.SameLine();
ImGui.Dummy(barButtonSize with { X = 0 });
}
if (ImGui.BeginPopup("Popup"))
{
if ((!entry.IsModerator ?? false) && !(userIsMod || userIsOwner))
if ((!isModerator) && !(userIsMod || userIsOwner))
{
var pinText = (entry?.IsPinned ?? false) ? "Unpin user" : "Pin user";
var pinText = isPinned ? "Unpin user" : "Pin user";
if (UiShared.IconTextButton(FontAwesomeIcon.Thumbtack, pinText))
{
ImGui.CloseCurrentPopup();
_ = _apiController.GroupChangePinned(entry.GroupGID, entry.UserUID, !entry.IsPinned ?? false);
var userInfo = entry.GroupPairStatusInfo ^ GroupUserInfo.IsPinned;
_ = ApiController.GroupSetUserInfo(new GroupPairUserInfoDto(entry.Group, entry.User, userInfo));
}
UiShared.AttachToolTip("Pin this user to the Syncshell. Pinned users will not be deleted in case of a manually initiated Syncshell clean");
@@ -725,11 +958,11 @@ namespace MareSynchronos.UI
if (UiShared.CtrlPressed())
{
ImGui.CloseCurrentPopup();
_ = _apiController.GroupRemoveUser(entry.GroupGID, entry.UserUID);
_ = ApiController.GroupRemoveUser(entry);
}
}
UiShared.AttachToolTip("Hold CTRL and click to remove user " + (entry.UserAlias ?? entry.UserUID) + " from Syncshell");
UiShared.AttachToolTip("Hold CTRL and click to remove user " + (entry.UserAliasOrUID) + " from Syncshell");
if (UiShared.IconTextButton(FontAwesomeIcon.UserSlash, "Ban User"))
{
_showModalBanUser = true;
@@ -740,36 +973,31 @@ namespace MareSynchronos.UI
if (isOwner)
{
string modText = (entry.IsModerator ?? false) ? "Demod user" : "Mod user";
string modText = userIsMod ? "Demod user" : "Mod user";
if (UiShared.IconTextButton(FontAwesomeIcon.UserShield, modText))
{
if (UiShared.CtrlPressed())
{
ImGui.CloseCurrentPopup();
_ = _apiController.GroupSetModerator(entry.GroupGID, entry.UserUID, !entry.IsModerator ?? false);
var userInfo = entry.GroupPairStatusInfo ^ GroupUserInfo.IsModerator;
_ = ApiController.GroupSetUserInfo(new GroupPairUserInfoDto(entry.Group, entry.User, userInfo));
}
}
UiShared.AttachToolTip("Hold CTRL to change the moderator status for " + (entry.UserAlias ?? entry.UserUID) + Environment.NewLine +
UiShared.AttachToolTip("Hold CTRL to change the moderator status for " + (entry.UserAliasOrUID) + Environment.NewLine +
"Moderators can kick, ban/unban, pin/unpin users and clear the Syncshell.");
if (UiShared.IconTextButton(FontAwesomeIcon.Crown, "Transfer Ownership"))
{
if (UiShared.CtrlPressed() && UiShared.ShiftPressed())
{
ImGui.CloseCurrentPopup();
_ = _apiController.GroupChangeOwnership(entry.GroupGID, entry.UserUID);
_ = ApiController.GroupChangeOwnership(entry);
}
}
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.");
UiShared.AttachToolTip("Hold CTRL and SHIFT and click to transfer ownership of this Syncshell to " + (entry.UserAliasOrUID) + Environment.NewLine + "WARNING: This action is irreversible.");
}
ImGui.EndPopup();
}
if (!plusButtonShown && !(isOwner || (isModerator && !userIsMod && !userIsOwner)))
{
ImGui.SameLine();
ImGui.Dummy(barButtonSize with { X = 0 });
}
if (_showModalBanUser && !_banUserPopupOpen)
{
ImGui.OpenPopup("Ban User");
@@ -780,13 +1008,13 @@ namespace MareSynchronos.UI
if (ImGui.BeginPopupModal("Ban User", ref _showModalBanUser, UiShared.PopupWindowFlags))
{
UiShared.TextWrapped("User " + (entry.UserAlias ?? entry.UserUID) + " will be banned and removed from this Syncshell.");
UiShared.TextWrapped("User " + (entry.UserAliasOrUID) + " will be banned and removed from this Syncshell.");
ImGui.InputTextWithHint("##banreason", "Ban Reason", ref _banReason, 255);
if (ImGui.Button("Ban User"))
{
ImGui.CloseCurrentPopup();
var reason = _banReason;
_ = _apiController.GroupBanUser(entry.GroupGID, entry.UserUID, reason);
_ = ApiController.GroupBanUser(entry, reason);
_banReason = string.Empty;
}
UiShared.TextWrapped("The reason will be displayed in the banlist. The current server-side alias if present (Vanity ID) will automatically be attached to the reason.");

View File

@@ -1,25 +1,21 @@
using System;
using System.Collections.Generic;
using System.Linq;
using MareSynchronos.API;
using MareSynchronos.WebAPI;
using MareSynchronos.API.Dto.User;
using MareSynchronos.Managers;
namespace MareSynchronos.UI.Handlers
{
public class TagHandler
{
private readonly Configuration _configuration;
private readonly ApiController _apiController;
private readonly ServerConfigurationManager _serverConfigurationManager;
public TagHandler(Configuration configuration)
public TagHandler(ServerConfigurationManager serverConfigurationManager)
{
_configuration = configuration;
_serverConfigurationManager = serverConfigurationManager;
}
public void AddTag(string tag)
{
GetAvailableTagsForCurrentServer().Add(tag);
_configuration.Save();
_serverConfigurationManager.Save();
}
public void RemoveTag(string tag)
@@ -30,20 +26,20 @@ namespace MareSynchronos.UI.Handlers
GetUidTagDictionaryForCurrentServer().Keys
.ToList()
.ForEach(otherUid => RemoveTagFromPairedUid(otherUid, tag));
_configuration.Save();
_serverConfigurationManager.Save();
}
public void SetTagOpen(string tag, bool open)
{
if (open)
{
_configuration.OpenPairTags.Add(tag);
_serverConfigurationManager.CurrentServer!.OpenPairTags.Add(tag);
}
else
{
_configuration.OpenPairTags.Remove(tag);
_serverConfigurationManager.CurrentServer!.OpenPairTags.Remove(tag);
}
_configuration.Save();
_serverConfigurationManager.Save();
}
/// <summary>
@@ -53,7 +49,7 @@ namespace MareSynchronos.UI.Handlers
/// <returns>open true/false</returns>
public bool IsTagOpen(string tag)
{
return _configuration.OpenPairTags.Contains(tag);
return _serverConfigurationManager.CurrentServer!.OpenPairTags.Contains(tag);
}
public List<string> GetAllTagsSorted()
@@ -71,30 +67,30 @@ namespace MareSynchronos.UI.Handlers
.ToHashSet(StringComparer.Ordinal);
}
public void AddTagToPairedUid(ClientPairDto pair, string tagName)
public void AddTagToPairedUid(UserPairDto pair, string tagName)
{
var tagDictionary = GetUidTagDictionaryForCurrentServer();
var tagsForPair = tagDictionary.GetValueOrDefault(pair.OtherUID, new List<string>());
var tagsForPair = tagDictionary.GetValueOrDefault(pair.User.UID, new List<string>());
tagsForPair.Add(tagName);
tagDictionary[pair.OtherUID] = tagsForPair;
_configuration.Save();
tagDictionary[pair.User.UID] = tagsForPair;
_serverConfigurationManager.Save();
}
public void RemoveTagFromPairedUid(ClientPairDto pair, string tagName)
public void RemoveTagFromPairedUid(UserPairDto pair, string tagName)
{
RemoveTagFromPairedUid(pair.OtherUID, tagName);
_configuration.Save();
RemoveTagFromPairedUid(pair.User.UID, tagName);
_serverConfigurationManager.Save();
}
public bool HasTag(ClientPairDto pair, string tagName)
public bool HasTag(UserPairDto pair, string tagName)
{
var tagsForPair = GetUidTagDictionaryForCurrentServer().GetValueOrDefault(pair.OtherUID, new List<string>());
var tagsForPair = GetUidTagDictionaryForCurrentServer().GetValueOrDefault(pair.User.UID, new List<string>());
return tagsForPair.Contains(tagName, StringComparer.Ordinal);
}
public bool HasAnyTag(ClientPairDto pair)
public bool HasAnyTag(UserPairDto pair)
{
return GetUidTagDictionaryForCurrentServer().ContainsKey(pair.OtherUID);
return GetUidTagDictionaryForCurrentServer().ContainsKey(pair.User.UID);
}
private void RemoveTagFromPairedUid(string otherUid, string tagName)
@@ -114,22 +110,12 @@ namespace MareSynchronos.UI.Handlers
private Dictionary<string, List<string>> GetUidTagDictionaryForCurrentServer()
{
if (!_configuration.UidServerPairedUserTags.ContainsKey(_configuration.ApiUri))
{
_configuration.UidServerPairedUserTags.Add(_configuration.ApiUri, new(StringComparer.Ordinal));
}
return _configuration.UidServerPairedUserTags[_configuration.ApiUri];
return _serverConfigurationManager.CurrentServer!.UidServerPairedUserTags;
}
private HashSet<string> GetAvailableTagsForCurrentServer()
{
if (!_configuration.ServerAvailablePairTags.ContainsKey(_configuration.ApiUri))
{
_configuration.ServerAvailablePairTags.Add(_configuration.ApiUri, new(StringComparer.Ordinal));
}
return _configuration.ServerAvailablePairTags[_configuration.ApiUri];
return _serverConfigurationManager.CurrentServer!.ServerAvailablePairTags;
}
}
}

View File

@@ -1,9 +1,4 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using System.Numerics;
using Dalamud.Interface.Colors;
using Dalamud.Interface.Windowing;
using ImGuiNET;
@@ -11,25 +6,30 @@ using MareSynchronos.Utils;
using MareSynchronos.Localization;
using Dalamud.Utility;
using MareSynchronos.FileCache;
using Dalamud.Interface;
using MareSynchronos.Managers;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Delegates;
namespace MareSynchronos.UI;
internal class IntroUi : Window, IDisposable
{
private readonly UiShared _uiShared;
private readonly Configuration _pluginConfiguration;
private readonly ConfigurationService _configService;
private readonly PeriodicFileScanner _fileCacheManager;
private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly WindowSystem _windowSystem;
private bool _readFirstPage;
public event SwitchUi? SwitchToMainUi;
public event VoidDelegate? SwitchToMainUi;
private string[] TosParagraphs;
private string[]? _tosParagraphs;
private Task _timeoutTask;
private string _timeoutLabel;
private Task? _timeoutTask;
private string _timeoutLabel = string.Empty;
private Dictionary<string, string> _languages = new(StringComparer.Ordinal) { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } };
private readonly Dictionary<string, string> _languages = new(StringComparer.Ordinal) { { "English", "en" }, { "Deutsch", "de" }, { "Français", "fr" } };
private int _currentLanguage;
public void Dispose()
@@ -39,20 +39,22 @@ internal class IntroUi : Window, IDisposable
_windowSystem.RemoveWindow(this);
}
public IntroUi(WindowSystem windowSystem, UiShared uiShared, Configuration pluginConfiguration,
PeriodicFileScanner fileCacheManager) : base("Mare Synchronos Setup")
public IntroUi(WindowSystem windowSystem, UiShared uiShared, ConfigurationService configService,
PeriodicFileScanner fileCacheManager, ServerConfigurationManager serverConfigurationManager) : base("Mare Synchronos Setup")
{
Logger.Verbose("Creating " + nameof(IntroUi));
_uiShared = uiShared;
_pluginConfiguration = pluginConfiguration;
_configService = configService;
_fileCacheManager = fileCacheManager;
_serverConfigurationManager = serverConfigurationManager;
_windowSystem = windowSystem;
IsOpen = false;
SizeConstraints = new WindowSizeConstraints()
{
MinimumSize = new Vector2(600, 400),
MaximumSize = new Vector2(600, 2000)
MaximumSize = new Vector2(600, 2000),
};
GetToSLocalization();
@@ -64,7 +66,7 @@ internal class IntroUi : Window, IDisposable
{
if (_uiShared.IsInGpose) return;
if (!_pluginConfiguration.AcceptedAgreement && !_readFirstPage)
if (!_configService.Current.AcceptedAgreement && !_readFirstPage)
{
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
ImGui.TextUnformatted("Welcome to Mare Synchronos");
@@ -87,13 +89,12 @@ internal class IntroUi : Window, IDisposable
for (int i = 60; i > 0; i--)
{
_timeoutLabel = $"{Strings.ToS.ButtonWillBeAvailableIn} {i}s";
Logger.Debug(_timeoutLabel);
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
}
});
}
}
else if (!_pluginConfiguration.AcceptedAgreement && _readFirstPage)
else if (!_configService.Current.AcceptedAgreement && _readFirstPage)
{
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
var textSize = ImGui.CalcTextSize(Strings.ToS.LanguageLabel);
@@ -124,20 +125,20 @@ internal class IntroUi : Window, IDisposable
ImGui.Separator();
UiShared.TextWrapped(TosParagraphs[0]);
UiShared.TextWrapped(TosParagraphs[1]);
UiShared.TextWrapped(TosParagraphs[2]);
UiShared.TextWrapped(TosParagraphs[3]);
UiShared.TextWrapped(TosParagraphs[4]);
UiShared.TextWrapped(TosParagraphs[5]);
UiShared.TextWrapped(_tosParagraphs![0]);
UiShared.TextWrapped(_tosParagraphs![1]);
UiShared.TextWrapped(_tosParagraphs![2]);
UiShared.TextWrapped(_tosParagraphs![3]);
UiShared.TextWrapped(_tosParagraphs![4]);
UiShared.TextWrapped(_tosParagraphs![5]);
ImGui.Separator();
if (_timeoutTask?.IsCompleted ?? true)
{
if (ImGui.Button(Strings.ToS.AgreeLabel + "##toSetup"))
{
_pluginConfiguration.AcceptedAgreement = true;
_pluginConfiguration.Save();
_configService.Current.AcceptedAgreement = true;
_configService.Save();
}
}
else
@@ -145,10 +146,10 @@ internal class IntroUi : Window, IDisposable
UiShared.TextWrapped(_timeoutLabel);
}
}
else if (_pluginConfiguration.AcceptedAgreement
&& (string.IsNullOrEmpty(_pluginConfiguration.CacheFolder)
|| _pluginConfiguration.InitialScanComplete == false
|| !Directory.Exists(_pluginConfiguration.CacheFolder)))
else if (_configService.Current.AcceptedAgreement
&& (string.IsNullOrEmpty(_configService.Current.CacheFolder)
|| _configService.Current.InitialScanComplete == false
|| !Directory.Exists(_configService.Current.CacheFolder)))
{
if (_uiShared.UidFontBuilt) ImGui.PushFont(_uiShared.UidFont);
ImGui.TextUnformatted("File Storage Setup");
@@ -171,11 +172,11 @@ internal class IntroUi : Window, IDisposable
_uiShared.DrawCacheDirectorySetting();
}
if (!_fileCacheManager.IsScanRunning && !string.IsNullOrEmpty(_pluginConfiguration.CacheFolder) && _uiShared.HasValidPenumbraModPath && Directory.Exists(_pluginConfiguration.CacheFolder))
if (!_fileCacheManager.IsScanRunning && !string.IsNullOrEmpty(_configService.Current.CacheFolder) && _uiShared.HasValidPenumbraModPath && Directory.Exists(_configService.Current.CacheFolder))
{
if (ImGui.Button("Start Scan##startScan"))
{
_fileCacheManager.InvokeScan(true);
_fileCacheManager.InvokeScan(forced: true);
}
}
else
@@ -204,7 +205,37 @@ internal class IntroUi : Window, IDisposable
UiShared.TextWrapped("Once you have received a secret key you can connect to the service using the tools provided below.");
_uiShared.DrawServiceSelection(() => { });
var idx = _uiShared.DrawServiceSelection(selectOnChange: true);
var text = "Enter Secret Key";
var buttonText = "Save";
var buttonWidth = _secretKey.Length != 64 ? 0 : ImGuiHelpers.GetButtonSize(buttonText).X + ImGui.GetStyle().ItemSpacing.X;
var textSize = ImGui.CalcTextSize(text);
ImGui.AlignTextToFramePadding();
ImGui.Text(text);
ImGui.SameLine();
ImGui.SetNextItemWidth(UiShared.GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonWidth - textSize.X);
ImGui.InputText("", ref _secretKey, 64);
if (_secretKey.Length > 0 && _secretKey.Length != 64)
{
UiShared.ColorTextWrapped("Your secret key must be exactly 64 characters long. Don't enter your Lodestone auth here.", ImGuiColors.DalamudRed);
}
else if (_secretKey.Length == 64)
{
ImGui.SameLine();
if (ImGui.Button(buttonText))
{
if (_serverConfigurationManager.CurrentServer == null) _serverConfigurationManager.SelectServer(0);
_serverConfigurationManager.CurrentServer!.SecretKeys.Add(_serverConfigurationManager.CurrentServer.SecretKeys.Select(k => k.Key).LastOrDefault() + 1, new SecretKey()
{
FriendlyName = $"Secret Key added on Setup ({DateTime.Now:yyyy-MM-dd})",
Key = _secretKey,
});
_serverConfigurationManager.AddCurrentCharacterToServer(addLastSecretKey: true);
_secretKey = string.Empty;
Task.Run(() => _uiShared.ApiController.CreateConnections(forceGetToken: true));
}
}
}
else
{
@@ -222,6 +253,6 @@ internal class IntroUi : Window, IDisposable
_uiShared.LoadLocalization(_languages.ElementAt(changeLanguageTo).Value);
}
TosParagraphs = new[] { Strings.ToS.Paragraph1, Strings.ToS.Paragraph2, Strings.ToS.Paragraph3, Strings.ToS.Paragraph4, Strings.ToS.Paragraph5, Strings.ToS.Paragraph6 };
_tosParagraphs = new[] { Strings.ToS.Paragraph1, Strings.ToS.Paragraph2, Strings.ToS.Paragraph3, Strings.ToS.Paragraph4, Strings.ToS.Paragraph5, Strings.ToS.Paragraph6 };
}
}

View File

@@ -3,42 +3,40 @@ using Dalamud.Interface.Colors;
using Dalamud.Interface.Windowing;
using ImGuiNET;
using MareSynchronos.WebAPI;
using System;
using System.IO;
using System.Linq;
using System.Numerics;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
using Dalamud.Utility;
using Newtonsoft.Json;
using MareSynchronos.Export;
using MareSynchronos.API.Data;
using MareSynchronos.Managers;
using MareSynchronos.API.Data.Comparer;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Delegates;
namespace MareSynchronos.UI;
public delegate void SwitchUi();
public class SettingsUi : Window, IDisposable
{
private readonly Configuration _configuration;
private readonly ConfigurationService _configService;
private readonly WindowSystem _windowSystem;
private readonly ApiController _apiController;
private ApiController ApiController => _uiShared.ApiController;
private readonly MareCharaFileManager _mareCharaFileManager;
private readonly PairManager _pairManager;
private readonly ServerConfigurationManager _serverConfigurationManager;
private readonly UiShared _uiShared;
public CharacterCacheDto LastCreatedCharacterData { private get; set; }
public CharacterData? LastCreatedCharacterData { private get; set; }
public event SwitchUi? SwitchToIntroUi;
public event VoidDelegate? SwitchToIntroUi;
private bool _overwriteExistingLabels = false;
private bool? _notesSuccessfullyApplied = null;
private string _lastTab = string.Empty;
private bool _openPopupOnAddition;
private bool _hideInfoMessages;
private bool _disableOptionalPluginsWarnings;
private bool _wasOpen = false;
public SettingsUi(WindowSystem windowSystem,
UiShared uiShared, Configuration configuration, ApiController apiController,
MareCharaFileManager mareCharaFileManager) : base("Mare Synchronos Settings")
UiShared uiShared, ConfigurationService configService,
MareCharaFileManager mareCharaFileManager, PairManager pairManager, ServerConfigurationManager serverConfigurationManager) : base("Mare Synchronos Settings")
{
Logger.Verbose("Creating " + nameof(SettingsUi));
@@ -48,27 +46,26 @@ public class SettingsUi : Window, IDisposable
MaximumSize = new Vector2(800, 2000),
};
_configuration = configuration;
_configService = configService;
_windowSystem = windowSystem;
_apiController = apiController;
_mareCharaFileManager = mareCharaFileManager;
_pairManager = pairManager;
_serverConfigurationManager = serverConfigurationManager;
_uiShared = uiShared;
_openPopupOnAddition = _configuration.OpenPopupOnAdd;
_hideInfoMessages = _configuration.HideInfoMessages;
_disableOptionalPluginsWarnings = _configuration.DisableOptionalPluginWarnings;
_uiShared.GposeStart += _uiShared_GposeStart;
_uiShared.GposeEnd += _uiShared_GposeEnd;
_uiShared.GposeStart += UiShared_GposeStart;
_uiShared.GposeEnd += UiShared_GposeEnd;
windowSystem.AddWindow(this);
}
private void _uiShared_GposeEnd()
private void UiShared_GposeEnd()
{
IsOpen = _wasOpen;
}
private void _uiShared_GposeStart()
private void UiShared_GposeStart()
{
_wasOpen = IsOpen;
IsOpen = false;
@@ -78,15 +75,15 @@ public class SettingsUi : Window, IDisposable
{
Logger.Verbose("Disposing " + nameof(SettingsUi));
_uiShared.GposeStart -= _uiShared_GposeStart;
_uiShared.GposeEnd -= _uiShared_GposeEnd;
_uiShared.GposeStart -= UiShared_GposeStart;
_uiShared.GposeEnd -= UiShared_GposeEnd;
_windowSystem.RemoveWindow(this);
}
public override void Draw()
{
var pluginState = _uiShared.DrawOtherPluginState();
_ = _uiShared.DrawOtherPluginState();
DrawSettingsContent();
}
@@ -116,7 +113,7 @@ public class SettingsUi : Window, IDisposable
ImGui.EndTabItem();
}
if (_apiController.ServerState is ServerState.Connected)
if (ApiController.ServerState is ServerState.Connected)
{
if (ImGui.BeginTabItem("Transfers"))
{
@@ -131,344 +128,29 @@ public class SettingsUi : Window, IDisposable
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("User Administration"))
if (ImGui.BeginTabItem("Service Settings"))
{
DrawUserAdministration(_apiController.IsConnected);
DrawServerConfiguration();
ImGui.EndTabItem();
}
if (_apiController.IsConnected && _apiController.IsModerator)
if (ImGui.BeginTabItem("Debug"))
{
if (ImGui.BeginTabItem("Administration"))
{
DrawAdministration();
DrawDebug();
ImGui.EndTabItem();
}
}
ImGui.EndTabBar();
}
}
private string _forbiddenFileHashEntry = string.Empty;
private string _forbiddenFileHashForbiddenBy = string.Empty;
private string _bannedUserHashEntry = string.Empty;
private string _bannedUserReasonEntry = string.Empty;
private void DrawServerConfiguration()
{
_lastTab = "Service Settings";
if (ApiController.ServerAlive)
{
UiShared.FontText("Service Actions", _uiShared.UidFont);
private void DrawGeneral()
{
if (!string.Equals(_lastTab, "General", StringComparison.OrdinalIgnoreCase))
{
_notesSuccessfullyApplied = null;
}
_lastTab = "General";
UiShared.FontText("Notes", _uiShared.UidFont);
if (UiShared.IconTextButton(FontAwesomeIcon.StickyNote, "Export all your user notes to clipboard"))
{
ImGui.SetClipboardText(_uiShared.GetNotes());
}
if (UiShared.IconTextButton(FontAwesomeIcon.FileImport, "Import notes from clipboard"))
{
_notesSuccessfullyApplied = null;
var notes = ImGui.GetClipboardText();
_notesSuccessfullyApplied = _uiShared.ApplyNotesFromClipboard(notes, _overwriteExistingLabels);
}
ImGui.SameLine();
ImGui.Checkbox("Overwrite existing notes", ref _overwriteExistingLabels);
UiShared.DrawHelpText("If this option is selected all already existing notes for UIDs will be overwritten by the imported notes.");
if (_notesSuccessfullyApplied.HasValue && _notesSuccessfullyApplied.Value)
{
UiShared.ColorTextWrapped("User Notes successfully imported", ImGuiColors.HealerGreen);
}
else if (_notesSuccessfullyApplied.HasValue && !_notesSuccessfullyApplied.Value)
{
UiShared.ColorTextWrapped("Attempt to import notes from clipboard failed. Check formatting and try again", ImGuiColors.DalamudRed);
}
if (ImGui.Checkbox("Open Notes Popup on user addition", ref _openPopupOnAddition))
{
_apiController.LastAddedUser = null;
_configuration.OpenPopupOnAdd = _openPopupOnAddition;
_configuration.Save();
}
UiShared.DrawHelpText("This will open a popup that allows you to set the notes for a user after successfully adding them to your individual pairs.");
ImGui.Separator();
UiShared.FontText("Server Messages", _uiShared.UidFont);
if (ImGui.Checkbox("Hide Server Info Messages", ref _hideInfoMessages))
{
_configuration.HideInfoMessages = _hideInfoMessages;
_configuration.Save();
}
UiShared.DrawHelpText("Enabling this will not print any \"Info\" labeled messages into the game chat.");
if (ImGui.Checkbox("Disable optional plugin warnings", ref _disableOptionalPluginsWarnings))
{
_configuration.DisableOptionalPluginWarnings = _disableOptionalPluginsWarnings;
_configuration.Save();
}
UiShared.DrawHelpText("Enabling this will not print any \"Warning\" labeled messages for missing optional plugins Heels or Customize+ in the game chat.");
}
private void DrawAdministration()
{
_lastTab = "Administration";
if (ImGui.TreeNode("Forbidden Files Changes"))
{
if (ImGui.BeginTable("ForbiddenFilesTable", 3, ImGuiTableFlags.RowBg))
{
ImGui.TableSetupColumn("File Hash", ImGuiTableColumnFlags.None, 290);
ImGui.TableSetupColumn("Forbidden By", ImGuiTableColumnFlags.None, 290);
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 70);
ImGui.TableHeadersRow();
foreach (var forbiddenFile in _apiController.AdminForbiddenFiles)
{
ImGui.TableNextColumn();
ImGui.Text(forbiddenFile.Hash);
ImGui.TableNextColumn();
string by = forbiddenFile.ForbiddenBy;
if (ImGui.InputText("##forbiddenBy" + forbiddenFile.Hash, ref by, 255))
{
forbiddenFile.ForbiddenBy = by;
}
ImGui.TableNextColumn();
if (_apiController.IsAdmin)
{
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(
FontAwesomeIcon.Upload.ToIconString() + "##updateFile" + forbiddenFile.Hash))
{
_ = _apiController.AdminUpdateOrAddForbiddenFile(forbiddenFile);
}
ImGui.SameLine();
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString() + "##deleteFile" +
forbiddenFile.Hash))
{
_ = _apiController.AdminDeleteForbiddenFile(forbiddenFile);
}
ImGui.PopFont();
}
}
if (_apiController.IsAdmin)
{
ImGui.TableNextColumn();
ImGui.InputText("##addFileHash", ref _forbiddenFileHashEntry, 255);
ImGui.TableNextColumn();
ImGui.InputText("##addForbiddenBy", ref _forbiddenFileHashForbiddenBy, 255);
ImGui.TableNextColumn();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString() + "##addForbiddenFile"))
{
_ = _apiController.AdminUpdateOrAddForbiddenFile(new ForbiddenFileDto()
{
ForbiddenBy = _forbiddenFileHashForbiddenBy,
Hash = _forbiddenFileHashEntry
});
}
ImGui.PopFont();
ImGui.NextColumn();
}
ImGui.EndTable();
}
ImGui.TreePop();
}
if (ImGui.TreeNode("Banned Users"))
{
if (ImGui.BeginTable("BannedUsersTable", 3, ImGuiTableFlags.RowBg))
{
ImGui.TableSetupColumn("Character Hash", ImGuiTableColumnFlags.None, 290);
ImGui.TableSetupColumn("Reason", ImGuiTableColumnFlags.None, 290);
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 70);
ImGui.TableHeadersRow();
foreach (var bannedUser in _apiController.AdminBannedUsers)
{
ImGui.TableNextColumn();
ImGui.Text(bannedUser.CharacterHash);
ImGui.TableNextColumn();
string reason = bannedUser.Reason;
ImGuiInputTextFlags moderatorFlags = _apiController.IsModerator
? ImGuiInputTextFlags.ReadOnly
: ImGuiInputTextFlags.None;
if (ImGui.InputText("##bannedReason" + bannedUser.CharacterHash, ref reason, 255,
moderatorFlags))
{
bannedUser.Reason = reason;
}
ImGui.TableNextColumn();
ImGui.PushFont(UiBuilder.IconFont);
if (_apiController.IsAdmin)
{
if (ImGui.Button(FontAwesomeIcon.Upload.ToIconString() + "##updateUser" +
bannedUser.CharacterHash))
{
_ = _apiController.AdminUpdateOrAddBannedUser(bannedUser);
}
ImGui.SameLine();
}
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString() + "##deleteUser" +
bannedUser.CharacterHash))
{
_ = _apiController.AdminDeleteBannedUser(bannedUser);
}
ImGui.PopFont();
}
ImGui.TableNextColumn();
ImGui.InputText("##addUserHash", ref _bannedUserHashEntry, 255);
ImGui.TableNextColumn();
if (_apiController.IsAdmin)
{
ImGui.InputText("##addUserReason", ref _bannedUserReasonEntry, 255);
}
else
{
_bannedUserReasonEntry = "Banned by " + _uiShared.PlayerName;
ImGui.InputText("##addUserReason", ref _bannedUserReasonEntry, 255,
ImGuiInputTextFlags.ReadOnly);
}
ImGui.TableNextColumn();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Plus.ToIconString() + "##addForbiddenFile"))
{
_ = _apiController.AdminUpdateOrAddBannedUser(new BannedUserDto()
{
CharacterHash = _forbiddenFileHashForbiddenBy,
Reason = _forbiddenFileHashEntry
});
}
ImGui.PopFont();
ImGui.EndTable();
}
ImGui.TreePop();
}
if (ImGui.TreeNode("Online Users"))
{
if (ImGui.Button("Refresh Online Users"))
{
_ = _apiController.RefreshOnlineUsers();
}
if (ImGui.BeginTable("OnlineUsersTable", 3, ImGuiTableFlags.RowBg))
{
ImGui.TableSetupColumn("UID", ImGuiTableColumnFlags.None, 100);
ImGui.TableSetupColumn("Character Hash", ImGuiTableColumnFlags.None, 300);
ImGui.TableSetupColumn("Actions", ImGuiTableColumnFlags.None, 70);
ImGui.TableHeadersRow();
foreach (var onlineUser in _apiController.AdminOnlineUsers)
{
ImGui.TableNextColumn();
ImGui.PushFont(UiBuilder.IconFont);
string icon = onlineUser.IsModerator
? FontAwesomeIcon.ChessKing.ToIconString()
: onlineUser.IsAdmin
? FontAwesomeIcon.Crown.ToIconString()
: FontAwesomeIcon.User.ToIconString();
ImGui.Text(icon);
ImGui.PopFont();
ImGui.SameLine();
ImGui.Text(onlineUser.UID);
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Copy.ToIconString() + "##onlineUserCopyUID" +
onlineUser.CharacterNameHash))
{
ImGui.SetClipboardText(onlineUser.UID);
}
ImGui.PopFont();
ImGui.TableNextColumn();
string charNameHash = onlineUser.CharacterNameHash;
ImGui.InputText("##onlineUserHash" + onlineUser.CharacterNameHash, ref charNameHash, 255,
ImGuiInputTextFlags.ReadOnly);
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Copy.ToIconString() + "##onlineUserCopyHash" +
onlineUser.CharacterNameHash))
{
ImGui.SetClipboardText(onlineUser.UID);
}
ImGui.PopFont();
ImGui.TableNextColumn();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.SkullCrossbones.ToIconString() + "##onlineUserBan" +
onlineUser.CharacterNameHash))
{
_ = _apiController.AdminUpdateOrAddBannedUser(new BannedUserDto
{
CharacterHash = onlineUser.CharacterNameHash,
Reason = "Banned by " + _uiShared.PlayerName
});
}
ImGui.SameLine();
if (!string.Equals(onlineUser.UID, _apiController.UID, StringComparison.Ordinal) && _apiController.IsAdmin)
{
if (!onlineUser.IsModerator)
{
if (ImGui.Button(FontAwesomeIcon.ChessKing.ToIconString() +
"##onlineUserModerator" +
onlineUser.CharacterNameHash))
{
_apiController.AdminChangeModeratorStatus(onlineUser.UID, true);
}
}
else
{
if (ImGui.Button(FontAwesomeIcon.User.ToIconString() +
"##onlineUserNonModerator" +
onlineUser.CharacterNameHash))
{
_apiController.AdminChangeModeratorStatus(onlineUser.UID, false);
}
}
}
ImGui.PopFont();
}
ImGui.EndTable();
}
ImGui.TreePop();
}
}
private bool _deleteFilesPopupModalShown = false;
private bool _deleteAccountPopupModalShown = false;
private void DrawUserAdministration(bool serverAlive)
{
_lastTab = "UserAdministration";
if (serverAlive)
{
if (ImGui.Button("Delete all my files"))
{
_deleteFilesPopupModalShown = true;
@@ -490,7 +172,7 @@ public class SettingsUi : Window, IDisposable
if (ImGui.Button("Delete everything", new Vector2(buttonSize, 0)))
{
Task.Run(() => _apiController.FilesDeleteAll());
Task.Run(() => ApiController.FilesDeleteAll());
_deleteFilesPopupModalShown = false;
}
@@ -504,7 +186,7 @@ public class SettingsUi : Window, IDisposable
UiShared.SetScaledWindowSize(325);
ImGui.EndPopup();
}
ImGui.SameLine();
if (ImGui.Button("Delete account"))
{
_deleteAccountPopupModalShown = true;
@@ -527,7 +209,7 @@ public class SettingsUi : Window, IDisposable
if (ImGui.Button("Delete account", new Vector2(buttonSize, 0)))
{
Task.Run(() => _apiController.UserDelete());
Task.Run(() => ApiController.UserDelete());
_deleteAccountPopupModalShown = false;
SwitchToIntroUi?.Invoke();
}
@@ -542,37 +224,277 @@ public class SettingsUi : Window, IDisposable
UiShared.SetScaledWindowSize(325);
ImGui.EndPopup();
}
ImGui.Separator();
}
if (!_configuration.FullPause)
UiShared.FontText("Service & Character Settings", _uiShared.UidFont);
var idx = _uiShared.DrawServiceSelection();
ImGui.Dummy(new Vector2(10, 10));
var selectedServer = _serverConfigurationManager.GetServerByIndex(idx);
if (selectedServer == _serverConfigurationManager.CurrentServer)
{
UiShared.ColorTextWrapped("Note: to change servers or your secret key you need to disconnect from your current Mare Synchronos server.", ImGuiColors.DalamudYellow);
UiShared.ColorTextWrapped("For any changes to be applied to the current service you need to reconnect to the service.", ImGuiColors.DalamudYellow);
}
var marePaused = _configuration.FullPause;
if (_configuration.HasValidSetup())
if (ImGui.BeginTabBar("serverTabBar"))
{
if (ImGui.Checkbox("Disconnect Mare Synchronos", ref marePaused))
if (ImGui.BeginTabItem("Character Management"))
{
_configuration.FullPause = marePaused;
_configuration.Save();
Task.Run(() => _apiController.CreateConnections(false));
UiShared.ColorTextWrapped("Characters listed here will automatically connect to the selected Mare service with the settings as provided below." +
" Make sure to enter the character names correctly or use the 'Add current character' button at the bottom.", ImGuiColors.DalamudYellow);
int i = 0;
foreach (var item in selectedServer.Authentications.ToList())
{
UiShared.DrawWithID("selectedChara" + i, () =>
{
var charaName = item.CharacterName;
if (ImGui.InputText("Character Name", ref charaName, 64))
{
item.CharacterName = charaName;
_serverConfigurationManager.Save();
}
var worldIdx = (ushort)item.WorldId;
var data = _uiShared.WorldData;
if (!data.TryGetValue(worldIdx, out string? worldPreview))
{
worldPreview = data.First().Value;
}
if (ImGui.BeginCombo("World", worldPreview))
{
foreach (var world in data)
{
bool isSelected = worldIdx == world.Key;
if (ImGui.Selectable(world.Value, isSelected))
{
item.WorldId = world.Key;
_serverConfigurationManager.Save();
}
}
ImGui.EndCombo();
}
var secretKeyIdx = item.SecretKeyIdx;
var keys = selectedServer.SecretKeys;
if (!keys.TryGetValue(secretKeyIdx, out var secretKey))
{
secretKey = new();
}
var friendlyName = secretKey.FriendlyName;
if (ImGui.BeginCombo("Secret Key", friendlyName))
{
foreach (var kvp in keys)
{
bool isSelected = kvp.Key == secretKeyIdx;
if (ImGui.Selectable(kvp.Value.FriendlyName, isSelected))
{
item.SecretKeyIdx = kvp.Key;
_serverConfigurationManager.Save();
}
}
ImGui.EndCombo();
}
UiShared.DrawHelpText("Completely pauses the sync and clears your current data (not uploaded files) on the service.");
}
else
if (UiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Character"))
{
UiShared.ColorText("You cannot reconnect without a valid account on the service.", ImGuiColors.DalamudYellow);
if (UiShared.CtrlPressed())
_serverConfigurationManager.RemoveCharacterFromServer(idx, item);
}
UiShared.AttachToolTip("Hold CTRL to delete this entry.");
if (item != selectedServer.Authentications.LastOrDefault())
ImGui.Separator();
});
i++;
if (marePaused)
{
_uiShared.DrawServiceSelection(() => { });
}
ImGui.Separator();
if (!selectedServer.Authentications.Any(c => string.Equals(c.CharacterName, _uiShared.PlayerName, StringComparison.Ordinal)
&& c.WorldId == _uiShared.WorldId))
{
if (UiShared.IconTextButton(FontAwesomeIcon.User, "Add current character"))
{
_serverConfigurationManager.AddCurrentCharacterToServer(idx);
}
ImGui.SameLine();
}
if (UiShared.IconTextButton(FontAwesomeIcon.Plus, "Add new character"))
{
_serverConfigurationManager.AddEmptyCharacterToServer(idx);
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Secret Key Management"))
{
foreach (var item in selectedServer.SecretKeys.ToList())
{
UiShared.DrawWithID("key" + item.Key, () =>
{
var friendlyName = item.Value.FriendlyName;
if (ImGui.InputText("Secret Key Display Name", ref friendlyName, 255))
{
item.Value.FriendlyName = friendlyName;
_serverConfigurationManager.Save();
}
var key = item.Value.Key;
if (ImGui.InputText("Secret Key", ref key, 64))
{
item.Value.Key = key;
_serverConfigurationManager.Save();
}
if (UiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Secret Key"))
{
if (UiShared.CtrlPressed())
{
selectedServer.SecretKeys.Remove(item.Key);
_serverConfigurationManager.Save();
}
}
UiShared.AttachToolTip("Hold CTRL to delete this secret key entry");
});
if (item.Key != selectedServer.SecretKeys.Keys.LastOrDefault())
ImGui.Separator();
}
ImGui.Separator();
if (UiShared.IconTextButton(FontAwesomeIcon.Plus, "Add new Secret Key"))
{
selectedServer.SecretKeys.Add(selectedServer.SecretKeys.Last().Key + 1, new SecretKey()
{
FriendlyName = "New Secret Key",
});
_serverConfigurationManager.Save();
}
ImGui.EndTabItem();
}
if (ImGui.BeginTabItem("Service Settings"))
{
var serverUri = selectedServer.ServerUri;
ImGui.InputText("Service URI", ref serverUri, 255, ImGuiInputTextFlags.ReadOnly);
UiShared.DrawHelpText("You cannot edit the service URI. Add a new service if you need to edit the URI.");
var serverName = selectedServer.ServerName;
var isMain = string.Equals(serverName, ApiController.MainServer, StringComparison.OrdinalIgnoreCase);
var flags = isMain ? ImGuiInputTextFlags.ReadOnly : ImGuiInputTextFlags.None;
if (ImGui.InputText("Service Name", ref serverName, 255, flags))
{
selectedServer.ServerName = serverName;
_serverConfigurationManager.Save();
}
if (isMain)
{
UiShared.DrawHelpText("You cannot edit the name of the main service.");
}
if (!isMain)
{
if (UiShared.IconTextButton(FontAwesomeIcon.Trash, "Delete Service"))
{
if (UiShared.CtrlPressed())
{
_serverConfigurationManager.DeleteServer(selectedServer);
}
}
UiShared.DrawHelpText("Hold CTRL to delete this service");
}
ImGui.EndTabItem();
}
ImGui.EndTabBar();
}
}
private void DrawGeneral()
{
if (!string.Equals(_lastTab, "General", StringComparison.OrdinalIgnoreCase))
{
_notesSuccessfullyApplied = null;
}
_lastTab = "General";
UiShared.FontText("Notes", _uiShared.UidFont);
if (UiShared.IconTextButton(FontAwesomeIcon.StickyNote, "Export all your user notes to clipboard"))
{
ImGui.SetClipboardText(UiShared.GetNotes(_pairManager.DirectPairs.UnionBy(_pairManager.GroupPairs.SelectMany(p => p.Value), p => p.UserData, UserDataComparer.Instance).ToList()));
}
if (UiShared.IconTextButton(FontAwesomeIcon.FileImport, "Import notes from clipboard"))
{
_notesSuccessfullyApplied = null;
var notes = ImGui.GetClipboardText();
_notesSuccessfullyApplied = _uiShared.ApplyNotesFromClipboard(notes, _overwriteExistingLabels);
}
ImGui.SameLine();
ImGui.Checkbox("Overwrite existing notes", ref _overwriteExistingLabels);
UiShared.DrawHelpText("If this option is selected all already existing notes for UIDs will be overwritten by the imported notes.");
if (_notesSuccessfullyApplied.HasValue && _notesSuccessfullyApplied.Value)
{
UiShared.ColorTextWrapped("User Notes successfully imported", ImGuiColors.HealerGreen);
}
else if (_notesSuccessfullyApplied.HasValue && !_notesSuccessfullyApplied.Value)
{
UiShared.ColorTextWrapped("Attempt to import notes from clipboard failed. Check formatting and try again", ImGuiColors.DalamudRed);
}
var openPopupOnAddition = _configService.Current.OpenPopupOnAdd;
var hideInfoMessages = _configService.Current.HideInfoMessages;
var disableOptionalPluginWarnings = _configService.Current.DisableOptionalPluginWarnings;
var onlineNotifs = _configService.Current.ShowOnlineNotifications;
var onlineNotifsPairsOnly = _configService.Current.ShowOnlineNotificationsOnlyForIndividualPairs;
if (ImGui.Checkbox("Open Notes Popup on user addition", ref openPopupOnAddition))
{
ApiController.LastAddedUser = null;
_configService.Current.OpenPopupOnAdd = openPopupOnAddition;
_configService.Save();
}
UiShared.DrawHelpText("This will open a popup that allows you to set the notes for a user after successfully adding them to your individual pairs.");
ImGui.Separator();
UiShared.FontText("Server Messages", _uiShared.UidFont);
if (ImGui.Checkbox("Hide Server Info Messages", ref hideInfoMessages))
{
_configService.Current.HideInfoMessages = hideInfoMessages;
_configService.Save();
}
UiShared.DrawHelpText("Enabling this will not print any \"Info\" labeled messages into the game chat.");
if (ImGui.Checkbox("Disable optional plugin warnings", ref disableOptionalPluginWarnings))
{
_configService.Current.DisableOptionalPluginWarnings = disableOptionalPluginWarnings;
_configService.Save();
}
UiShared.DrawHelpText("Enabling this will not print any \"Warning\" labeled messages for missing optional plugins Heels or Customize+ in the game chat.");
if (ImGui.Checkbox("Enable online notifications", ref onlineNotifs))
{
_configService.Current.ShowOnlineNotifications = onlineNotifs;
_configService.Save();
}
UiShared.DrawHelpText("Enabling this will show a small notification in the bottom right corner when pairs go online.");
if (!onlineNotifs) ImGui.BeginDisabled();
if (ImGui.Checkbox("Notify only for individual pairs", ref onlineNotifsPairsOnly))
{
_configService.Current.ShowOnlineNotificationsOnlyForIndividualPairs = onlineNotifsPairsOnly;
_configService.Save();
}
UiShared.DrawHelpText("Enabling this will only show online notifications for individual pairs.");
if (!onlineNotifs) ImGui.EndDisabled();
}
private bool _deleteFilesPopupModalShown = false;
private bool _deleteAccountPopupModalShown = false;
private void DrawDebug()
{
_lastTab = "Debug";
UiShared.FontText("Debug", _uiShared.UidFont);
@@ -590,9 +512,6 @@ public class SettingsUi : Window, IDisposable
UiShared.AttachToolTip("Use this when reporting mods being rejected from the server.");
}
private string _charaFileSavePath = string.Empty;
private string _charaFileLoadPath = string.Empty;
private void DrawBlockedTransfers()
{
_lastTab = "BlockedTransfers";
@@ -609,7 +528,7 @@ public class SettingsUi : Window, IDisposable
ImGui.TableHeadersRow();
foreach (var item in _apiController.ForbiddenTransfers)
foreach (var item in ApiController.ForbiddenTransfers)
{
ImGui.TableNextColumn();
if (item is UploadFileTransfer transfer)
@@ -630,14 +549,14 @@ public class SettingsUi : Window, IDisposable
private void DrawCurrentTransfers()
{
_lastTab = "Transfers";
bool showTransferWindow = _configuration.ShowTransferWindow;
bool showTransferWindow = _configService.Current.ShowTransferWindow;
if (ImGui.Checkbox("Show separate Transfer window while transfers are active", ref showTransferWindow))
{
_configuration.ShowTransferWindow = showTransferWindow;
_configuration.Save();
_configService.Current.ShowTransferWindow = showTransferWindow;
_configService.Save();
}
if (_configuration.ShowTransferWindow)
if (_configService.Current.ShowTransferWindow)
{
ImGui.Indent();
bool editTransferWindowPosition = _uiShared.EditTrackerPosition;
@@ -651,8 +570,8 @@ public class SettingsUi : Window, IDisposable
if (ImGui.BeginTable("TransfersTable", 2))
{
ImGui.TableSetupColumn(
$"Uploads ({UiShared.ByteToString(_apiController.CurrentUploads.Sum(a => a.Transferred))} / {UiShared.ByteToString(_apiController.CurrentUploads.Sum(a => a.Total))})");
ImGui.TableSetupColumn($"Downloads ({UiShared.ByteToString(_apiController.CurrentDownloads.SelectMany(k => k.Value).ToList().Sum(a => a.Transferred))} / {UiShared.ByteToString(_apiController.CurrentDownloads.SelectMany(k => k.Value).ToList().Sum(a => a.Total))})");
$"Uploads ({UiShared.ByteToString(ApiController.CurrentUploads.Sum(a => a.Transferred))} / {UiShared.ByteToString(ApiController.CurrentUploads.Sum(a => a.Total))})");
ImGui.TableSetupColumn($"Downloads ({UiShared.ByteToString(ApiController.CurrentDownloads.SelectMany(k => k.Value).ToList().Sum(a => a.Transferred))} / {UiShared.ByteToString(ApiController.CurrentDownloads.SelectMany(k => k.Value).ToList().Sum(a => a.Total))})");
ImGui.TableHeadersRow();
@@ -663,7 +582,7 @@ public class SettingsUi : Window, IDisposable
ImGui.TableSetupColumn("Uploaded");
ImGui.TableSetupColumn("Size");
ImGui.TableHeadersRow();
foreach (var transfer in _apiController.CurrentUploads.ToArray())
foreach (var transfer in ApiController.CurrentUploads.ToArray())
{
var color = UiShared.UploadColor((transfer.Transferred, transfer.Total));
ImGui.PushStyleColor(ImGuiCol.Text, color);
@@ -687,7 +606,7 @@ public class SettingsUi : Window, IDisposable
ImGui.TableSetupColumn("Downloaded");
ImGui.TableSetupColumn("Size");
ImGui.TableHeadersRow();
foreach (var transfer in _apiController.CurrentDownloads.SelectMany(k => k.Value).ToArray())
foreach (var transfer in ApiController.CurrentDownloads.SelectMany(k => k.Value).ToArray())
{
var color = UiShared.UploadColor((transfer.Transferred, transfer.Total));
ImGui.PushStyleColor(ImGuiCol.Text, color);
@@ -761,11 +680,11 @@ public class SettingsUi : Window, IDisposable
ImGui.Unindent();
}
bool openInGpose = _configuration.OpenGposeImportOnGposeStart;
bool openInGpose = _configService.Current.OpenGposeImportOnGposeStart;
if (ImGui.Checkbox("Open MCDF import window when GPose loads", ref openInGpose))
{
_configuration.OpenGposeImportOnGposeStart = openInGpose;
_configuration.Save();
_configService.Current.OpenGposeImportOnGposeStart = openInGpose;
_configService.Save();
}
UiShared.DrawHelpText("This will automatically open the import menu when loading into Gpose. If unchecked you can open the menu manually with /mare gpose");
@@ -788,7 +707,7 @@ public class SettingsUi : Window, IDisposable
{
Task.Run(() =>
{
foreach (var file in Directory.GetFiles(_configuration.CacheFolder))
foreach (var file in Directory.GetFiles(_configService.Current.CacheFolder))
{
File.Delete(file);
}

View File

@@ -1,40 +1,44 @@
using System;
using System.IO;
using System.Linq;
using System.Globalization;
using System.Numerics;
using System.Runtime.InteropServices;
using System.Text;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using Dalamud.Interface;
using Dalamud.Interface.Colors;
using Dalamud.Interface.ImGuiFileDialog;
using Dalamud.Plugin;
using Dalamud.Utility;
using ImGuiNET;
using MareSynchronos.Delegates;
using MareSynchronos.FileCache;
using MareSynchronos.Localization;
using MareSynchronos.Managers;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Models;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.UI;
public class UiShared : IDisposable
public partial class UiShared : IDisposable
{
[DllImport("user32")]
public static extern short GetKeyState(int nVirtKey);
[LibraryImport("user32")]
internal static partial short GetKeyState(int nVirtKey);
private readonly IpcManager _ipcManager;
private readonly ApiController _apiController;
private readonly PeriodicFileScanner _cacheScanner;
public readonly FileDialogManager FileDialogManager;
private readonly Configuration _pluginConfiguration;
private readonly ConfigurationService _configService;
private readonly DalamudUtil _dalamudUtil;
private readonly DalamudPluginInterface _pluginInterface;
private readonly Dalamud.Localization _localization;
private readonly ServerConfigurationManager _serverConfigurationManager;
public long FileCacheSize => _cacheScanner.FileCacheSize;
public string PlayerName => _dalamudUtil.PlayerName;
public uint WorldId => _dalamudUtil.WorldId;
public Dictionary<ushort, string> WorldData => _dalamudUtil.WorldData.Value;
public bool HasValidPenumbraModPath => !(_ipcManager.PenumbraModDirectory() ?? string.Empty).IsNullOrEmpty() && Directory.Exists(_ipcManager.PenumbraModDirectory());
public bool EditTrackerPosition { get; set; }
public ImFontPtr UidFont { get; private set; }
@@ -45,38 +49,40 @@ public class UiShared : IDisposable
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 static ImGuiWindowFlags PopupWindowFlags = ImGuiWindowFlags.NoResize |
public static readonly ImGuiWindowFlags PopupWindowFlags = ImGuiWindowFlags.NoResize |
ImGuiWindowFlags.NoScrollbar |
ImGuiWindowFlags.NoScrollWithMouse;
public ApiController ApiController => _apiController;
public UiShared(IpcManager ipcManager, ApiController apiController, PeriodicFileScanner cacheScanner, FileDialogManager fileDialogManager,
Configuration pluginConfiguration, DalamudUtil dalamudUtil, DalamudPluginInterface pluginInterface, Dalamud.Localization localization)
ConfigurationService configService, DalamudUtil dalamudUtil, DalamudPluginInterface pluginInterface, Dalamud.Localization localization,
ServerConfigurationManager serverManager)
{
_ipcManager = ipcManager;
_apiController = apiController;
_cacheScanner = cacheScanner;
FileDialogManager = fileDialogManager;
_pluginConfiguration = pluginConfiguration;
_configService = configService;
_dalamudUtil = dalamudUtil;
_pluginInterface = pluginInterface;
_localization = localization;
_isDirectoryWritable = IsDirectoryWritable(_pluginConfiguration.CacheFolder);
_serverConfigurationManager = serverManager;
_isDirectoryWritable = IsDirectoryWritable(_configService.Current.CacheFolder);
_pluginInterface.UiBuilder.BuildFonts += BuildFont;
_pluginInterface.UiBuilder.RebuildFonts();
_dalamudUtil.GposeStart += _dalamudUtil_GposeStart;
_dalamudUtil.GposeEnd += _dalamudUtil_GposeEnd;
_dalamudUtil.GposeStart += DalamudUtil_GposeStart;
_dalamudUtil.GposeEnd += DalamudUtil_GposeEnd;
}
private void _dalamudUtil_GposeEnd()
private void DalamudUtil_GposeEnd()
{
GposeEnd?.Invoke();
}
private void _dalamudUtil_GposeStart()
private void DalamudUtil_GposeStart()
{
GposeStart?.Invoke();
}
@@ -219,13 +225,13 @@ public class UiShared : IDisposable
? "Collecting files"
: $"Processing {_cacheScanner.CurrentFileProgress} / {_cacheScanner.TotalFiles} files");
}
else if (_pluginConfiguration.FileScanPaused)
else if (_configService.Current.FileScanPaused)
{
ImGui.Text("File scanner is paused");
ImGui.SameLine();
if (ImGui.Button("Force Rescan##forcedrescan"))
{
_cacheScanner.InvokeScan(true);
_cacheScanner.InvokeScan(forced: true);
}
}
else if (_cacheScanner.haltScanLocks.Any(f => f.Value > 0))
@@ -245,18 +251,15 @@ public class UiShared : IDisposable
public void PrintServerState()
{
var serverName = _apiController.ServerDictionary.ContainsKey(_pluginConfiguration.ApiUri)
? _apiController.ServerDictionary[_pluginConfiguration.ApiUri]
: _pluginConfiguration.ApiUri;
if (_apiController.ServerState is ServerState.Connected)
{
ImGui.TextUnformatted("Service " + serverName + ":");
ImGui.TextUnformatted("Service " + _serverConfigurationManager.CurrentServer!.ServerName + ":");
ImGui.SameLine();
ImGui.TextColored(ImGuiColors.ParsedGreen, "Available");
ImGui.SameLine();
ImGui.TextUnformatted("(");
ImGui.SameLine();
ImGui.TextColored(ImGuiColors.ParsedGreen, _apiController.OnlineUsers.ToString());
ImGui.TextColored(ImGuiColors.ParsedGreen, _apiController.OnlineUsers.ToString(CultureInfo.InvariantCulture));
ImGui.SameLine();
ImGui.Text("Users Online");
ImGui.SameLine();
@@ -345,17 +348,23 @@ public class UiShared : IDisposable
return $"{dblSByte:0.00} {suffix[i]}";
}
private int _serverSelectionIndex = 0;
private int _serverSelectionIndex = -1;
private string _customServerName = "";
private string _customServerUri = "";
private bool _enterSecretKey = false;
private bool _cacheDirectoryHasOtherFilesThanCache = false;
private bool _cacheDirectoryIsValidPath = true;
public void DrawServiceSelection(Action? callBackOnExit = null)
public int DrawServiceSelection(bool selectOnChange = false)
{
string[] comboEntries = _apiController.ServerDictionary.Values.ToArray();
_serverSelectionIndex = Array.IndexOf(_apiController.ServerDictionary.Keys.ToArray(), _pluginConfiguration.ApiUri);
string[] comboEntries = _serverConfigurationManager.GetServerNames();
if (_serverSelectionIndex == -1)
_serverSelectionIndex = Array.IndexOf(_serverConfigurationManager.GetServerApiUrls(), _serverConfigurationManager.CurrentApiUrl);
for (int i = 0; i < comboEntries.Length; i++)
{
if (string.Equals(_serverConfigurationManager.CurrentServer?.ServerName, comboEntries[i], StringComparison.OrdinalIgnoreCase))
comboEntries[i] += " [Current]";
}
if (ImGui.BeginCombo("Select Service", comboEntries[_serverSelectionIndex]))
{
for (int i = 0; i < comboEntries.Length; i++)
@@ -363,9 +372,11 @@ public class UiShared : IDisposable
bool isSelected = _serverSelectionIndex == i;
if (ImGui.Selectable(comboEntries[i], isSelected))
{
_pluginConfiguration.ApiUri = _apiController.ServerDictionary.Single(k => string.Equals(k.Value, comboEntries[i], StringComparison.Ordinal)).Key;
_pluginConfiguration.Save();
_ = _apiController.CreateConnections();
_serverSelectionIndex = i;
if (selectOnChange)
{
_serverConfigurationManager.SelectServer(i);
}
}
if (isSelected)
@@ -377,113 +388,61 @@ public class UiShared : IDisposable
ImGui.EndCombo();
}
if (_serverSelectionIndex != 0)
if (_serverConfigurationManager.GetSecretKey(_serverSelectionIndex) != null)
{
ImGui.SameLine();
ImGui.PushFont(UiBuilder.IconFont);
if (ImGui.Button(FontAwesomeIcon.Trash.ToIconString() + "##deleteService"))
var text = "Connect";
if (_serverSelectionIndex == _serverConfigurationManager.GetCurrentServerIndex()) text = "Reconnect";
if (IconTextButton(FontAwesomeIcon.Link, text))
{
_pluginConfiguration.CustomServerList.Remove(_pluginConfiguration.ApiUri);
_pluginConfiguration.ApiUri = _apiController.ServerDictionary.First().Key;
_pluginConfiguration.Save();
_serverConfigurationManager.SelectServer(_serverSelectionIndex);
_ = _apiController.CreateConnections();
}
ImGui.PopFont();
}
if (ImGui.TreeNode("Add Custom Service"))
{
ImGui.SetNextItemWidth(250);
ImGui.InputText("Custom Service Name", ref _customServerName, 255);
ImGui.InputText("Custom Service URI", ref _customServerUri, 255);
ImGui.SetNextItemWidth(250);
ImGui.InputText("Custom Service Address", ref _customServerUri, 255);
if (ImGui.Button("Add Custom Service"))
ImGui.InputText("Custom Service Name", ref _customServerName, 255);
if (UiShared.IconTextButton(FontAwesomeIcon.Plus, "Add Custom Service"))
{
if (!string.IsNullOrEmpty(_customServerUri)
&& !string.IsNullOrEmpty(_customServerName)
&& !_pluginConfiguration.CustomServerList.ContainsValue(_customServerName)
&& !_pluginConfiguration.CustomServerList.ContainsKey(_customServerUri))
&& !string.IsNullOrEmpty(_customServerName))
{
_pluginConfiguration.CustomServerList[_customServerUri] = _customServerName;
_customServerUri = string.Empty;
_serverConfigurationManager.AddServer(new ServerStorage()
{
ServerName = _customServerName,
ServerUri = _customServerUri,
});
_customServerName = string.Empty;
_pluginConfiguration.Save();
_customServerUri = string.Empty;
_configService.Save();
}
}
ImGui.TreePop();
}
PrintServerState();
if (!_apiController.ServerAlive && (_pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri) && !_pluginConfiguration.ClientSecret[_pluginConfiguration.ApiUri].IsNullOrEmpty()))
{
ColorTextWrapped("You already have an account on this server.", ImGuiColors.DalamudYellow);
ImGui.SameLine();
if (ImGui.Button("Connect##connectToService"))
{
_pluginConfiguration.FullPause = false;
_pluginConfiguration.Save();
Task.Run(() => _apiController.CreateConnections(true));
}
return _serverSelectionIndex;
}
string checkboxText = _pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri)
? "I want to switch accounts"
: "I have an account";
ImGui.Checkbox(checkboxText, ref _enterSecretKey);
if (_enterSecretKey)
{
if (_pluginConfiguration.ClientSecret.ContainsKey(_pluginConfiguration.ApiUri))
{
ColorTextWrapped("A secret key was previously set for this service. Entering a new secret key will overwrite the one set prior.", ImGuiColors.DalamudYellow);
}
var text = "Enter Secret Key";
var buttonText = "Save";
var buttonWidth = _secretKey.Length != 64 ? 0 : ImGuiHelpers.GetButtonSize(buttonText).X + ImGui.GetStyle().ItemSpacing.X;
var textSize = ImGui.CalcTextSize(text);
ImGui.AlignTextToFramePadding();
ImGui.Text(text);
ImGui.SameLine();
ImGui.SetNextItemWidth(GetWindowContentRegionWidth() - ImGui.GetWindowContentRegionMin().X - buttonWidth - textSize.X);
ImGui.InputText("", ref _secretKey, 64);
if (_secretKey.Length > 0 && _secretKey.Length != 64)
{
ColorTextWrapped("Your secret key must be exactly 64 characters long. Don't enter your Lodestone auth here.", ImGuiColors.DalamudRed);
}
else if (_secretKey.Length == 64)
{
ImGui.SameLine();
if (ImGui.Button(buttonText))
{
_pluginConfiguration.ClientSecret[_pluginConfiguration.ApiUri] = _secretKey;
_pluginConfiguration.Save();
_secretKey = string.Empty;
Task.Run(() => _apiController.CreateConnections(true));
_enterSecretKey = false;
callBackOnExit?.Invoke();
}
}
}
}
private string _secretKey = "";
public static void OutlineTextWrapped(string text, Vector4 textcolor, Vector4 outlineColor, float dist = 3)
{
var cursorPos = ImGui.GetCursorPos();
UiShared.ColorTextWrapped(text, outlineColor);
ColorTextWrapped(text, outlineColor);
ImGui.SetCursorPos(new(cursorPos.X, cursorPos.Y + dist));
UiShared.ColorTextWrapped(text, outlineColor);
ColorTextWrapped(text, outlineColor);
ImGui.SetCursorPos(new(cursorPos.X + dist, cursorPos.Y));
UiShared.ColorTextWrapped(text, outlineColor);
ColorTextWrapped(text, outlineColor);
ImGui.SetCursorPos(new(cursorPos.X + dist, cursorPos.Y + dist));
UiShared.ColorTextWrapped(text, outlineColor);
ColorTextWrapped(text, outlineColor);
ImGui.SetCursorPos(new(cursorPos.X + dist / 2, cursorPos.Y + dist / 2));
UiShared.ColorTextWrapped(text, textcolor);
ColorTextWrapped(text, textcolor);
ImGui.SetCursorPos(new(cursorPos.X + dist / 2, cursorPos.Y + dist / 2));
UiShared.ColorTextWrapped(text, textcolor);
ColorTextWrapped(text, textcolor);
}
public static void DrawHelpText(string helpText)
@@ -507,7 +466,7 @@ public class UiShared : IDisposable
public void DrawCacheDirectorySetting()
{
ColorTextWrapped("Note: The storage folder should be somewhere close to root (i.e. C:\\MareStorage) in a new empty folder. DO NOT point this to your game folder. DO NOT point this to your Penumbra folder.", ImGuiColors.DalamudYellow);
var cacheDirectory = _pluginConfiguration.CacheFolder;
var cacheDirectory = _configService.Current.CacheFolder;
ImGui.InputText("Storage Folder##cache", ref cacheDirectory, 255, ImGuiInputTextFlags.ReadOnly);
ImGui.SameLine();
@@ -531,8 +490,8 @@ public class UiShared : IDisposable
&& !_cacheDirectoryHasOtherFilesThanCache
&& _cacheDirectoryIsValidPath)
{
_pluginConfiguration.CacheFolder = path;
_pluginConfiguration.Save();
_configService.Current.CacheFolder = path;
_configService.Save();
_cacheScanner.StartScan();
}
});
@@ -557,11 +516,11 @@ public class UiShared : IDisposable
"Restrict yourself to latin letters (A-Z), underscores (_), dashes (-) and arabic numbers (0-9).", ImGuiColors.DalamudRed);
}
float maxCacheSize = (float)_pluginConfiguration.MaxLocalCacheInGiB;
float maxCacheSize = (float)_configService.Current.MaxLocalCacheInGiB;
if (ImGui.SliderFloat("Maximum Storage Size in GiB", ref maxCacheSize, 1f, 200f, "%.2f GiB"))
{
_pluginConfiguration.MaxLocalCacheInGiB = maxCacheSize;
_pluginConfiguration.Save();
_configService.Current.MaxLocalCacheInGiB = maxCacheSize;
_configService.Save();
}
DrawHelpText("The storage is automatically governed by Mare. It will clear itself automatically once it reaches the set capacity by removing the oldest unused files. You typically do not need to clear it yourself.");
}
@@ -569,19 +528,17 @@ public class UiShared : IDisposable
private bool _isDirectoryWritable = false;
private bool _isPenumbraDirectory = false;
public bool IsDirectoryWritable(string dirPath, bool throwIfFails = false)
public static bool IsDirectoryWritable(string dirPath, bool throwIfFails = false)
{
try
{
using (FileStream fs = File.Create(
using FileStream fs = File.Create(
Path.Combine(
dirPath,
Path.GetRandomFileName()
),
1,
FileOptions.DeleteOnClose)
)
{ }
FileOptions.DeleteOnClose);
return true;
}
catch
@@ -595,23 +552,23 @@ public class UiShared : IDisposable
public void RecalculateFileCacheSize()
{
_cacheScanner.InvokeScan(true);
_cacheScanner.InvokeScan(forced: true);
}
public void DrawTimeSpanBetweenScansSetting()
{
var timeSpan = _pluginConfiguration.TimeSpanBetweenScansInSeconds;
var timeSpan = _configService.Current.TimeSpanBetweenScansInSeconds;
if (ImGui.SliderInt("Seconds between scans##timespan", ref timeSpan, 20, 60))
{
_pluginConfiguration.TimeSpanBetweenScansInSeconds = timeSpan;
_pluginConfiguration.Save();
_configService.Current.TimeSpanBetweenScansInSeconds = timeSpan;
_configService.Save();
}
DrawHelpText("This is the time in seconds between file scans. Increase it to reduce system load. A too high setting can cause issues when manually fumbling about in the cache or Penumbra mods folders.");
var isPaused = _pluginConfiguration.FileScanPaused;
var isPaused = _configService.Current.FileScanPaused;
if (ImGui.Checkbox("Pause periodic file scan##filescanpause", ref isPaused))
{
_pluginConfiguration.FileScanPaused = isPaused;
_pluginConfiguration.Save();
_configService.Current.FileScanPaused = isPaused;
_configService.Save();
}
DrawHelpText("This allows you to stop the periodic scans of your Penumbra and Mare cache directories. Use this to move the Mare cache and Penumbra mod folders around. If you enable this permanently, run a Force rescan after adding mods to Penumbra.");
}
@@ -653,24 +610,21 @@ public class UiShared : IDisposable
return buttonClicked;
}
private const string NotesStart = "##MARE_SYNCHRONOS_USER_NOTES_START##";
private const string NotesEnd = "##MARE_SYNCHRONOS_USER_NOTES_END##";
private const string _notesStart = "##MARE_SYNCHRONOS_USER_NOTES_START##";
private const string _notesEnd = "##MARE_SYNCHRONOS_USER_NOTES_END##";
public string GetNotes(string? gid = null)
public static string GetNotes(List<Pair> pairs)
{
var comments = _pluginConfiguration.GetCurrentServerUidComments();
StringBuilder sb = new();
sb.AppendLine(NotesStart);
foreach (var userEntry in comments.Where(c => !string.IsNullOrEmpty(c.Key)))
sb.AppendLine(_notesStart);
foreach (var entry in pairs)
{
if (gid != null)
{
if (!ApiController.GroupPairedClients.Any(p => string.Equals(p.GroupGID, gid, StringComparison.Ordinal) && string.Equals(p.UserUID, userEntry.Key, StringComparison.Ordinal))) continue;
}
var note = entry.GetNote();
if (note.IsNullOrEmpty()) continue;
sb.AppendLine(userEntry.Key + ":\"" + userEntry.Value + "\"");
sb.AppendLine(entry.UserData.UID + ":\"" + entry.GetNote() + "\"");
}
sb.AppendLine(NotesEnd);
sb.AppendLine(_notesEnd);
return sb.ToString();
}
@@ -680,14 +634,14 @@ public class UiShared : IDisposable
var splitNotes = notes.Split(Environment.NewLine, StringSplitOptions.RemoveEmptyEntries).ToList();
var splitNotesStart = splitNotes.FirstOrDefault();
var splitNotesEnd = splitNotes.LastOrDefault();
if (!string.Equals(splitNotesStart, NotesStart) || !string.Equals(splitNotesEnd, NotesEnd))
if (!string.Equals(splitNotesStart, _notesStart) || !string.Equals(splitNotesEnd, _notesEnd))
{
return false;
}
splitNotes.RemoveAll(n => string.Equals(n, NotesStart) || string.Equals(n, NotesEnd));
splitNotes.RemoveAll(n => string.Equals(n, _notesStart) || string.Equals(n, _notesEnd));
var comments = _pluginConfiguration.GetCurrentServerUidComments();
var comments = _serverConfigurationManager.CurrentServer!.UidServerComments;
foreach (var note in splitNotes)
{
@@ -697,7 +651,7 @@ public class UiShared : IDisposable
var uid = splittedEntry[0];
var comment = splittedEntry[1].Trim('"');
if (comments.ContainsKey(uid) && !overwrite) continue;
_pluginConfiguration.SetCurrentServerUidComment(uid, comment);
_serverConfigurationManager.CurrentServer.UidServerComments[uid] = comment;
}
catch
{
@@ -705,7 +659,7 @@ public class UiShared : IDisposable
}
}
_pluginConfiguration.Save();
_serverConfigurationManager.Save();
return true;
}
@@ -713,7 +667,7 @@ public class UiShared : IDisposable
public void Dispose()
{
_pluginInterface.UiBuilder.BuildFonts -= BuildFont;
_dalamudUtil.GposeStart -= _dalamudUtil_GposeStart;
_dalamudUtil.GposeEnd -= _dalamudUtil_GposeEnd;
_dalamudUtil.GposeStart -= DalamudUtil_GposeStart;
_dalamudUtil.GposeEnd -= DalamudUtil_GposeEnd;
}
}

View File

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

View File

@@ -1,5 +1,4 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Concurrent;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Utils;

View File

@@ -1,40 +1,34 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Game;
using Dalamud.Game;
using Dalamud.Game.ClientState;
using Dalamud.Game.ClientState.Conditions;
using Dalamud.Game.ClientState.Objects;
using Dalamud.Game.ClientState.Objects.SubKinds;
using Dalamud.Game.ClientState.Objects.Types;
using Dalamud.Game.Gui;
using Dalamud.Game.Text.SeStringHandling;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using FFXIVClientStructs.FFXIV.Client.Game.Control;
using Lumina.Excel.GeneratedSheets;
using MareSynchronos.Delegates;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
namespace MareSynchronos.Utils;
public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor);
public delegate void VoidDelegate();
public class DalamudUtil : IDisposable
{
private readonly ClientState _clientState;
private readonly ObjectTable _objectTable;
private readonly Framework _framework;
private readonly Condition _condition;
private readonly Dalamud.Game.ClientState.Conditions.Condition _condition;
private readonly ChatGui _chatGui;
private readonly Dalamud.Data.DataManager _gameData;
public event VoidDelegate? LogIn;
public event VoidDelegate? LogOut;
public event VoidDelegate? FrameworkUpdate;
public event VoidDelegate? ClassJobChanged;
private uint? classJobId = 0;
private uint? _classJobId = 0;
public event VoidDelegate? DelayedFrameworkUpdate;
public event VoidDelegate? ZoneSwitchStart;
public event VoidDelegate? ZoneSwitchEnd;
@@ -58,21 +52,31 @@ public class DalamudUtil : IDisposable
return false;
}
public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework, Condition condition, ChatGui chatGui)
public DalamudUtil(ClientState clientState, ObjectTable objectTable, Framework framework, Dalamud.Game.ClientState.Conditions.Condition condition, ChatGui chatGui,
Dalamud.Data.DataManager gameData)
{
_clientState = clientState;
_objectTable = objectTable;
_framework = framework;
_condition = condition;
_chatGui = chatGui;
_gameData = gameData;
_framework.Update += FrameworkOnUpdate;
if (IsLoggedIn)
{
classJobId = _clientState.LocalPlayer!.ClassJob.Id;
ClientStateOnLogin(null, EventArgs.Empty);
_classJobId = _clientState.LocalPlayer!.ClassJob.Id;
ClientStateOnLogin(sender: null, EventArgs.Empty);
}
WorldData = new(() =>
{
return gameData.GetExcelSheet<World>(Dalamud.ClientLanguage.English)!
.Where(w => w.IsPublic && !w.Name.RawData.IsEmpty)
.ToDictionary(w => (ushort)w.RowId, w => w.Name.ToString());
});
}
public Lazy<Dictionary<ushort, string>> WorldData { get; private set; }
public void PrintInfoChat(string message)
{
SeStringBuilder se = new SeStringBuilder().AddText("[Mare Synchronos] Info: ").AddItalics(message);
@@ -119,7 +123,8 @@ public class DalamudUtil : IDisposable
return;
}
else if (_sentBetweenAreas)
if (_sentBetweenAreas)
{
Logger.Debug("Zone switch/Gpose end");
_sentBetweenAreas = false;
@@ -160,9 +165,9 @@ public class DalamudUtil : IDisposable
{
var newclassJobId = _clientState.LocalPlayer.ClassJob.Id;
if (classJobId != newclassJobId)
if (_classJobId != newclassJobId)
{
classJobId = newclassJobId;
_classJobId = newclassJobId;
ClassJobChanged?.Invoke();
}
}
@@ -205,7 +210,7 @@ public class DalamudUtil : IDisposable
public bool IsPlayerPresent => _clientState.LocalPlayer != null && _clientState.LocalPlayer.IsValid();
public bool IsObjectPresent(Dalamud.Game.ClientState.Objects.Types.GameObject? obj)
public static bool IsObjectPresent(Dalamud.Game.ClientState.Objects.Types.GameObject? obj)
{
return obj != null && obj.IsValid();
}
@@ -218,18 +223,19 @@ public class DalamudUtil : IDisposable
public unsafe IntPtr GetPet(IntPtr? playerPointer = null)
{
var mgr = CharacterManager.Instance();
if (playerPointer == null) playerPointer = PlayerPointer;
return (IntPtr)mgr->LookupPetByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
playerPointer ??= PlayerPointer;
return (IntPtr)mgr->LookupPetByOwnerObject((BattleChara*)playerPointer);
}
public unsafe IntPtr GetCompanion(IntPtr? playerPointer = null)
{
var mgr = CharacterManager.Instance();
if (playerPointer == null) playerPointer = PlayerPointer;
return (IntPtr)mgr->LookupBuddyByOwnerObject((FFXIVClientStructs.FFXIV.Client.Game.Character.BattleChara*)playerPointer);
playerPointer ??= PlayerPointer;
return (IntPtr)mgr->LookupBuddyByOwnerObject((BattleChara*)playerPointer);
}
public string PlayerName => _clientState.LocalPlayer?.Name.ToString() ?? "--";
public uint WorldId => _clientState.LocalPlayer!.HomeWorld.Id;
public IntPtr PlayerPointer => _clientState.LocalPlayer?.Address ?? IntPtr.Zero;
@@ -241,7 +247,7 @@ public class DalamudUtil : IDisposable
{
return _objectTable.Where(obj =>
obj.ObjectKind == Dalamud.Game.ClientState.Objects.Enums.ObjectKind.Player &&
!string.Equals(obj.Name.ToString(), PlayerName, StringComparison.Ordinal)).Select(p => (PlayerCharacter)p).ToList();
!string.Equals(obj.Name.ToString(), PlayerName, StringComparison.Ordinal)).Cast<PlayerCharacter>().ToList();
}
public Dalamud.Game.ClientState.Objects.Types.Character? GetCharacterFromObjectTableByIndex(int index)

View File

@@ -1,26 +1,24 @@
using System;
using System.Diagnostics;
using System.Diagnostics;
using Dalamud.Logging;
using Dalamud.Utility;
using Lumina.Excel.GeneratedSheets;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Utils;
internal class Logger : ILogger
{
private readonly string name;
private readonly string _name;
public static void Info(string info)
public static void Info(string? info)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
PluginLog.Information($"[{caller}] {info}");
}
public static void Debug(string debug, string stringToHighlight = "")
public static void Debug(string? debug, string stringToHighlight = "")
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
if (debug.Contains(stringToHighlight, StringComparison.Ordinal) && !stringToHighlight.IsNullOrEmpty())
if (debug != null && debug.Contains(stringToHighlight, StringComparison.Ordinal) && !stringToHighlight.IsNullOrEmpty())
{
PluginLog.Warning($"[{caller}] {debug}");
}
@@ -30,31 +28,31 @@ internal class Logger : ILogger
}
}
public static void Error(string msg, Exception ex)
public static void Error(string? msg, Exception ex)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
PluginLog.Error($"[{caller}] {msg} {Environment.NewLine} Exception: {ex.Message} {Environment.NewLine} {ex.StackTrace}");
}
public static void Warn(string msg, Exception ex)
public static void Warn(string? msg, Exception ex)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
PluginLog.Warning($"[{caller}] {msg} {Environment.NewLine} Exception: {ex.Message} {Environment.NewLine} {ex.StackTrace}");
}
public static void Error(string msg)
public static void Error(string? msg)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
PluginLog.Error($"[{caller}] {msg}");
}
public static void Warn(string warn)
public static void Warn(string? warn)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
PluginLog.Warning($"[{caller}] {warn}");
}
public static void Verbose(string verbose)
public static void Verbose(string? verbose)
{
var caller = new StackTrace().GetFrame(1)?.GetMethod()?.ReflectedType?.Name ?? "Unknown";
#if DEBUG
@@ -66,7 +64,7 @@ internal class Logger : ILogger
public Logger(string name)
{
this.name = name;
this._name = name;
}
public void Log<TState>(LogLevel logLevel, EventId eventId, TState state, Exception? exception, Func<TState, Exception?, string> formatter)
@@ -76,22 +74,22 @@ internal class Logger : ILogger
switch (logLevel)
{
case LogLevel.Debug:
PluginLog.Debug($"[{name}] [{eventId}] {formatter(state, exception)}");
PluginLog.Debug($"[{_name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Error:
case LogLevel.Critical:
PluginLog.Error($"[{name}] [{eventId}] {formatter(state, exception)}");
PluginLog.Error($"[{_name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Information:
PluginLog.Information($"[{name}] [{eventId}] {formatter(state, exception)}");
PluginLog.Information($"[{_name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Warning:
PluginLog.Warning($"[{name}] [{eventId}] {formatter(state, exception)}");
PluginLog.Warning($"[{_name}] [{eventId}] {formatter(state, exception)}");
break;
case LogLevel.Trace:
default:
#if DEBUG
PluginLog.Verbose($"[{name}] [{eventId}] {formatter(state, exception)}");
PluginLog.Verbose($"[{_name}] [{eventId}] {formatter(state, exception)}");
#else
PluginLog.Verbose($"[{name}] {eventId} {state} {formatter(state, exception)}");
#endif

View File

@@ -1,10 +1,8 @@
using System;
using System.Collections.Generic;
using System.Globalization;
using System.Linq;
using System.Globalization;
using System.Reflection;
using System.Text;
using System.Threading.Tasks;
using System.Runtime.Serialization.Formatters.Binary;
using System.Runtime.Serialization;
using Newtonsoft.Json;
namespace MareSynchronos.Utils;
@@ -28,4 +26,9 @@ public static class VariousExtensions
return default;
}
public static T DeepClone<T>(this T obj)
{
return JsonConvert.DeserializeObject<T>(JsonConvert.SerializeObject(obj))!;
}
}

View File

@@ -1,19 +1,14 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Collections.Concurrent;
using System.Net;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Net.Http.Json;
using System.Runtime.CompilerServices;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using Dalamud.Utility;
using LZ4;
using MareSynchronos.API;
using MareSynchronos.API.Data;
using MareSynchronos.API.Dto.Files;
using MareSynchronos.API.Routes;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
using Microsoft.AspNetCore.SignalR.Client;
@@ -188,7 +183,7 @@ public partial class ApiController
public int GetDownloadId() => _downloadId++;
public async Task DownloadFiles(int currentDownloadId, List<FileReplacementDto> fileReplacementDto, CancellationToken ct)
public async Task DownloadFiles(int currentDownloadId, List<FileReplacementData> fileReplacementDto, CancellationToken ct)
{
DownloadStarted?.Invoke();
try
@@ -213,7 +208,7 @@ public partial class ApiController
private async Task<HttpResponseMessage> SendRequestInternalAsync(HttpRequestMessage requestMessage, CancellationToken? ct = null)
{
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", this.Authorization);
requestMessage.Headers.Authorization = new AuthenticationHeaderValue("Bearer", this._serverManager.GetToken());
if (requestMessage.Content != null)
{
@@ -224,7 +219,7 @@ public partial class ApiController
Logger.Debug("Sending " + requestMessage.Method + " to " + requestMessage.RequestUri);
}
if(ct != null)
if (ct != null)
return await _httpClient.SendAsync(requestMessage, ct.Value).ConfigureAwait(false);
return await _httpClient.SendAsync(requestMessage).ConfigureAwait(false);
}
@@ -236,12 +231,12 @@ public partial class ApiController
return await SendRequestInternalAsync(requestMessage, ct).ConfigureAwait(false);
}
private async Task DownloadFilesInternal(int currentDownloadId, List<FileReplacementDto> fileReplacementDto, CancellationToken ct)
private async Task DownloadFilesInternal(int currentDownloadId, List<FileReplacementData> fileReplacement, CancellationToken ct)
{
Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")");
List<DownloadFileDto> downloadFileInfoFromService = new();
downloadFileInfoFromService.AddRange(await FilesGetSizes(fileReplacementDto.Select(f => f.Hash).ToList()).ConfigureAwait(false));
downloadFileInfoFromService.AddRange(await FilesGetSizes(fileReplacement.Select(f => f.Hash).ToList()).ConfigureAwait(false));
Logger.Debug("Files with size 0 or less: " + string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
@@ -261,7 +256,7 @@ public partial class ApiController
await Parallel.ForEachAsync(downloadGroups, new ParallelOptions()
{
MaxDegreeOfParallelism = downloadGroups.Count(),
CancellationToken = ct
CancellationToken = ct,
},
async (fileGroup, token) =>
{
@@ -277,7 +272,7 @@ public partial class ApiController
file.Transferred += bytesDownloaded;
});
var tempPath = Path.Combine(_pluginConfiguration.CacheFolder, file.Hash + ".tmp");
var tempPath = Path.Combine(_configService.Current.CacheFolder, file.Hash + ".tmp");
try
{
await DownloadFileHttpClient(file, tempPath, progress, token).ConfigureAwait(false);
@@ -298,7 +293,7 @@ public partial class ApiController
var tempFileData = await File.ReadAllBytesAsync(tempPath, token).ConfigureAwait(false);
var extratokenedFile = LZ4Codec.Unwrap(tempFileData);
File.Delete(tempPath);
var filePath = Path.Combine(_pluginConfiguration.CacheFolder, file.Hash);
var filePath = Path.Combine(_configService.Current.CacheFolder, file.Hash);
await File.WriteAllBytesAsync(filePath, extratokenedFile, token).ConfigureAwait(false);
var fi = new FileInfo(filePath);
Func<DateTime> RandomDayInThePast()
@@ -329,10 +324,10 @@ public partial class ApiController
CancelDownload(currentDownloadId);
}
public async Task PushCharacterData(CharacterCacheDto character, List<string> visibleCharacterIds)
public async Task PushCharacterData(API.Data.CharacterData character, List<UserData> visibleCharacters)
{
if (!IsConnected || string.Equals(SecretKey, "-", StringComparison.Ordinal)) return;
Logger.Debug("Sending Character data to service " + ApiUri);
if (!IsConnected) return;
Logger.Debug("Sending Character data to service " + _serverManager.CurrentApiUrl);
CancelUpload();
_uploadCancellationTokenSource = new CancellationTokenSource();
@@ -367,7 +362,7 @@ public partial class ApiController
{
CurrentUploads.Add(new UploadFileTransfer(file)
{
Total = new FileInfo(_fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath).Length
Total = new FileInfo(_fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath).Length,
});
}
catch (Exception ex)
@@ -383,7 +378,7 @@ public partial class ApiController
{
ForbiddenTransfers.Add(new UploadFileTransfer(file)
{
LocalFile = _fileDbManager.GetFileCacheByHash(file.Hash)?.ResolvedFilepath ?? string.Empty
LocalFile = _fileDbManager.GetFileCacheByHash(file.Hash)?.ResolvedFilepath ?? string.Empty,
});
}
}
@@ -431,7 +426,7 @@ public partial class ApiController
if (!uploadToken.IsCancellationRequested)
{
Logger.Info("Pushing character data for " + character.GetHashCode() + " to " + string.Join(", ", visibleCharacterIds));
Logger.Info("Pushing character data for " + character.GetHashCode() + " to " + string.Join(", ", visibleCharacters.Select(c => c.AliasOrUID)));
StringBuilder sb = new();
foreach (var item in character.FileReplacements)
{
@@ -442,14 +437,14 @@ public partial class ApiController
sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}");
}
Logger.Debug("Chara data contained: " + Environment.NewLine + sb.ToString());
await UserPushData(character, visibleCharacterIds).ConfigureAwait(false);
await UserPushData(new(visibleCharacters, character)).ConfigureAwait(false);
}
else
{
Logger.Warn("=== Upload operation was cancelled ===");
}
Logger.Verbose("Upload complete for " + character.GetHashCode());
Logger.Verbose("Upload complete for " + character.DataHash);
_uploadCancellationTokenSource = null;
}

View File

@@ -1,7 +1,4 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.API.Dto.User;
using MareSynchronos.Utils;
using Microsoft.AspNetCore.SignalR.Client;
@@ -11,18 +8,17 @@ public partial class ApiController
{
public async Task UserDelete()
{
_pluginConfiguration.ClientSecret.Remove(ApiUri);
_pluginConfiguration.Save();
CheckConnection();
await FilesDeleteAll().ConfigureAwait(false);
await _mareHub!.SendAsync(nameof(UserDelete)).ConfigureAwait(false);
await CreateConnections().ConfigureAwait(false);
}
public async Task UserPushData(CharacterCacheDto characterCache, List<string> visibleCharacterIds)
public async Task UserPushData(UserCharaDataMessageDto dto)
{
try
{
await _mareHub!.InvokeAsync(nameof(UserPushData), characterCache, visibleCharacterIds).ConfigureAwait(false);
await _mareHub!.InvokeAsync(nameof(UserPushData), dto).ConfigureAwait(false);
}
catch (Exception ex)
{
@@ -30,33 +26,31 @@ public partial class ApiController
}
}
public async Task<List<ClientPairDto>> UserGetPairedClients()
public async Task<List<UserPairDto>> UserGetPairedClients()
{
return await _mareHub!.InvokeAsync<List<ClientPairDto>>(nameof(UserGetPairedClients)).ConfigureAwait(false);
return await _mareHub!.InvokeAsync<List<UserPairDto>>(nameof(UserGetPairedClients)).ConfigureAwait(false);
}
public async Task<List<string>> UserGetOnlineCharacters()
public async Task<List<OnlineUserIdentDto>> UserGetOnlinePairs()
{
return await _mareHub!.InvokeAsync<List<string>>(nameof(UserGetOnlineCharacters)).ConfigureAwait(false);
return await _mareHub!.InvokeAsync<List<OnlineUserIdentDto>>(nameof(UserGetOnlinePairs)).ConfigureAwait(false);
}
public async Task UserAddPair(string uid)
public async Task UserSetPairPermissions(UserPermissionsDto dto)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(UserAddPair), uid.Trim()).ConfigureAwait(false);
Logger.Verbose("Sending UserSetPairPermissions: " + dto);
await _mareHub!.SendAsync(nameof(UserSetPairPermissions), dto).ConfigureAwait(false);
}
public async Task UserChangePairPauseStatus(string uid, bool paused)
public async Task UserAddPair(UserDto dto)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(UserChangePairPauseStatus), uid, paused).ConfigureAwait(false);
if (!IsConnected) return;
await _mareHub!.SendAsync(nameof(UserAddPair), dto).ConfigureAwait(false);
}
public async Task UserRemovePair(string uid)
public async Task UserRemovePair(UserDto dto)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(UserRemovePair), uid).ConfigureAwait(false);
if (!IsConnected) return;
await _mareHub!.SendAsync(nameof(UserRemovePair), dto).ConfigureAwait(false);
}
}

View File

@@ -1,57 +0,0 @@
using System;
using System.Collections.Generic;
using System.Threading.Tasks;
using MareSynchronos.API;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public async Task AdminUpdateOrAddForbiddenFile(ForbiddenFileDto forbiddenFile)
{
await _mareHub!.SendAsync(nameof(AdminUpdateOrAddForbiddenFile), forbiddenFile).ConfigureAwait(false);
}
public async Task AdminDeleteForbiddenFile(ForbiddenFileDto forbiddenFile)
{
await _mareHub!.SendAsync(nameof(AdminDeleteForbiddenFile), forbiddenFile).ConfigureAwait(false);
}
public async Task AdminUpdateOrAddBannedUser(BannedUserDto bannedUser)
{
await _mareHub!.SendAsync(nameof(AdminUpdateOrAddBannedUser), bannedUser).ConfigureAwait(false);
}
public async Task AdminDeleteBannedUser(BannedUserDto bannedUser)
{
await _mareHub!.SendAsync(nameof(AdminDeleteBannedUser), bannedUser).ConfigureAwait(false);
}
public async Task RefreshOnlineUsers()
{
AdminOnlineUsers = await AdminGetOnlineUsers().ConfigureAwait(false);
}
public async Task<List<OnlineUserDto>> AdminGetOnlineUsers()
{
return await _mareHub!.InvokeAsync<List<OnlineUserDto>>(nameof(AdminGetOnlineUsers)).ConfigureAwait(false);
}
public List<OnlineUserDto> AdminOnlineUsers { get; set; } = new List<OnlineUserDto>();
public async Task AdminChangeModeratorStatus(string onlineUserUID, bool isModerator)
{
await _mareHub!.SendAsync(nameof(AdminChangeModeratorStatus), onlineUserUID, isModerator).ConfigureAwait(false);
}
public async Task<List<ForbiddenFileDto>> AdminGetForbiddenFiles()
{
return await _mareHub!.InvokeAsync<List<ForbiddenFileDto>>(nameof(AdminGetForbiddenFiles)).ConfigureAwait(false);
}
public async Task<List<BannedUserDto>> AdminGetBannedUsers()
{
return await _mareHub!.InvokeAsync<List<BannedUserDto>>(nameof(AdminGetBannedUsers)).ConfigureAwait(false);
}
}

View File

@@ -1,21 +1,25 @@
using System;
using System.Linq;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.API.Data.Enum;
using MareSynchronos.API.Dto;
using MareSynchronos.API.Dto.Group;
using MareSynchronos.API.Dto.User;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public ClientPairDto? LastAddedUser { get; set; }
public void OnUserUpdateClientPairs(Action<ClientPairDto> act)
public UserPairDto? LastAddedUser { get; set; }
private void ExecuteSafely(Action act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserUpdateClientPairs), act);
try
{
act();
}
catch (Exception ex)
{
Logger.Error("Error on executing safely", ex);
}
}
public void OnUpdateSystemInfo(Action<SystemInfoDto> act)
@@ -24,60 +28,12 @@ public partial class ApiController
_mareHub!.On(nameof(Client_UpdateSystemInfo), act);
}
public void OnUserReceiveCharacterData(Action<CharacterCacheDto, string> act)
public void OnUserReceiveCharacterData(Action<OnlineUserCharaDataDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserReceiveCharacterData), act);
}
public void OnUserChangePairedPlayer(Action<string, bool> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserChangePairedPlayer), act);
}
public void OnGroupChange(Action<GroupDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupChange), act);
}
public void OnGroupUserChange(Action<GroupPairDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupUserChange), act);
}
public void OnAdminForcedReconnect(Action act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_AdminForcedReconnect), act);
}
public void OnAdminDeleteBannedUser(Action<BannedUserDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_AdminDeleteBannedUser), act);
}
public void OnAdminDeleteForbiddenFile(Action<ForbiddenFileDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_AdminDeleteForbiddenFile), act);
}
public void OnAdminUpdateOrAddBannedUser(Action<BannedUserDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_AdminUpdateOrAddBannedUser), act);
}
public void OnAdminUpdateOrAddForbiddenFile(Action<ForbiddenFileDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_AdminUpdateOrAddForbiddenFile), act);
}
public void OnReceiveServerMessage(Action<MessageSeverity, string> act)
{
if (_initialized) return;
@@ -90,25 +46,200 @@ public partial class ApiController
_mareHub!.On(nameof(Client_DownloadReady), act);
}
public Task Client_UserUpdateClientPairs(ClientPairDto dto)
public void OnGroupSendFullInfo(Action<GroupFullInfoDto> act)
{
var entry = PairedClients.SingleOrDefault(e => string.Equals(e.OtherUID, dto.OtherUID, System.StringComparison.Ordinal));
if (dto.IsRemoved)
{
PairedClients.RemoveAll(p => string.Equals(p.OtherUID, dto.OtherUID, System.StringComparison.Ordinal));
return Task.CompletedTask;
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupSendFullInfo), act);
}
if (entry == null)
public Task Client_GroupSendFullInfo(GroupFullInfoDto dto)
{
LastAddedUser = dto;
PairedClients.Add(dto);
Logger.Verbose("Client_GroupSendFullInfo: " + dto);
ExecuteSafely(() => _pairManager.AddGroup(dto));
return Task.CompletedTask;
}
entry.IsPaused = dto.IsPaused;
entry.IsPausedFromOthers = dto.IsPausedFromOthers;
entry.IsSynced = dto.IsSynced;
public void OnGroupSendInfo(Action<GroupInfoDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupSendInfo), act);
}
public Task Client_GroupSendInfo(GroupInfoDto dto)
{
Logger.Verbose("Client_GroupSendInfo: " + dto);
ExecuteSafely(() => _pairManager.SetGroupInfo(dto));
return Task.CompletedTask;
}
public void OnGroupDelete(Action<GroupDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupDelete), act);
}
public Task Client_GroupDelete(GroupDto dto)
{
Logger.Verbose("Client_GroupDelete: " + dto);
ExecuteSafely(() => _pairManager.RemoveGroup(dto.Group));
return Task.CompletedTask;
}
public void OnGroupPairJoined(Action<GroupPairFullInfoDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupPairJoined), act);
}
public Task Client_GroupPairJoined(GroupPairFullInfoDto dto)
{
Logger.Verbose("Client_GroupPairJoined: " + dto);
ExecuteSafely(() => _pairManager.AddGroupPair(dto));
return Task.CompletedTask;
}
public void OnGroupPairLeft(Action<GroupPairDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupPairLeft), act);
}
public Task Client_GroupPairLeft(GroupPairDto dto)
{
Logger.Verbose("Client_GroupPairLeft: " + dto);
ExecuteSafely(() => _pairManager.RemoveGroupPair(dto));
return Task.CompletedTask;
}
public void OnGroupChangePermissions(Action<GroupPermissionDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupChangePermissions), act);
}
public Task Client_GroupChangePermissions(GroupPermissionDto dto)
{
Logger.Verbose("Client_GroupChangePermissions: " + dto);
ExecuteSafely(() => _pairManager.SetGroupPermissions(dto));
return Task.CompletedTask;
}
public void OnGroupPairChangePermissions(Action<GroupPairUserPermissionDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupPairChangePermissions), act);
}
public Task Client_GroupPairChangePermissions(GroupPairUserPermissionDto dto)
{
Logger.Verbose("Client_GroupPairChangePermissions: " + dto);
ExecuteSafely(() =>
{
if (string.Equals(dto.UID, UID, StringComparison.Ordinal)) _pairManager.SetGroupUserPermissions(dto);
else _pairManager.SetGroupPairUserPermissions(dto);
});
return Task.CompletedTask;
}
public void OnGroupPairChangeUserInfo(Action<GroupPairUserInfoDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_GroupPairChangeUserInfo), act);
}
public Task Client_GroupPairChangeUserInfo(GroupPairUserInfoDto dto)
{
Logger.Verbose("Client_GroupPairChangeUserInfo: " + dto);
ExecuteSafely(() =>
{
if (string.Equals(dto.UID, UID, StringComparison.Ordinal)) _pairManager.SetGroupStatusInfo(dto);
else _pairManager.SetGroupPairStatusInfo(dto);
});
return Task.CompletedTask;
}
public Task Client_UserReceiveCharacterData(OnlineUserCharaDataDto dto)
{
Logger.Verbose("Client_UserReceiveCharacterData: " + dto.User);
ExecuteSafely(() => _pairManager.ReceiveCharaData(dto));
return Task.CompletedTask;
}
public void OnUserAddClientPair(Action<UserPairDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserAddClientPair), act);
}
public Task Client_UserAddClientPair(UserPairDto dto)
{
Logger.Debug($"Client_UserAddClientPair: " + dto);
ExecuteSafely(() => _pairManager.AddUserPair(dto));
return Task.CompletedTask;
}
public void OnUserRemoveClientPair(Action<UserDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserRemoveClientPair), act);
}
public Task Client_UserRemoveClientPair(UserDto dto)
{
Logger.Debug($"Client_UserRemoveClientPair: " + dto);
ExecuteSafely(() => _pairManager.RemoveUserPair(dto));
return Task.CompletedTask;
}
public void OnUserSendOffline(Action<UserDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserSendOffline), act);
}
public Task Client_UserSendOffline(UserDto dto)
{
Logger.Debug($"Client_UserSendOffline: {dto}");
ExecuteSafely(() => _pairManager.MarkPairOffline(dto.User));
return Task.CompletedTask;
}
public void OnUserSendOnline(Action<OnlineUserIdentDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserSendOnline), act);
}
public Task Client_UserSendOnline(OnlineUserIdentDto dto)
{
Logger.Debug($"Client_UserSendOnline: {dto}");
ExecuteSafely(() => _pairManager.MarkPairOnline(dto, this));
return Task.CompletedTask;
}
public void OnUserUpdateOtherPairPermissions(Action<UserPermissionsDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserUpdateOtherPairPermissions), act);
}
public Task Client_UserUpdateOtherPairPermissions(UserPermissionsDto dto)
{
Logger.Debug($"Client_UserUpdateOtherPairPermissions: {dto}");
ExecuteSafely(() => _pairManager.UpdatePairPermissions(dto));
return Task.CompletedTask;
}
public void OnUserUpdateSelfPairPermissions(Action<UserPermissionsDto> act)
{
if (_initialized) return;
_mareHub!.On(nameof(Client_UserUpdateSelfPairPermissions), act);
}
public Task Client_UserUpdateSelfPairPermissions(UserPermissionsDto dto)
{
Logger.Debug($"Client_UserUpdateSelfPairPermissions: {dto}");
ExecuteSafely(() => _pairManager.UpdateSelfPairPermissions(dto));
return Task.CompletedTask;
}
@@ -118,114 +249,6 @@ public partial class ApiController
return Task.CompletedTask;
}
public Task Client_UserReceiveCharacterData(CharacterCacheDto clientPairDto, string characterIdent)
{
Logger.Verbose("Received DTO for " + characterIdent);
CharacterReceived?.Invoke(null, new CharacterReceivedEventArgs(characterIdent, clientPairDto));
return Task.CompletedTask;
}
public Task Client_UserChangePairedPlayer(string characterIdent, bool isOnline)
{
if (isOnline) PairedClientOnline?.Invoke(characterIdent);
else PairedClientOffline?.Invoke(characterIdent);
return Task.CompletedTask;
}
public async Task Client_GroupChange(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 GroupsGetUsersInGroup(dto.GID).ConfigureAwait(false));
return;
}
existingGroup.OwnedBy = dto.OwnedBy ?? existingGroup.OwnedBy;
existingGroup.InvitesEnabled = dto.InvitesEnabled ?? existingGroup.InvitesEnabled;
existingGroup.IsPaused = dto.IsPaused ?? existingGroup.IsPaused;
existingGroup.IsModerator = dto.IsModerator ?? existingGroup.IsModerator;
}
public Task Client_GroupUserChange(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 Task.CompletedTask;
}
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 Task.CompletedTask;
}
existingUser.IsPaused = dto.IsPaused ?? existingUser.IsPaused;
existingUser.UserAlias = dto.UserAlias ?? existingUser.UserAlias;
existingUser.IsPinned = dto.IsPinned ?? existingUser.IsPinned;
existingUser.IsModerator = dto.IsModerator ?? existingUser.IsModerator;
return Task.CompletedTask;
}
public Task Client_AdminForcedReconnect()
{
_ = CreateConnections();
return Task.CompletedTask;
}
public Task Client_AdminDeleteBannedUser(BannedUserDto dto)
{
AdminBannedUsers.RemoveAll(a => string.Equals(a.CharacterHash, dto.CharacterHash, System.StringComparison.Ordinal));
return Task.CompletedTask;
}
public Task Client_AdminDeleteForbiddenFile(ForbiddenFileDto dto)
{
AdminForbiddenFiles.RemoveAll(f => string.Equals(f.Hash, dto.Hash, System.StringComparison.Ordinal));
return Task.CompletedTask;
}
public Task Client_AdminUpdateOrAddBannedUser(BannedUserDto dto)
{
var user = AdminBannedUsers.SingleOrDefault(b => string.Equals(b.CharacterHash, dto.CharacterHash, System.StringComparison.Ordinal));
if (user == null)
{
AdminBannedUsers.Add(dto);
}
else
{
user.Reason = dto.Reason;
}
return Task.CompletedTask;
}
public Task Client_AdminUpdateOrAddForbiddenFile(ForbiddenFileDto dto)
{
var user = AdminForbiddenFiles.SingleOrDefault(b => string.Equals(b.Hash, dto.Hash, System.StringComparison.Ordinal));
if (user == null)
{
AdminForbiddenFiles.Add(dto);
}
else
{
user.ForbiddenBy = dto.ForbiddenBy;
}
return Task.CompletedTask;
}
public Task Client_ReceiveServerMessage(MessageSeverity severity, string message)
{
switch (severity)
@@ -240,7 +263,7 @@ public partial class ApiController
break;
case MessageSeverity.Information:
Logger.Info(message);
if (!_pluginConfiguration.HideInfoMessages)
if (_configService.Current.HideInfoMessages)
{
_dalamudUtil.PrintInfoChat(message);
}

View File

@@ -1,116 +1,115 @@
using MareSynchronos.API;
using MareSynchronos.API.Dto.Group;
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> GroupCreate()
private void CheckConnection()
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return new GroupCreatedDto();
return await _mareHub!.InvokeAsync<GroupCreatedDto>(nameof(GroupCreate)).ConfigureAwait(false);
if (ServerState is not (ServerState.Connected or ServerState.Connecting or ServerState.Reconnecting)) throw new System.Exception("Not connected");
}
public async Task<bool> GroupChangePassword(string gid, string newpassword)
public async Task<List<BannedGroupUserDto>> GroupGetBannedUsers(GroupDto group)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return false;
return await _mareHub!.InvokeAsync<bool>(nameof(GroupChangePassword), gid, newpassword).ConfigureAwait(false);
CheckConnection();
return await _mareHub!.InvokeAsync<List<BannedGroupUserDto>>(nameof(GroupGetBannedUsers), group).ConfigureAwait(false);
}
public async Task<List<GroupDto>> GroupsGetAll()
public async Task GroupClear(GroupDto group)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return new List<GroupDto>();
return await _mareHub!.InvokeAsync<List<GroupDto>>(nameof(GroupsGetAll)).ConfigureAwait(false);
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupClear), group).ConfigureAwait(false);
}
public async Task<List<GroupPairDto>> GroupsGetUsersInGroup(string gid)
public async Task GroupChangeOwnership(GroupPairDto groupPair)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return new List<GroupPairDto>();
return await _mareHub!.InvokeAsync<List<GroupPairDto>>(nameof(GroupsGetUsersInGroup), gid).ConfigureAwait(false);
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupChangeOwnership), groupPair).ConfigureAwait(false);
}
public async Task<bool> GroupJoin(string gid, string password)
public async Task<bool> GroupChangePassword(GroupPasswordDto groupPassword)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return false;
return await _mareHub!.InvokeAsync<bool>(nameof(GroupJoin), gid.Trim(), password).ConfigureAwait(false);
CheckConnection();
return await _mareHub!.InvokeAsync<bool>(nameof(GroupChangePassword), groupPassword).ConfigureAwait(false);
}
public async Task GroupChangeInviteState(string gid, bool opened)
public async Task<GroupPasswordDto> GroupCreate()
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(GroupChangeInviteState), gid, opened).ConfigureAwait(false);
CheckConnection();
return await _mareHub!.InvokeAsync<GroupPasswordDto>(nameof(GroupCreate)).ConfigureAwait(false);
}
public async Task GroupDelete(string gid)
public async Task<List<GroupFullInfoDto>> GroupsGetAll()
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(GroupDelete), gid).ConfigureAwait(false);
CheckConnection();
return await _mareHub!.InvokeAsync<List<GroupFullInfoDto>>(nameof(GroupsGetAll)).ConfigureAwait(false);
}
public async Task GroupChangePinned(string gid, string uid, bool isPinned)
public async Task<List<GroupPairFullInfoDto>> GroupsGetUsersInGroup(GroupDto group)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(GroupChangePinned), gid, uid, isPinned).ConfigureAwait(false);
CheckConnection();
return await _mareHub!.InvokeAsync<List<GroupPairFullInfoDto>>(nameof(GroupsGetUsersInGroup), group).ConfigureAwait(false);
}
public async Task GroupClear(string gid)
public async Task GroupBanUser(GroupPairDto dto, string reason)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(GroupClear), gid).ConfigureAwait(false);
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupBanUser), dto, reason).ConfigureAwait(false);
}
public async Task GroupLeave(string gid)
public async Task GroupChangeGroupPermissionState(GroupPermissionDto dto)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(GroupLeave), gid).ConfigureAwait(false);
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupChangeGroupPermissionState), dto).ConfigureAwait(false);
}
public async Task GroupChangePauseState(string gid, bool isPaused)
public async Task GroupChangeIndividualPermissionState(GroupPairUserPermissionDto dto)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(GroupChangePauseState), gid, isPaused).ConfigureAwait(false);
CheckConnection();
Logger.Debug("Sending " + dto);
await _mareHub!.SendAsync(nameof(GroupChangeIndividualPermissionState), dto).ConfigureAwait(false);
}
public async Task GroupRemoveUser(string gid, string uid)
public async Task GroupDelete(GroupDto group)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(GroupRemoveUser), gid, uid).ConfigureAwait(false);
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupDelete), group).ConfigureAwait(false);
}
public async Task GroupChangeOwnership(string gid, string uid)
public async Task<bool> GroupJoin(GroupPasswordDto passwordedGroup)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(GroupChangeOwnership), gid, uid).ConfigureAwait(false);
CheckConnection();
return await _mareHub!.InvokeAsync<bool>(nameof(GroupJoin), passwordedGroup).ConfigureAwait(false);
}
public async Task GroupBanUser(string gid, string uid, string reason)
public async Task GroupLeave(GroupDto group)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(GroupBanUser), gid, uid, reason).ConfigureAwait(false);
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupLeave), group).ConfigureAwait(false);
}
public async Task GroupUnbanUser(string gid, string uid)
public async Task GroupRemoveUser(GroupPairDto groupPair)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(GroupUnbanUser), gid, uid).ConfigureAwait(false);
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupRemoveUser), groupPair).ConfigureAwait(false);
}
public async Task<List<BannedGroupUserDto>> GroupGetBannedUsers(string gid)
public async Task GroupUnbanUser(GroupPairDto groupPair)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return new();
return await _mareHub!.InvokeAsync<List<BannedGroupUserDto>>(nameof(GroupGetBannedUsers), gid).ConfigureAwait(false);
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupUnbanUser), groupPair).ConfigureAwait(false);
}
public async Task GroupSetModerator(string gid, string uid, bool isModerator)
public async Task GroupSetUserInfo(GroupPairUserInfoDto userInfo)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return;
await _mareHub!.SendAsync(nameof(GroupSetModerator), gid, uid, isModerator).ConfigureAwait(false);
CheckConnection();
await _mareHub!.SendAsync(nameof(GroupSetUserInfo), userInfo).ConfigureAwait(false);
}
public async Task<List<string>> GroupCreateTempInvite(string gid, int amount)
public async Task<List<string>> GroupCreateTempInvite(GroupDto group, int amount)
{
if (!IsConnected || string.Equals(SecretKey, "-", System.StringComparison.Ordinal)) return new();
return await _mareHub!.InvokeAsync<List<string>>(nameof(GroupCreateTempInvite), gid, amount).ConfigureAwait(false);
CheckConnection();
return await _mareHub!.InvokeAsync<List<string>>(nameof(GroupCreateTempInvite), group, amount).ConfigureAwait(false);
}
}

View File

@@ -1,26 +1,19 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using MareSynchronos.API;
using System.Collections.Concurrent;
using MareSynchronos.API.Routes;
using MareSynchronos.FileCache;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
using MareSynchronos.API.Dto;
using MareSynchronos.API.SignalR;
using MareSynchronos.Managers;
using Dalamud.Utility;
using MareSynchronos.MareConfiguration;
using MareSynchronos.Delegates;
namespace MareSynchronos.WebAPI;
public delegate void SimpleStringDelegate(string str);
public record JwtCache(string ApiUrl, string CharaIdent, string SecretKey);
public partial class ApiController : IDisposable, IMareHubClient
{
public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)";
@@ -28,20 +21,19 @@ public partial class ApiController : IDisposable, IMareHubClient
public readonly int[] SupportedServerVersions = { IMareHub.ApiVersion };
private readonly Configuration _pluginConfiguration;
private readonly ConfigurationService _configService;
private readonly DalamudUtil _dalamudUtil;
private readonly FileCacheManager _fileDbManager;
private readonly PairManager _pairManager;
private readonly ServerConfigurationManager _serverManager;
private CancellationTokenSource _connectionCancellationTokenSource;
private Dictionary<JwtCache, string> _jwtToken = new();
private string Authorization => _jwtToken.GetValueOrDefault(new JwtCache(ApiUri, _dalamudUtil.PlayerNameHashed, SecretKey), string.Empty);
private HubConnection? _mareHub;
private CancellationTokenSource? _uploadCancellationTokenSource = new();
private CancellationTokenSource? _healthCheckTokenSource = new();
private ConnectionDto? _connectionDto;
public ServerInfoDto ServerInfo => _connectionDto?.ServerInfo ?? new ServerInfoDto();
public ServerInfo ServerInfo => _connectionDto?.ServerInfo ?? new ServerInfo();
public string AuthFailureMessage { get; private set; } = string.Empty;
public SystemInfoDto SystemInfoDto { get; private set; } = new();
@@ -51,18 +43,21 @@ public partial class ApiController : IDisposable, IMareHubClient
private HttpClient _httpClient;
public ApiController(Configuration pluginConfiguration, DalamudUtil dalamudUtil, FileCacheManager fileDbManager)
public ApiController(ConfigurationService configService, DalamudUtil dalamudUtil, FileCacheManager fileDbManager, PairManager pairManager, ServerConfigurationManager serverManager)
{
Logger.Verbose("Creating " + nameof(ApiController));
_pluginConfiguration = pluginConfiguration;
_configService = configService;
_dalamudUtil = dalamudUtil;
_fileDbManager = fileDbManager;
_pairManager = pairManager;
_serverManager = serverManager;
_connectionCancellationTokenSource = new CancellationTokenSource();
_dalamudUtil.LogIn += DalamudUtilOnLogIn;
_dalamudUtil.LogOut += DalamudUtilOnLogOut;
ServerState = ServerState.Offline;
_verifiedUploadedHashes = new(StringComparer.Ordinal);
_httpClient = new();
if (_dalamudUtil.IsLoggedIn)
{
@@ -72,25 +67,17 @@ public partial class ApiController : IDisposable, IMareHubClient
private void DalamudUtilOnLogOut()
{
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token).ConfigureAwait(false));
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token, ServerState.Disconnected).ConfigureAwait(false));
ServerState = ServerState.Offline;
}
private void DalamudUtilOnLogIn()
{
Task.Run(() => CreateConnections(true));
Task.Run(() => CreateConnections(forceGetToken: true));
}
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;
@@ -100,33 +87,14 @@ public partial class ApiController : IDisposable, IMareHubClient
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 IsDownloading => !CurrentDownloads.IsEmpty;
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 string UID => _connectionDto?.User.UID ?? string.Empty;
public string DisplayName => _connectionDto?.User.AliasOrUID ?? string.Empty;
public int OnlineUsers => SystemInfoDto.OnlineUsers;
private ServerState _serverState;
@@ -149,16 +117,24 @@ public partial class ApiController : IDisposable, IMareHubClient
_httpClient?.Dispose();
_httpClient = new();
if (_pluginConfiguration.FullPause)
if (_serverManager.CurrentServer?.FullPause ?? true)
{
Logger.Info("Not recreating Connection, paused");
ServerState = ServerState.Disconnected;
_connectionDto = null;
await StopConnection(_connectionCancellationTokenSource.Token).ConfigureAwait(false);
await StopConnection(_connectionCancellationTokenSource.Token, ServerState.Disconnected).ConfigureAwait(false);
return;
}
await StopConnection(_connectionCancellationTokenSource.Token).ConfigureAwait(false);
var secretKey = _serverManager.GetSecretKey();
if (secretKey.IsNullOrEmpty())
{
Logger.Warn("No secret key set for current character");
_connectionDto = null;
await StopConnection(_connectionCancellationTokenSource.Token, ServerState.NoSecretKey).ConfigureAwait(false);
return;
}
await StopConnection(_connectionCancellationTokenSource.Token, ServerState.Disconnected).ConfigureAwait(false);
Logger.Info("Recreating Connection");
@@ -168,37 +144,31 @@ public partial class ApiController : IDisposable, IMareHubClient
_verifiedUploadedHashes.Clear();
while (ServerState is not ServerState.Connected && !token.IsCancellationRequested)
{
if (string.IsNullOrEmpty(SecretKey))
{
await Task.Delay(TimeSpan.FromSeconds(2)).ConfigureAwait(false);
continue;
}
AuthFailureMessage = string.Empty;
await StopConnection(token).ConfigureAwait(false);
await StopConnection(token, ServerState.Disconnected).ConfigureAwait(false);
ServerState = ServerState.Connecting;
try
{
Logger.Debug("Building connection");
if (!_jwtToken.TryGetValue(new JwtCache(ApiUri, _dalamudUtil.PlayerNameHashed, SecretKey), out var jwtToken) || forceGetToken)
if (_serverManager.GetToken() == null || forceGetToken)
{
Logger.Debug("Requesting new JWT");
using HttpClient httpClient = new();
var postUri = MareAuth.AuthFullPath(new Uri(ApiUri
var postUri = MareAuth.AuthFullPath(new Uri(_serverManager.CurrentApiUrl
.Replace("wss://", "https://", StringComparison.OrdinalIgnoreCase)
.Replace("ws://", "http://", StringComparison.OrdinalIgnoreCase)));
using var sha256 = SHA256.Create();
var auth = BitConverter.ToString(sha256.ComputeHash(Encoding.UTF8.GetBytes(SecretKey))).Replace("-", "", StringComparison.OrdinalIgnoreCase);
var auth = secretKey.GetHash256();
var result = await httpClient.PostAsync(postUri, new FormUrlEncodedContent(new[]
{
new KeyValuePair<string, string>("auth", auth),
new KeyValuePair<string, string>("charaIdent", _dalamudUtil.PlayerNameHashed)
new KeyValuePair<string, string>("charaIdent", _dalamudUtil.PlayerNameHashed),
})).ConfigureAwait(false);
AuthFailureMessage = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
result.EnsureSuccessStatusCode();
_jwtToken[new JwtCache(ApiUri, _dalamudUtil.PlayerNameHashed, SecretKey)] = await result.Content.ReadAsStringAsync().ConfigureAwait(false);
_serverManager.SaveToken(await result.Content.ReadAsStringAsync().ConfigureAwait(false));
Logger.Debug("JWT Success");
}
@@ -214,8 +184,7 @@ public partial class ApiController : IDisposable, IMareHubClient
await _mareHub.StartAsync(token).ConfigureAwait(false);
OnReceiveServerMessage((sev, msg) => Client_ReceiveServerMessage(sev, msg));
OnUpdateSystemInfo((dto) => Client_UpdateSystemInfo(dto));
await InitializeData().ConfigureAwait(false);
_connectionDto = await GetConnectionDto().ConfigureAwait(false);
@@ -223,15 +192,12 @@ public partial class ApiController : IDisposable, IMareHubClient
if (_connectionDto.ServerVersion != IMareHub.ApiVersion)
{
ServerState = ServerState.VersionMisMatch;
await StopConnection(token).ConfigureAwait(false);
await StopConnection(token, ServerState.VersionMisMatch).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;
@@ -245,13 +211,12 @@ public partial class ApiController : IDisposable, IMareHubClient
if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
ServerState = ServerState.Unauthorized;
await StopConnection(token).ConfigureAwait(false);
await StopConnection(token, ServerState.Unauthorized).ConfigureAwait(false);
return;
}
else
{
ServerState = ServerState.Offline;
ServerState = ServerState.Reconnecting;
Logger.Info("Failed to establish connection, retrying");
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token).ConfigureAwait(false);
}
@@ -267,12 +232,6 @@ public partial class ApiController : IDisposable, IMareHubClient
}
}
private Task MareHubOnReconnected(string? arg)
{
_ = Task.Run(() => CreateConnections(false));
return Task.CompletedTask;
}
private async Task ClientHealthCheck(CancellationToken ct)
{
while (!ct.IsCancellationRequested && _mareHub != null)
@@ -289,36 +248,55 @@ public partial class ApiController : IDisposable, IMareHubClient
}
}
private async Task InitializeData(CancellationToken token)
private async Task InitializeData()
{
if (_mareHub == null) return;
Logger.Debug("Initializing data");
OnUserUpdateClientPairs((dto) => Client_UserUpdateClientPairs(dto));
OnUserChangePairedPlayer((ident, online) => Client_UserChangePairedPlayer(ident, online));
OnUserReceiveCharacterData((dto, ident) => Client_UserReceiveCharacterData(dto, ident));
OnGroupChange(async (dto) => await Client_GroupChange(dto).ConfigureAwait(false));
OnGroupUserChange((dto) => Client_GroupUserChange(dto));
OnDownloadReady((guid) => Client_DownloadReady(guid));
OnReceiveServerMessage((sev, msg) => Client_ReceiveServerMessage(sev, msg));
OnUpdateSystemInfo((dto) => Client_UpdateSystemInfo(dto));
OnAdminForcedReconnect(() => Client_AdminForcedReconnect());
OnUserSendOffline((dto) => Client_UserSendOffline(dto));
OnUserAddClientPair((dto) => Client_UserAddClientPair(dto));
OnUserReceiveCharacterData((dto) => Client_UserReceiveCharacterData(dto));
OnUserRemoveClientPair(dto => Client_UserRemoveClientPair(dto));
OnUserSendOnline(dto => Client_UserSendOnline(dto));
OnUserUpdateOtherPairPermissions(dto => Client_UserUpdateOtherPairPermissions(dto));
OnUserUpdateSelfPairPermissions(dto => Client_UserUpdateSelfPairPermissions(dto));
PairedClients = await UserGetPairedClients().ConfigureAwait(false);
Groups = await GroupsGetAll().ConfigureAwait(false);
GroupPairedClients.Clear();
foreach (var group in Groups)
OnGroupChangePermissions((dto) => Client_GroupChangePermissions(dto));
OnGroupDelete((dto) => Client_GroupDelete(dto));
OnGroupPairChangePermissions((dto) => Client_GroupPairChangePermissions(dto));
OnGroupPairChangeUserInfo((dto) => Client_GroupPairChangeUserInfo(dto));
OnGroupPairJoined((dto) => Client_GroupPairJoined(dto));
OnGroupPairLeft((dto) => Client_GroupPairLeft(dto));
OnGroupSendFullInfo((dto) => Client_GroupSendFullInfo(dto));
OnGroupSendInfo((dto) => Client_GroupSendInfo(dto));
foreach (var userPair in await UserGetPairedClients().ConfigureAwait(false))
{
GroupPairedClients.AddRange(await GroupsGetUsersInGroup(group.GID).ConfigureAwait(false));
Logger.Debug($"Pair: {userPair}");
_pairManager.AddUserPair(userPair);
}
foreach (var entry in await GroupsGetAll().ConfigureAwait(false))
{
Logger.Debug($"Group: {entry}");
_pairManager.AddGroup(entry);
}
foreach (var group in _pairManager.GroupPairs.Keys)
{
var users = await GroupsGetUsersInGroup(group).ConfigureAwait(false);
foreach (var user in users)
{
Logger.Debug($"GroupPair: {user}");
_pairManager.AddGroupPair(user);
}
}
if (IsModerator)
foreach (var entry in await UserGetOnlinePairs().ConfigureAwait(false))
{
AdminForbiddenFiles = await AdminGetForbiddenFiles().ConfigureAwait(false);
AdminBannedUsers = await AdminGetBannedUsers().ConfigureAwait(false);
OnAdminUpdateOrAddBannedUser((dto) => Client_AdminUpdateOrAddBannedUser(dto));
OnAdminDeleteBannedUser((dto) => Client_AdminDeleteBannedUser(dto));
OnAdminUpdateOrAddForbiddenFile(dto => Client_AdminUpdateOrAddForbiddenFile(dto));
OnAdminDeleteForbiddenFile(dto => Client_AdminDeleteForbiddenFile(dto));
_pairManager.MarkPairOnline(entry, this);
}
_healthCheckTokenSource?.Cancel();
@@ -338,7 +316,7 @@ public partial class ApiController : IDisposable, IMareHubClient
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
ServerState = ServerState.Offline;
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token).ConfigureAwait(false));
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token, ServerState.Disconnected).ConfigureAwait(false));
_connectionCancellationTokenSource?.Cancel();
_healthCheckTokenSource?.Cancel();
_uploadCancellationTokenSource?.Cancel();
@@ -347,9 +325,9 @@ public partial class ApiController : IDisposable, IMareHubClient
private HubConnection BuildHubConnection(string hubName)
{
return new HubConnectionBuilder()
.WithUrl(ApiUri + hubName, options =>
.WithUrl(_serverManager.CurrentApiUrl + hubName, options =>
{
options.Headers.Add("Authorization", "Bearer " + Authorization);
options.Headers.Add("Authorization", "Bearer " + _serverManager.GetToken());
options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling;
})
.WithAutomaticReconnect(new ForeverRetryPolicy())
@@ -366,7 +344,9 @@ public partial class ApiController : IDisposable, IMareHubClient
CurrentUploads.Clear();
CurrentDownloads.Clear();
_uploadCancellationTokenSource?.Cancel();
_healthCheckTokenSource?.Cancel();
Disconnected?.Invoke();
_pairManager.ClearPairs();
ServerState = ServerState.Offline;
Logger.Info("Connection closed");
return Task.CompletedTask;
@@ -376,16 +356,24 @@ public partial class ApiController : IDisposable, IMareHubClient
{
_connectionDto = null;
_healthCheckTokenSource?.Cancel();
ServerState = ServerState.Disconnected;
ServerState = ServerState.Reconnecting;
Logger.Warn("Connection closed... Reconnecting");
Logger.Warn(arg?.Message ?? string.Empty);
Logger.Warn(arg?.StackTrace ?? string.Empty);
Disconnected?.Invoke();
ServerState = ServerState.Offline;
_pairManager.ClearPairs();
return Task.CompletedTask;
}
private async Task StopConnection(CancellationToken token)
private async Task MareHubOnReconnected(string? arg)
{
ServerState = ServerState.Connecting;
await InitializeData().ConfigureAwait(false);
_connectionDto = await GetConnectionDto().ConfigureAwait(false);
ServerState = ServerState.Connected;
}
private async Task StopConnection(CancellationToken token, ServerState state)
{
if (_mareHub is not null)
{
@@ -401,15 +389,9 @@ public partial class ApiController : IDisposable, IMareHubClient
CurrentUploads.Clear();
CurrentDownloads.Clear();
Disconnected?.Invoke();
_pairManager.ClearPairs();
_mareHub = null;
}
if (ServerState != ServerState.Disconnected)
{
while (ServerState != ServerState.Offline)
{
await Task.Delay(16).ConfigureAwait(false);
}
ServerState = state;
}
}

View File

@@ -3,9 +3,12 @@
public enum ServerState
{
Offline,
Connecting,
Reconnecting,
Disconnected,
Connected,
Unauthorized,
VersionMisMatch,
RateLimited
RateLimited,
NoSecretKey,
}

View File

@@ -1,16 +0,0 @@
using System;
using MareSynchronos.API;
namespace MareSynchronos.WebAPI.Utils;
public class CharacterReceivedEventArgs : EventArgs
{
public CharacterReceivedEventArgs(string characterNameHash, CharacterCacheDto characterData)
{
CharacterData = characterData;
CharacterNameHash = characterNameHash;
}
public CharacterCacheDto CharacterData { get; set; }
public string CharacterNameHash { get; set; }
}

View File

@@ -0,0 +1,17 @@
using MareSynchronos.API.Dto.Files;
namespace MareSynchronos.WebAPI.Utils;
public class DownloadFileTransfer : FileTransfer
{
private DownloadFileDto Dto => (DownloadFileDto)TransferDto;
public DownloadFileTransfer(DownloadFileDto dto) : base(dto) { }
public Uri DownloadUri => new(Dto.Url);
public override long Total
{
set { }
get => Dto.Size;
}
public override bool CanBeTransferred => Dto.FileExists && !Dto.IsForbidden && Dto.Size > 0;
}

View File

@@ -1,5 +1,4 @@
using MareSynchronos.API;
using System;
using MareSynchronos.API.Dto.Files;
namespace MareSynchronos.WebAPI.Utils;
@@ -26,24 +25,3 @@ public abstract class FileTransfer
return Hash;
}
}
public class UploadFileTransfer : FileTransfer
{
public UploadFileTransfer(UploadFileDto dto) : base(dto) { }
public override long Total { get; set; }
public string LocalFile { get; set; } = string.Empty;
}
public class DownloadFileTransfer : FileTransfer
{
private DownloadFileDto Dto => (DownloadFileDto)TransferDto;
public DownloadFileTransfer(DownloadFileDto dto) : base(dto) { }
public Uri DownloadUri => new(Dto.Url);
public override long Total
{
set { }
get => Dto.Size;
}
public override bool CanBeTransferred => Dto.FileExists && !Dto.IsForbidden && Dto.Size > 0;
}

View File

@@ -1,5 +1,4 @@
using System;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI.Utils;

View File

@@ -0,0 +1,10 @@
using MareSynchronos.API.Dto.Files;
namespace MareSynchronos.WebAPI.Utils;
public class UploadFileTransfer : FileTransfer
{
public UploadFileTransfer(UploadFileDto dto) : base(dto) { }
public override long Total { get; set; }
public string LocalFile { get; set; } = string.Empty;
}

Binary file not shown.