add api to mare, change all to file scoped namespace

This commit is contained in:
Stanley Dimant
2022-09-29 15:52:33 +02:00
parent b10a02f228
commit ac6c46390c
27 changed files with 4436 additions and 4373 deletions

Submodule MareAPI updated: 9dc1e901aa...57a7ab8262

View File

@@ -7,10 +7,10 @@ using System.Linq;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos
namespace MareSynchronos;
public static class ConfigurationExtensions
{
public static class ConfigurationExtensions
{
public static bool HasValidSetup(this Configuration configuration)
{
return configuration.AcceptedAgreement && configuration.InitialScanComplete
@@ -35,11 +35,11 @@ namespace MareSynchronos
configuration.UidServerComments[configuration.ApiUri][uid] = comment;
}
}
}
[Serializable]
public class Configuration : IPluginConfiguration
{
[Serializable]
public class Configuration : IPluginConfiguration
{
private string _apiUri = string.Empty;
private int _maxParallelScan = 10;
[NonSerialized]
@@ -197,5 +197,4 @@ namespace MareSynchronos
Save();
}
}
}
}

View File

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

View File

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

View File

@@ -8,13 +8,13 @@ using MareSynchronos.WebAPI;
using Action = System.Action;
using System.Collections.Concurrent;
namespace MareSynchronos.Managers
namespace MareSynchronos.Managers;
public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx);
public delegate void HeelsOffsetChange(float change);
public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath);
public class IpcManager : IDisposable
{
public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx);
public delegate void HeelsOffsetChange(float change);
public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath);
public class IpcManager : IDisposable
{
private readonly ICallGateSubscriber<int> _glamourerApiVersion;
private readonly ICallGateSubscriber<string, GameObject?, object>? _glamourerApplyAll;
private readonly ICallGateSubscriber<GameObject?, string>? _glamourerGetAllCustomization;
@@ -397,5 +397,4 @@ namespace MareSynchronos.Managers
PenumbraDisposed?.Invoke();
actionQueue.Clear();
}
}
}

View File

@@ -14,12 +14,12 @@ using MareSynchronos.FileCache;
using Newtonsoft.Json;
#endif
namespace MareSynchronos.Managers
{
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
namespace MareSynchronos.Managers;
public class PlayerManager : IDisposable
{
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
public class PlayerManager : IDisposable
{
private readonly ApiController _apiController;
private readonly CharacterDataFactory _characterDataFactory;
private readonly DalamudUtil _dalamudUtil;
@@ -267,5 +267,4 @@ namespace MareSynchronos.Managers
}
}, token);
}
}
}

View File

@@ -8,12 +8,12 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MareSynchronos.Managers
{
public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
namespace MareSynchronos.Managers;
public class TransientResourceManager : IDisposable
{
public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
public class TransientResourceManager : IDisposable
{
private readonly IpcManager manager;
private readonly DalamudUtil dalamudUtil;
@@ -201,5 +201,4 @@ namespace MareSynchronos.Managers
SemiTransientResources[objectKind].Add(item);
}
}
}
}

View File

@@ -6,11 +6,11 @@ using MareSynchronos.API;
using MareSynchronos.Utils;
using Lumina.Excel.GeneratedSheets;
namespace MareSynchronos.Models
namespace MareSynchronos.Models;
[JsonObject(MemberSerialization.OptIn)]
public class CharacterData
{
[JsonObject(MemberSerialization.OptIn)]
public class CharacterData
{
[JsonProperty]
public Dictionary<ObjectKind, List<FileReplacement>> FileReplacements { get; set; } = new();
@@ -84,5 +84,4 @@ namespace MareSynchronos.Models
}
return stringBuilder.ToString();
}
}
}

View File

@@ -6,10 +6,10 @@ using MareSynchronos.API;
using System.Text.RegularExpressions;
using MareSynchronos.FileCache;
namespace MareSynchronos.Models
namespace MareSynchronos.Models;
public class FileReplacement
{
public class FileReplacement
{
private readonly FileCacheManager fileDbManager;
public FileReplacement(FileCacheManager fileDbManager)
@@ -56,5 +56,4 @@ namespace MareSynchronos.Models
builder.AppendLine($"Modded: {HasFileReplacement} - {string.Join(",", GamePaths)} => {ResolvedPath}");
return builder.ToString();
}
}
}

View File

