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,8 +7,8 @@ using System.Linq;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos
{
namespace MareSynchronos;
public static class ConfigurationExtensions
{
public static bool HasValidSetup(this Configuration configuration)
@@ -198,4 +198,3 @@ namespace MareSynchronos
}
}
}
}

View File

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

View File

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

View File

@@ -8,8 +8,8 @@ using MareSynchronos.WebAPI;
using Action = System.Action;
using System.Collections.Concurrent;
namespace MareSynchronos.Managers
{
namespace MareSynchronos.Managers;
public delegate void PenumbraRedrawEvent(IntPtr address, int objTblIdx);
public delegate void HeelsOffsetChange(float change);
public delegate void PenumbraResourceLoadEvent(IntPtr drawObject, string gamePath, string filePath);
@@ -398,4 +398,3 @@ namespace MareSynchronos.Managers
actionQueue.Clear();
}
}
}

View File

@@ -14,8 +14,8 @@ using MareSynchronos.FileCache;
using Newtonsoft.Json;
#endif
namespace MareSynchronos.Managers
{
namespace MareSynchronos.Managers;
public delegate void PlayerHasChanged(CharacterCacheDto characterCache);
public class PlayerManager : IDisposable
@@ -268,4 +268,3 @@ namespace MareSynchronos.Managers
}, token);
}
}
}

View File

@@ -8,8 +8,8 @@ using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace MareSynchronos.Managers
{
namespace MareSynchronos.Managers;
public delegate void TransientResourceLoadedEvent(IntPtr drawObject);
public class TransientResourceManager : IDisposable
@@ -202,4 +202,3 @@ namespace MareSynchronos.Managers
}
}
}
}

View File

@@ -6,8 +6,8 @@ using MareSynchronos.API;
using MareSynchronos.Utils;
using Lumina.Excel.GeneratedSheets;
namespace MareSynchronos.Models
{
namespace MareSynchronos.Models;
[JsonObject(MemberSerialization.OptIn)]
public class CharacterData
{
@@ -85,4 +85,3 @@ namespace MareSynchronos.Models
return stringBuilder.ToString();
}
}
}

View File

@@ -6,8 +6,8 @@ using MareSynchronos.API;
using System.Text.RegularExpressions;
using MareSynchronos.FileCache;
namespace MareSynchronos.Models
{
namespace MareSynchronos.Models;
public class FileReplacement
{
private readonly FileCacheManager fileDbManager;
@@ -57,4 +57,3 @@ namespace MareSynchronos.Models
return builder.ToString();
}
}
}

View File

@@ -5,8 +5,8 @@ using System.Runtime.InteropServices;
using MareSynchronos.Utils;
using Penumbra.GameData.ByteString;
namespace MareSynchronos.Models
{
namespace MareSynchronos.Models;
public class PlayerRelatedObject
{
private readonly Func<IntPtr> getAddress;
@@ -133,4 +133,3 @@ namespace MareSynchronos.Models
return hasChanges;
}
}
}

View File

@@ -15,8 +15,8 @@ using MareSynchronos.Utils;
using Dalamud.Game.ClientState.Conditions;
using MareSynchronos.FileCache;
namespace MareSynchronos
{
namespace MareSynchronos;
public sealed class Plugin : IDalamudPlugin
{
private const string CommandName = "/mare";
@@ -215,4 +215,3 @@ namespace MareSynchronos
_introUi.Toggle();
}
}
}

View File

@@ -14,8 +14,8 @@ using MareSynchronos.API;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.UI
{
namespace MareSynchronos.UI;
public class CompactUi : Window, IDisposable
{
private readonly ApiController _apiController;
@@ -560,4 +560,3 @@ namespace MareSynchronos.UI
};
}
}
}

View File

@@ -12,8 +12,8 @@ using MareSynchronos.Localization;
using Dalamud.Utility;
using MareSynchronos.FileCache;
namespace MareSynchronos.UI
{
namespace MareSynchronos.UI;
internal class IntroUi : Window, IDisposable
{
private readonly UiShared _uiShared;
@@ -295,4 +295,3 @@ namespace MareSynchronos.UI
return new($"{Strings.ToS.ParagraphLabel} {paragraphIdx + 1}, {Strings.ToS.SentenceLabel} {sentenceIdx + 1}, {Strings.ToS.WordLabel} {wordIdx + 1}", splitSentence[wordIdx]);
}
}
}

View File

@@ -14,8 +14,8 @@ using MareSynchronos.WebAPI.Utils;
using System.Diagnostics;
using Dalamud.Utility;
namespace MareSynchronos.UI
{
namespace MareSynchronos.UI;
public delegate void SwitchUi();
public class SettingsUi : Window, IDisposable
{
@@ -610,4 +610,3 @@ namespace MareSynchronos.UI
base.OnClose();
}
}
}

View File

@@ -18,8 +18,8 @@ using MareSynchronos.Managers;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI;
namespace MareSynchronos.UI
{
namespace MareSynchronos.UI;
public class UiShared : IDisposable
{
[DllImport("user32")]
@@ -553,4 +553,3 @@ namespace MareSynchronos.UI
_pluginInterface.UiBuilder.BuildFonts -= BuildFont;
}
}
}

View File

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

View File

@@ -11,8 +11,8 @@ using Dalamud.Game.ClientState.Objects.SubKinds;
using FFXIVClientStructs.FFXIV.Client.Game.Character;
using GameObject = FFXIVClientStructs.FFXIV.Client.Game.Object.GameObject;
namespace MareSynchronos.Utils
{
namespace MareSynchronos.Utils;
public delegate void PlayerChange(Dalamud.Game.ClientState.Objects.Types.Character actor);
public delegate void LogIn();
@@ -239,4 +239,3 @@ namespace MareSynchronos.Utils
_framework.Update -= FrameworkOnUpdate;
}
}
}

View File

@@ -5,8 +5,8 @@ using Dalamud.Logging;
using Dalamud.Utility;
using Microsoft.Extensions.Logging;
namespace MareSynchronos.Utils
{
namespace MareSynchronos.Utils;
[ProviderAlias("Dalamud")]
public class DalamudLoggingProvider : ILoggerProvider
{
@@ -109,4 +109,3 @@ namespace MareSynchronos.Utils
public IDisposable BeginScope<TState>(TState state) => default!;
}
}

View File

@@ -6,8 +6,8 @@ using System.Reflection;
using System.Text;
using System.Threading.Tasks;
namespace MareSynchronos.Utils
{
namespace MareSynchronos.Utils;
public static class VariousExtensions
{
public static DateTime GetLinkerTime(Assembly assembly)
@@ -29,4 +29,3 @@ namespace MareSynchronos.Utils
return default;
}
}
}

View File

@@ -13,8 +13,8 @@ using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI
{
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
private readonly HashSet<string> _verifiedUploadedHashes;
@@ -315,4 +315,3 @@ namespace MareSynchronos.WebAPI
}
}
}

View File

@@ -1,11 +1,10 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.Utils;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI
{
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public async Task DeleteAccount()
@@ -41,4 +40,4 @@ namespace MareSynchronos.WebAPI
}
}
}

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,8 +3,8 @@ using System.Threading.Tasks;
using MareSynchronos.API;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI
{
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public async Task AddOrUpdateForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
@@ -44,4 +44,3 @@ namespace MareSynchronos.WebAPI
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false);
}
}
}

View File

@@ -4,8 +4,8 @@ using MareSynchronos.API;
using MareSynchronos.Utils;
using MareSynchronos.WebAPI.Utils;
namespace MareSynchronos.WebAPI
{
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
private void UserForcedReconnectCallback()
@@ -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
}