@@ -5,10 +5,10 @@ using System.Runtime.InteropServices;
using MareSynchronos.Utils;
using Penumbra.GameData.ByteString;
namespace MareSynchronos.Models
namespace MareSynchronos.Models;
public class PlayerRelatedObject
{
public class PlayerRelatedObject
{
private readonly Func<IntPtr> getAddress;
public unsafe Character* Character => (Character*)Address;
@@ -132,5 +132,4 @@ namespace MareSynchronos.Models
return hasChanges;
}
}
}

View File

@@ -15,10 +15,10 @@ using MareSynchronos.Utils;
using Dalamud.Game.ClientState.Conditions;
using MareSynchronos.FileCache;
namespace MareSynchronos
namespace MareSynchronos;
public sealed class Plugin : IDalamudPlugin
{
public sealed class Plugin : IDalamudPlugin
{
private const string CommandName = "/mare";
private readonly ApiController _apiController;
private readonly CommandManager _commandManager;
@@ -214,5 +214,4 @@ namespace MareSynchronos
else
_introUi.Toggle();
}
}
}

View File

@@ -14,10 +14,10 @@ using MareSynchronos.API;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.UI
namespace MareSynchronos.UI;
public class CompactUi : Window, IDisposable
{
public class CompactUi : Window, IDisposable
{
private readonly ApiController _apiController;
private readonly Configuration _configuration;
private readonly Dictionary<string, bool> _showUidForEntry = new();
@@ -559,5 +559,4 @@ namespace MareSynchronos.UI
_ => string.Empty
};
}
}
}

View File

@@ -12,10 +12,10 @@ using MareSynchronos.Localization;
using Dalamud.Utility;
using MareSynchronos.FileCache;
namespace MareSynchronos.UI
namespace MareSynchronos.UI;
internal class IntroUi : Window, IDisposable
{
internal class IntroUi : Window, IDisposable
{
private readonly UiShared _uiShared;
private readonly Configuration _pluginConfiguration;
private readonly PeriodicFileScanner _fileCacheManager;
@@ -294,5 +294,4 @@ namespace MareSynchronos.UI
var wordIdx = random.Next(splitSentence.Length);
return new($"{Strings.ToS.ParagraphLabel} {paragraphIdx + 1}, {Strings.ToS.SentenceLabel} {sentenceIdx + 1}, {Strings.ToS.WordLabel} {wordIdx + 1}", splitSentence[wordIdx]);
}
}
}

View File

@@ -14,11 +14,11 @@ using MareSynchronos.WebAPI.Utils;
using System.Diagnostics;
using Dalamud.Utility;
namespace MareSynchronos.UI
namespace MareSynchronos.UI;
public delegate void SwitchUi();
public class SettingsUi : Window, IDisposable
{
public delegate void SwitchUi();
public class SettingsUi : Window, IDisposable
{
private readonly Configuration _configuration;
private readonly WindowSystem _windowSystem;
private readonly ApiController _apiController;
@@ -609,5 +609,4 @@ namespace MareSynchronos.UI
_uiShared.EditTrackerPosition = false;
base.OnClose();
}
}
}

View File

@@ -18,10 +18,10 @@ using MareSynchronos.Managers;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.UI
namespace MareSynchronos.UI;
public class UiShared : IDisposable
{
public class UiShared : IDisposable
{
[DllImport("user32")]
public static extern short GetKeyState(int nVirtKey);
@@ -552,5 +552,4 @@ namespace MareSynchronos.UI
{
_pluginInterface.UiBuilder.BuildFonts -= BuildFont;
}
}
}

View File

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

View File

@@ -11,19 +11,19 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
namespace MareSynchronos.Utils
namespace MareSynchronos.Utils;
public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor);
public delegate void LogIn();
public delegate void LogOut();
public delegate void ClassJobChanged();
public delegate void FrameworkUpdate();
public delegate void VoidDelegate();
public class DalamudUtil : IDisposable
{
public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor);
public delegate void LogIn();
public delegate void LogOut();
public delegate void ClassJobChanged();
public delegate void FrameworkUpdate();
public delegate void VoidDelegate();
public class DalamudUtil : IDisposable
{
private readonly ClientState _clientState;
private readonly ObjectTable _objectTable;
private readonly Framework _framework;
@@ -238,5 +238,4 @@ namespace MareSynchronos.Utils
_clientState.Logout -= ClientStateOnLogout;
_framework.Update -= FrameworkOnUpdate;
}
}
}

View File

@@ -5,11 +5,11 @@ using Dalamud.Logging;
using Dalamud.Utility;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Utils
namespace MareSynchronos.Utils;
[ProviderAlias("Dalamud")]
public class DalamudLoggingProvider : ILoggerProvider
{
[ProviderAlias("Dalamud")]
public class DalamudLoggingProvider : ILoggerProvider
{
private readonly ConcurrentDictionary<string, Logger> _loggers =
new(StringComparer.OrdinalIgnoreCase);
@@ -26,10 +26,10 @@ namespace MareSynchronos.Utils
{
_loggers.Clear();
}
}
}
internal class Logger : ILogger
{
internal class Logger : ILogger
{
private readonly string name;
public static void Info(string info)
@@ -108,5 +108,4 @@ namespace MareSynchronos.Utils
}
public IDisposable BeginScope<TState>(TState state) => default!;
}
}

View File

@@ -6,10 +6,10 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace MareSynchronos.Utils
namespace MareSynchronos.Utils;
public static class VariousExtensions
{
public static class VariousExtensions
{
public static DateTime GetLinkerTime(Assembly assembly)
{
const string BuildVersionMetadataPrefix = "+build";
@@ -28,5 +28,4 @@ namespace MareSynchronos.Utils
return default;
}
}
}

View File

@@ -13,10 +13,10 @@ using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public partial class ApiController
{
private readonly HashSet<string> _verifiedUploadedHashes;
private int _downloadId = 0;
@@ -313,6 +313,5 @@ namespace MareSynchronos.WebAPI
CurrentDownloads.TryRemove(downloadId, out _);
}
}
}
}

View File

@@ -1,13 +1,12 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.Utils;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public partial class ApiController
{
public async Task DeleteAccount()
{
_pluginConfiguration.ClientSecret.Remove(ApiUri);
@@ -39,6 +38,6 @@ namespace MareSynchronos.WebAPI
if (!IsConnected || SecretKey == "-") return;
await _mareHub!.SendAsync(Api.SendUserPairedClientRemoval, uid);
}
}
}

View File

@@ -1,372 +0,0 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.FileCache;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.WebAPI
{
public delegate void SimpleStringDelegate(string str);
public enum ServerState
{
Offline,
Disconnected,
Connected,
Unauthorized,
VersionMisMatch,
RateLimited
}
public partial class ApiController : IDisposable
{
public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)";
public const string MainServiceUri = "wss://maresynchronos.com";
public readonly int[] SupportedServerVersions = { Api.Version };
private readonly Configuration _pluginConfiguration;
private readonly DalamudUtil _dalamudUtil;
private readonly FileCacheManager _fileDbManager;
private CancellationTokenSource _connectionCancellationTokenSource;
private HubConnection? _mareHub;
private CancellationTokenSource? _uploadCancellationTokenSource = new();
private ConnectionDto? _connectionDto;
public SystemInfoDto SystemInfoDto { get; private set; } = new();
public bool IsModerator => (_connectionDto?.IsAdmin ?? false) || (_connectionDto?.IsModerator ?? false);
public bool IsAdmin => _connectionDto?.IsAdmin ?? false;
public ApiController(Configuration pluginConfiguration, DalamudUtil dalamudUtil, FileCacheManager fileDbManager)
{
Logger.Verbose("Creating " + nameof(ApiController));
_pluginConfiguration = pluginConfiguration;
_dalamudUtil = dalamudUtil;
_fileDbManager = fileDbManager;
_connectionCancellationTokenSource = new CancellationTokenSource();
_dalamudUtil.LogIn += DalamudUtilOnLogIn;
_dalamudUtil.LogOut += DalamudUtilOnLogOut;
ServerState = ServerState.Offline;
_verifiedUploadedHashes = new();
if (_dalamudUtil.IsLoggedIn)
{
DalamudUtilOnLogIn();
}
}
private void DalamudUtilOnLogOut()
{
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token));
ServerState = ServerState.Offline;
}
private void DalamudUtilOnLogIn()
{
Task.Run(CreateConnections);
}
public event EventHandler<CharacterReceivedEventArgs>? CharacterReceived;
public event VoidDelegate? Connected;
public event VoidDelegate? Disconnected;
public event SimpleStringDelegate? PairedClientOffline;
public event SimpleStringDelegate? PairedClientOnline;
public event SimpleStringDelegate? PairedWithOther;
public event SimpleStringDelegate? UnpairedFromOther;
public event VoidDelegate? DownloadStarted;
public event VoidDelegate? DownloadFinished;
public ConcurrentDictionary<int, List<DownloadFileTransfer>> CurrentDownloads { get; } = new();
public List<FileTransfer> CurrentUploads { get; } = new();
public List<FileTransfer> ForbiddenTransfers { get; } = new();
public List<BannedUserDto> AdminBannedUsers { get; private set; } = new();
public List<ForbiddenFileDto> AdminForbiddenFiles { get; private set; } = new();
public bool IsConnected => ServerState == ServerState.Connected;
public bool IsDownloading => CurrentDownloads.Count > 0;
public bool IsUploading => CurrentUploads.Count > 0;
public List<ClientPairDto> PairedClients { get; set; } = new();
public string SecretKey => _pluginConfiguration.ClientSecret.ContainsKey(ApiUri)
? _pluginConfiguration.ClientSecret[ApiUri] : string.Empty;
public bool ServerAlive => ServerState is ServerState.Connected or ServerState.RateLimited or ServerState.Unauthorized or ServerState.Disconnected;
public Dictionary<string, string> ServerDictionary => new Dictionary<string, string>()
{ { MainServiceUri, MainServer } }
.Concat(_pluginConfiguration.CustomServerList)
.ToDictionary(k => k.Key, k => k.Value);
public string UID => _connectionDto?.UID ?? string.Empty;
private string ApiUri => _pluginConfiguration.ApiUri;
public int OnlineUsers => SystemInfoDto.OnlineUsers;
private ServerState _serverState;
public ServerState ServerState
{
get => _serverState;
private set
{
Logger.Debug($"New ServerState: {value}, prev ServerState: {_serverState}");
_serverState = value;
}
}
public async Task CreateConnections()
{
Logger.Debug("CreateConnections called");
if (_pluginConfiguration.FullPause)
{
Logger.Info("Not recreating Connection, paused");
ServerState = ServerState.Disconnected;
_connectionDto = null;
await StopConnection(_connectionCancellationTokenSource.Token);
return;
}
await StopConnection(_connectionCancellationTokenSource.Token);
Logger.Info("Recreating Connection");
_connectionCancellationTokenSource.Cancel();
_connectionCancellationTokenSource = new CancellationTokenSource();
var token = _connectionCancellationTokenSource.Token;
_verifiedUploadedHashes.Clear();
while (ServerState is not ServerState.Connected && !token.IsCancellationRequested)
{
if (string.IsNullOrEmpty(SecretKey))
{
await Task.Delay(TimeSpan.FromSeconds(2));
continue;
}
await StopConnection(token);
try
{
Logger.Debug("Building connection");
while (!_dalamudUtil.IsPlayerPresent && !token.IsCancellationRequested)
{
Logger.Debug("Player not loaded in yet, waiting");
await Task.Delay(TimeSpan.FromSeconds(1), token);
}
if (token.IsCancellationRequested) break;
_mareHub = BuildHubConnection(Api.Path);
await _mareHub.StartAsync(token);
_mareHub.On<SystemInfoDto>(Api.OnUpdateSystemInfo, (dto) => SystemInfoDto = dto);
_connectionDto =
await _mareHub.InvokeAsync<ConnectionDto>(Api.InvokeHeartbeat, _dalamudUtil.PlayerNameHashed, token);
ServerState = ServerState.Connected;
if (_connectionDto.ServerVersion != Api.Version)
{
ServerState = ServerState.VersionMisMatch;
await StopConnection(token);
return;
}
if (ServerState is ServerState.Connected) // user is authorized && server is legit
{
await InitializeData(token);
_mareHub.Closed += MareHubOnClosed;
_mareHub.Reconnecting += MareHubOnReconnecting;
_mareHub.Reconnected += MareHubOnReconnected;
}
}
catch (HubException ex)
{
Logger.Warn(ex.GetType().ToString());
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
ServerState = ServerState.RateLimited;
await StopConnection(token);
return;
}
catch (HttpRequestException ex)
{
Logger.Warn(ex.GetType().ToString());
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
ServerState = ServerState.Unauthorized;
await StopConnection(token);
return;
}
else
{
ServerState = ServerState.Offline;
Logger.Info("Failed to establish connection, retrying");
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token);
}
}
catch (Exception ex)
{
Logger.Warn(ex.GetType().ToString());
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
Logger.Info("Failed to establish connection, retrying");
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token);
}
}
}
private Task MareHubOnReconnected(string? arg)
{
_ = Task.Run(CreateConnections);
return Task.CompletedTask;
}
private async Task InitializeData(CancellationToken token)
{
if (_mareHub == null) return;
Logger.Debug("Initializing data");
_mareHub.On<ClientPairDto, string>(Api.OnUserUpdateClientPairs,
UpdateLocalClientPairsCallback);
_mareHub.On<CharacterCacheDto, string>(Api.OnUserReceiveCharacterData,
ReceiveCharacterDataCallback);
_mareHub.On<string>(Api.OnUserRemoveOnlinePairedPlayer,
(s) => PairedClientOffline?.Invoke(s));
_mareHub.On<string>(Api.OnUserAddOnlinePairedPlayer,
(s) => PairedClientOnline?.Invoke(s));
_mareHub.On(Api.OnAdminForcedReconnect, UserForcedReconnectCallback);
PairedClients =
await _mareHub!.InvokeAsync<List<ClientPairDto>>(Api.InvokeUserGetPairedClients, token);
if (IsModerator)
{
AdminForbiddenFiles =
await _mareHub.InvokeAsync<List<ForbiddenFileDto>>(Api.InvokeAdminGetForbiddenFiles,
token);
AdminBannedUsers =
await _mareHub.InvokeAsync<List<BannedUserDto>>(Api.InvokeAdminGetBannedUsers,
token);
_mareHub.On<BannedUserDto>(Api.OnAdminUpdateOrAddBannedUser,
UpdateOrAddBannedUserCallback);
_mareHub.On<BannedUserDto>(Api.OnAdminDeleteBannedUser, DeleteBannedUserCallback);
_mareHub.On<ForbiddenFileDto>(Api.OnAdminUpdateOrAddForbiddenFile,
UpdateOrAddForbiddenFileCallback);
_mareHub.On<ForbiddenFileDto>(Api.OnAdminDeleteForbiddenFile,
DeleteForbiddenFileCallback);
}
Connected?.Invoke();
}
public void Dispose()
{
Logger.Verbose("Disposing " + nameof(ApiController));
_dalamudUtil.LogIn -= DalamudUtilOnLogIn;
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token));
_connectionCancellationTokenSource?.Cancel();
}
private HubConnection BuildHubConnection(string hubName)
{
return new HubConnectionBuilder()
.WithUrl(ApiUri + hubName, options =>
{
options.Headers.Add("Authorization", SecretKey);
options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling;
})
.WithAutomaticReconnect(new ForeverRetryPolicy())
.ConfigureLogging(a => {
a.ClearProviders().AddProvider(new DalamudLoggingProvider());
a.SetMinimumLevel(LogLevel.Warning);
})
.Build();
}
private Task MareHubOnClosed(Exception? arg)
{
CurrentUploads.Clear();
CurrentDownloads.Clear();
_uploadCancellationTokenSource?.Cancel();
Disconnected?.Invoke();
ServerState = ServerState.Offline;
Logger.Info("Connection closed");
return Task.CompletedTask;
}
private Task MareHubOnReconnecting(Exception? arg)
{
ServerState = ServerState.Disconnected;
Logger.Warn("Connection closed... Reconnecting");
Logger.Warn(arg?.Message ?? string.Empty);
Logger.Warn(arg?.StackTrace ?? string.Empty);
Disconnected?.Invoke();
ServerState = ServerState.Offline;
return Task.CompletedTask;
}
private async Task StopConnection(CancellationToken token)
{
if (_mareHub is not null)
{
_uploadCancellationTokenSource?.Cancel();
Logger.Info("Stopping existing connection");
_mareHub.Closed -= MareHubOnClosed;
_mareHub.Reconnecting -= MareHubOnReconnecting;
_mareHub.Reconnected -= MareHubOnReconnected;
await _mareHub.StopAsync(token);
await _mareHub.DisposeAsync();
CurrentUploads.Clear();
CurrentDownloads.Clear();
_uploadCancellationTokenSource?.Cancel();
Disconnected?.Invoke();
_mareHub = null;
}
if (ServerState != ServerState.Disconnected)
{
while (ServerState != ServerState.Offline)
{
await Task.Delay(16);
}
}
}
}
}

View File

@@ -3,10 +3,10 @@ using System.Threading.Tasks;
using MareSynchronos.API;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public partial class ApiController
{
public async Task AddOrUpdateForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
{
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile);
@@ -43,5 +43,4 @@ namespace MareSynchronos.WebAPI
{
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false);
}
}
}

View File

@@ -4,10 +4,10 @@ using MareSynchronos.API;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
namespace MareSynchronos.WebAPI
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public partial class ApiController
{
private void UserForcedReconnectCallback()
{
_ = CreateConnections();
@@ -74,5 +74,14 @@ namespace MareSynchronos.WebAPI
{
AdminForbiddenFiles.RemoveAll(f => f.Hash == obj.Hash);
}
private void GroupPairChangedCallback(GroupPairDto dto)
{
}
private void GroupChangedCallback(GroupDto dto)
{
}
}

View File

@@ -0,0 +1,64 @@
using MareSynchronos.API;
using Microsoft.AspNetCore.SignalR.Client;
using System.Collections.Generic;
using System.Threading.Tasks;
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public async Task<GroupCreatedDto> CreateGroup()
{
return await _mareHub!.InvokeAsync<GroupCreatedDto>(Api.InvokeGroupCreate);
}
public async Task<bool> ChangeGroupPassword(string gid, string newpassword)
{
return await _mareHub!.InvokeAsync<bool>(Api.InvokeGroupChangePassword, gid, newpassword);
}
public async Task<List<GroupDto>> GetGroups()
{
return await _mareHub!.InvokeAsync<List<GroupDto>>(Api.InvokeGroupGetGroups);
}
public async Task<List<GroupPairDto>> GetUsersInGroup(string gid)
{
return await _mareHub!.InvokeAsync<List<GroupPairDto>>(Api.InvokeGroupGetUsersInGroup, gid);
}
public async Task SendGroupJoin(string gid, string password)
{
if (!IsConnected || SecretKey == "-") return;
await _mareHub!.SendAsync(Api.SendGroupJoin, gid, password);
}
public async Task SendGroupChangeInviteState(string gid, bool opened)
{
await _mareHub!.SendAsync(Api.SendGroupChangeInviteState, gid, opened);
}
public async Task SendDeleteGroup(string gid)
{
await _mareHub!.SendAsync(Api.SendGroupDelete, gid);
}
public async Task SendLeaveGroup(string gid)
{
await _mareHub!.SendAsync(Api.SendGroupLeave, gid);
}
public async Task SendPauseGroup(string gid, bool isPaused)
{
await _mareHub!.SendAsync(Api.SendGroupPause, gid, isPaused);
}
public async Task SendRemoveUserFromGroup(string gid, string uid)
{
await _mareHub!.SendAsync(Api.SendGroupRemoveUser, gid, uid);
}
public async Task ChangeOwnerOfGroup(string gid, string uid)
{
await _mareHub!.SendAsync(Api.SendGroupChangeOwner, gid, uid);
}
}

View File

@@ -0,0 +1,372 @@
using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.FileCache;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
using Microsoft.AspNetCore.Http.Connections;
using Microsoft.AspNetCore.SignalR;
using Microsoft.AspNetCore.SignalR.Client;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.WebAPI;
public delegate void SimpleStringDelegate(string str);
public partial class ApiController : IDisposable
{
public const string MainServer = "Lunae Crescere Incipientis (Central Server EU)";
public const string MainServiceUri = "wss://maresynchronos.com";
public readonly int[] SupportedServerVersions = { Api.Version };
private readonly Configuration _pluginConfiguration;
private readonly DalamudUtil _dalamudUtil;
private readonly FileCacheManager _fileDbManager;
private CancellationTokenSource _connectionCancellationTokenSource;
private HubConnection? _mareHub;
private CancellationTokenSource? _uploadCancellationTokenSource = new();
private ConnectionDto? _connectionDto;
public SystemInfoDto SystemInfoDto { get; private set; } = new();
public bool IsModerator => (_connectionDto?.IsAdmin ?? false) || (_connectionDto?.IsModerator ?? false);
public bool IsAdmin => _connectionDto?.IsAdmin ?? false;
public ApiController(Configuration pluginConfiguration, DalamudUtil dalamudUtil, FileCacheManager fileDbManager)
{
Logger.Verbose("Creating " + nameof(ApiController));
_pluginConfiguration = pluginConfiguration;
_dalamudUtil = dalamudUtil;
_fileDbManager = fileDbManager;
_connectionCancellationTokenSource = new CancellationTokenSource();
_dalamudUtil.LogIn += DalamudUtilOnLogIn;
_dalamudUtil.LogOut += DalamudUtilOnLogOut;
ServerState = ServerState.Offline;
_verifiedUploadedHashes = new();
if (_dalamudUtil.IsLoggedIn)
{
DalamudUtilOnLogIn();
}
}
private void DalamudUtilOnLogOut()
{
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token));
ServerState = ServerState.Offline;
}
private void DalamudUtilOnLogIn()
{
Task.Run(CreateConnections);
}
public event EventHandler<CharacterReceivedEventArgs>? CharacterReceived;
public event VoidDelegate? Connected;
public event VoidDelegate? Disconnected;
public event SimpleStringDelegate? PairedClientOffline;
public event SimpleStringDelegate? PairedClientOnline;
public event SimpleStringDelegate? PairedWithOther;
public event SimpleStringDelegate? UnpairedFromOther;
public event VoidDelegate? DownloadStarted;
public event VoidDelegate? DownloadFinished;
public ConcurrentDictionary<int, List<DownloadFileTransfer>> CurrentDownloads { get; } = new();
public List<FileTransfer> CurrentUploads { get; } = new();
public List<FileTransfer> ForbiddenTransfers { get; } = new();
public List<BannedUserDto> AdminBannedUsers { get; private set; } = new();
public List<ForbiddenFileDto> AdminForbiddenFiles { get; private set; } = new();
public bool IsConnected => ServerState == ServerState.Connected;
public bool IsDownloading => CurrentDownloads.Count > 0;
public bool IsUploading => CurrentUploads.Count > 0;
public List<ClientPairDto> PairedClients { get; set; } = new();
public 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>()
{ { MainServiceUri, MainServer } }
.Concat(_pluginConfiguration.CustomServerList)
.ToDictionary(k => k.Key, k => k.Value);
public string UID => _connectionDto?.UID ?? string.Empty;
private string ApiUri => _pluginConfiguration.ApiUri;
public int OnlineUsers => SystemInfoDto.OnlineUsers;
private ServerState _serverState;
public ServerState ServerState
{
get => _serverState;
private set
{
Logger.Debug($"New ServerState: {value}, prev ServerState: {_serverState}");
_serverState = value;
}
}
public async Task CreateConnections()
{
Logger.Debug("CreateConnections called");
if (_pluginConfiguration.FullPause)
{
Logger.Info("Not recreating Connection, paused");
ServerState = ServerState.Disconnected;
_connectionDto = null;
await StopConnection(_connectionCancellationTokenSource.Token);
return;
}
await StopConnection(_connectionCancellationTokenSource.Token);
Logger.Info("Recreating Connection");
_connectionCancellationTokenSource.Cancel();
_connectionCancellationTokenSource = new CancellationTokenSource();
var token = _connectionCancellationTokenSource.Token;
_verifiedUploadedHashes.Clear();
while (ServerState is not ServerState.Connected && !token.IsCancellationRequested)
{
if (string.IsNullOrEmpty(SecretKey))
{
await Task.Delay(TimeSpan.FromSeconds(2));
continue;
}
await StopConnection(token);
try
{
Logger.Debug("Building connection");
while (!_dalamudUtil.IsPlayerPresent && !token.IsCancellationRequested)
{
Logger.Debug("Player not loaded in yet, waiting");
await Task.Delay(TimeSpan.FromSeconds(1), token);
}
if (token.IsCancellationRequested) break;
_mareHub = BuildHubConnection(Api.Path);
await _mareHub.StartAsync(token);
_mareHub.On<SystemInfoDto>(Api.OnUpdateSystemInfo, (dto) => SystemInfoDto = dto);
_connectionDto =
await _mareHub.InvokeAsync<ConnectionDto>(Api.InvokeHeartbeat, _dalamudUtil.PlayerNameHashed, token);
ServerState = ServerState.Connected;
if (_connectionDto.ServerVersion != Api.Version)
{
ServerState = ServerState.VersionMisMatch;
await StopConnection(token);
return;
}
if (ServerState is ServerState.Connected) // user is authorized && server is legit
{
await InitializeData(token);
_mareHub.Closed += MareHubOnClosed;
_mareHub.Reconnecting += MareHubOnReconnecting;
_mareHub.Reconnected += MareHubOnReconnected;
}
}
catch (HubException ex)
{
Logger.Warn(ex.GetType().ToString());
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
ServerState = ServerState.RateLimited;
await StopConnection(token);
return;
}
catch (HttpRequestException ex)
{
Logger.Warn(ex.GetType().ToString());
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
if (ex.StatusCode == System.Net.HttpStatusCode.Unauthorized)
{
ServerState = ServerState.Unauthorized;
await StopConnection(token);
return;
}
else
{
ServerState = ServerState.Offline;
Logger.Info("Failed to establish connection, retrying");
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token);
}
}
catch (Exception ex)
{
Logger.Warn(ex.GetType().ToString());
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
Logger.Info("Failed to establish connection, retrying");
await Task.Delay(TimeSpan.FromSeconds(new Random().Next(5, 20)), token);
}
}
}
private Task MareHubOnReconnected(string? arg)
{
_ = Task.Run(CreateConnections);
return Task.CompletedTask;
}
private async Task InitializeData(CancellationToken token)
{
if (_mareHub == null) return;
Logger.Debug("Initializing data");
_mareHub.On<ClientPairDto>(Api.OnUserUpdateClientPairs,
UpdateLocalClientPairsCallback);
_mareHub.On<CharacterCacheDto, string>(Api.OnUserReceiveCharacterData,
ReceiveCharacterDataCallback);
_mareHub.On<string>(Api.OnUserRemoveOnlinePairedPlayer,
(s) => PairedClientOffline?.Invoke(s));
_mareHub.On<string>(Api.OnUserAddOnlinePairedPlayer,
(s) => PairedClientOnline?.Invoke(s));
_mareHub.On(Api.OnAdminForcedReconnect, UserForcedReconnectCallback);
_mareHub.On<GroupDto>(Api.OnGroupChange, GroupChangedCallback);
_mareHub.On<GroupPairDto>(Api.OnGroupUserChange, GroupPairChangedCallback);
PairedClients =
await _mareHub!.InvokeAsync<List<ClientPairDto>>(Api.InvokeUserGetPairedClients, token);
Groups = await GetGroups();
foreach (var group in Groups)
{
GroupPairedClients.AddRange(await GetUsersInGroup(group.GID));
}
if (IsModerator)
{
AdminForbiddenFiles =
await _mareHub.InvokeAsync<List<ForbiddenFileDto>>(Api.InvokeAdminGetForbiddenFiles,
token);
AdminBannedUsers =
await _mareHub.InvokeAsync<List<BannedUserDto>>(Api.InvokeAdminGetBannedUsers,
token);
_mareHub.On<BannedUserDto>(Api.OnAdminUpdateOrAddBannedUser,
UpdateOrAddBannedUserCallback);
_mareHub.On<BannedUserDto>(Api.OnAdminDeleteBannedUser, DeleteBannedUserCallback);
_mareHub.On<ForbiddenFileDto>(Api.OnAdminUpdateOrAddForbiddenFile,
UpdateOrAddForbiddenFileCallback);
_mareHub.On<ForbiddenFileDto>(Api.OnAdminDeleteForbiddenFile,
DeleteForbiddenFileCallback);
}
Connected?.Invoke();
}
public void Dispose()
{
Logger.Verbose("Disposing " + nameof(ApiController));
_dalamudUtil.LogIn -= DalamudUtilOnLogIn;
_dalamudUtil.LogOut -= DalamudUtilOnLogOut;
Task.Run(async () => await StopConnection(_connectionCancellationTokenSource.Token));
_connectionCancellationTokenSource?.Cancel();
}
private HubConnection BuildHubConnection(string hubName)
{
return new HubConnectionBuilder()
.WithUrl(ApiUri + hubName, options =>
{
options.Headers.Add("Authorization", SecretKey);
options.Transports = HttpTransportType.WebSockets | HttpTransportType.ServerSentEvents | HttpTransportType.LongPolling;
})
.WithAutomaticReconnect(new ForeverRetryPolicy())
.ConfigureLogging(a =>
{
a.ClearProviders().AddProvider(new DalamudLoggingProvider());
a.SetMinimumLevel(LogLevel.Warning);
})
.Build();
}
private Task MareHubOnClosed(Exception? arg)
{
CurrentUploads.Clear();
CurrentDownloads.Clear();
_uploadCancellationTokenSource?.Cancel();
Disconnected?.Invoke();
ServerState = ServerState.Offline;
Logger.Info("Connection closed");
return Task.CompletedTask;
}
private Task MareHubOnReconnecting(Exception? arg)
{
ServerState = ServerState.Disconnected;
Logger.Warn("Connection closed... Reconnecting");
Logger.Warn(arg?.Message ?? string.Empty);
Logger.Warn(arg?.StackTrace ?? string.Empty);
Disconnected?.Invoke();
ServerState = ServerState.Offline;
return Task.CompletedTask;
}
private async Task StopConnection(CancellationToken token)
{
if (_mareHub is not null)
{
_uploadCancellationTokenSource?.Cancel();
Logger.Info("Stopping existing connection");
_mareHub.Closed -= MareHubOnClosed;
_mareHub.Reconnecting -= MareHubOnReconnecting;
_mareHub.Reconnected -= MareHubOnReconnected;
await _mareHub.StopAsync(token);
await _mareHub.DisposeAsync();
CurrentUploads.Clear();
CurrentDownloads.Clear();
_uploadCancellationTokenSource?.Cancel();
Disconnected?.Invoke();
_mareHub = null;
}
if (ServerState != ServerState.Disconnected)
{
while (ServerState != ServerState.Offline)
{
await Task.Delay(16);
}
}
}
}

View File

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