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

View File

@@ -13,306 +13,305 @@ 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;
public void CancelUpload()
{
private readonly HashSet<string> _verifiedUploadedHashes;
private int _downloadId = 0;
public void CancelUpload()
if (_uploadCancellationTokenSource != null)
{
if (_uploadCancellationTokenSource != null)
{
Logger.Debug("Cancelling upload");
_uploadCancellationTokenSource?.Cancel();
_mareHub!.SendAsync(Api.SendFileAbortUpload);
CurrentUploads.Clear();
}
}
public async Task DeleteAllMyFiles()
{
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles);
}
private async Task<string> DownloadFile(int downloadId, string hash, Uri downloadUri, CancellationToken ct)
{
using WebClient wc = new();
wc.Headers.Add("Authorization", SecretKey);
DownloadProgressChangedEventHandler progChanged = (s, e) =>
{
try
{
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = e.BytesReceived;
}
catch (Exception ex)
{
Logger.Warn("Could not set download progress for " + hash);
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
}
};
wc.DownloadProgressChanged += progChanged;
string fileName = Path.GetTempFileName();
ct.Register(wc.CancelAsync);
try
{
await wc.DownloadFileTaskAsync(downloadUri, fileName);
}
catch { }
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = CurrentDownloads[downloadId].Single(f => f.Hash == hash).Total;
wc.DownloadProgressChanged -= progChanged;
return fileName;
}
public int GetDownloadId() => _downloadId++;
public async Task DownloadFiles(int currentDownloadId, List<FileReplacementDto> fileReplacementDto, CancellationToken ct)
{
DownloadStarted?.Invoke();
try
{
await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct);
}
catch
{
CancelDownload(currentDownloadId);
}
finally
{
DownloadFinished?.Invoke();
}
}
private async Task DownloadFilesInternal(int currentDownloadId, List<FileReplacementDto> fileReplacementDto, CancellationToken ct)
{
Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")");
List<DownloadFileDto> downloadFileInfoFromService = new List<DownloadFileDto>();
downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync<List<DownloadFileDto>>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct));
Logger.Debug("Files with size 0 or less: " + string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
CurrentDownloads[currentDownloadId] = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d))
.Where(d => d.CanBeTransferred).ToList();
foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden))
{
if (ForbiddenTransfers.All(f => f.Hash != dto.Hash))
{
ForbiddenTransfers.Add(new DownloadFileTransfer(dto));
}
}
await Parallel.ForEachAsync(CurrentDownloads[currentDownloadId].Where(f => f.CanBeTransferred), new ParallelOptions()
{
MaxDegreeOfParallelism = 5,
CancellationToken = ct
},
async (file, token) =>
{
var hash = file.Hash;
var tempFile = await DownloadFile(currentDownloadId, file.Hash, file.DownloadUri, token);
if (token.IsCancellationRequested)
{
File.Delete(tempFile);
Logger.Debug("Detected cancellation, removing " + currentDownloadId);
DownloadFinished?.Invoke();
CancelDownload(currentDownloadId);
return;
}
var tempFileData = await File.ReadAllBytesAsync(tempFile, token);
var extractedFile = LZ4Codec.Unwrap(tempFileData);
File.Delete(tempFile);
var filePath = Path.Combine(_pluginConfiguration.CacheFolder, file.Hash);
await File.WriteAllBytesAsync(filePath, extractedFile, token);
var fi = new FileInfo(filePath);
Func<DateTime> RandomDayFunc()
{
DateTime start = new DateTime(1995, 1, 1);
Random gen = new Random();
int range = (DateTime.Today - start).Days;
return () => start.AddDays(gen.Next(range));
}
fi.CreationTime = RandomDayFunc().Invoke();
fi.LastAccessTime = RandomDayFunc().Invoke();
fi.LastWriteTime = RandomDayFunc().Invoke();
try
{
_ = _fileDbManager.CreateCacheEntry(filePath);
}
catch (Exception ex)
{
Logger.Warn("Issue adding file to the DB");
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace);
}
});
Logger.Debug("Download complete, removing " + currentDownloadId);
CancelDownload(currentDownloadId);
}
public async Task PushCharacterData(CharacterCacheDto character, List<string> visibleCharacterIds)
{
if (!IsConnected || SecretKey == "-") return;
Logger.Debug("Sending Character data to service " + ApiUri);
CancelUpload();
_uploadCancellationTokenSource = new CancellationTokenSource();
var uploadToken = _uploadCancellationTokenSource.Token;
Logger.Verbose("New Token Created");
List<string> unverifiedUploadHashes = new();
foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct()).Distinct().ToList())
{
if (!_verifiedUploadedHashes.Contains(item))
{
unverifiedUploadHashes.Add(item);
}
}
if (unverifiedUploadHashes.Any())
{
Logger.Debug("Verifying " + unverifiedUploadHashes.Count + " files");
var filesToUpload = await _mareHub!.InvokeAsync<List<UploadFileDto>>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken);
foreach (var file in filesToUpload.Where(f => !f.IsForbidden))
{
try
{
CurrentUploads.Add(new UploadFileTransfer(file)
{
Total = new FileInfo(_fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath).Length
});
}
catch (Exception ex)
{
Logger.Warn("Tried to request file " + file.Hash + " but file was not present");
Logger.Warn(ex.StackTrace!);
}
}
foreach (var file in filesToUpload.Where(c => c.IsForbidden))
{
if (ForbiddenTransfers.All(f => f.Hash != file.Hash))
{
ForbiddenTransfers.Add(new UploadFileTransfer(file)
{
LocalFile = _fileDbManager.GetFileCacheByHash(file.Hash)?.ResolvedFilepath ?? string.Empty
});
}
}
var totalSize = CurrentUploads.Sum(c => c.Total);
Logger.Debug("Compressing and uploading files");
foreach (var file in CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList())
{
Logger.Debug("Compressing and uploading " + file);
var data = await GetCompressedFileData(file.Hash, uploadToken);
CurrentUploads.Single(e => e.Hash == data.Item1).Total = data.Item2.Length;
await UploadFile(data.Item2, file.Hash, uploadToken);
if (!uploadToken.IsCancellationRequested) continue;
Logger.Warn("Cancel in filesToUpload loop detected");
CurrentUploads.Clear();
break;
}
if (CurrentUploads.Any())
{
var compressedSize = CurrentUploads.Sum(c => c.Total);
Logger.Debug($"Compressed {totalSize} to {compressedSize} ({(compressedSize / (double)totalSize):P2})");
}
Logger.Debug("Upload tasks complete, waiting for server to confirm");
var anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
Logger.Debug("Uploads open: " + anyUploadsOpen);
while (anyUploadsOpen && !uploadToken.IsCancellationRequested)
{
anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken);
Logger.Debug("Waiting for uploads to finish");
}
foreach (var item in unverifiedUploadHashes)
{
_verifiedUploadedHashes.Add(item);
}
CurrentUploads.Clear();
}
else
{
Logger.Debug("All files already verified");
}
if (!uploadToken.IsCancellationRequested)
{
Logger.Info("Pushing character data for " + character.GetHashCode() + " to " + string.Join(", ", visibleCharacterIds));
StringBuilder sb = new StringBuilder();
foreach (var item in character.FileReplacements)
{
sb.AppendLine($"FileReplacements for {item.Key}: {item.Value.Count}");
}
foreach (var item in character.GlamourerData)
{
sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}");
}
Logger.Debug("Chara data contained: " + Environment.NewLine + sb.ToString());
await _mareHub!.InvokeAsync(Api.InvokeUserPushCharacterDataToVisibleClients, character, visibleCharacterIds, uploadToken);
}
else
{
Logger.Warn("=== Upload operation was cancelled ===");
}
Logger.Verbose("Upload complete for " + character.GetHashCode());
_uploadCancellationTokenSource = null;
}
private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
{
var fileCache = _fileDbManager.GetFileCacheByHash(fileHash)!.ResolvedFilepath;
return (fileHash, LZ4Codec.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken), 0,
(int)new FileInfo(fileCache).Length));
}
private async Task UploadFile(byte[] compressedFile, string fileHash, CancellationToken uploadToken)
{
if (uploadToken.IsCancellationRequested) return;
async IAsyncEnumerable<byte[]> AsyncFileData([EnumeratorCancellation] CancellationToken token)
{
var chunkSize = 1024 * 512; // 512kb
using var ms = new MemoryStream(compressedFile);
var buffer = new byte[chunkSize];
int bytesRead;
while ((bytesRead = await ms.ReadAsync(buffer, 0, chunkSize, token)) > 0 && !token.IsCancellationRequested)
{
CurrentUploads.Single(f => f.Hash == fileHash).Transferred += bytesRead;
token.ThrowIfCancellationRequested();
yield return bytesRead == chunkSize ? buffer.ToArray() : buffer.Take(bytesRead).ToArray();
}
}
await _mareHub!.SendAsync(Api.SendFileUploadFileStreamAsync, fileHash, AsyncFileData(uploadToken), uploadToken);
}
public void CancelDownload(int downloadId)
{
while (CurrentDownloads.ContainsKey(downloadId))
{
CurrentDownloads.TryRemove(downloadId, out _);
}
Logger.Debug("Cancelling upload");
_uploadCancellationTokenSource?.Cancel();
_mareHub!.SendAsync(Api.SendFileAbortUpload);
CurrentUploads.Clear();
}
}
public async Task DeleteAllMyFiles()
{
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles);
}
private async Task<string> DownloadFile(int downloadId, string hash, Uri downloadUri, CancellationToken ct)
{
using WebClient wc = new();
wc.Headers.Add("Authorization", SecretKey);
DownloadProgressChangedEventHandler progChanged = (s, e) =>
{
try
{
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = e.BytesReceived;
}
catch (Exception ex)
{
Logger.Warn("Could not set download progress for " + hash);
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace ?? string.Empty);
}
};
wc.DownloadProgressChanged += progChanged;
string fileName = Path.GetTempFileName();
ct.Register(wc.CancelAsync);
try
{
await wc.DownloadFileTaskAsync(downloadUri, fileName);
}
catch { }
CurrentDownloads[downloadId].Single(f => f.Hash == hash).Transferred = CurrentDownloads[downloadId].Single(f => f.Hash == hash).Total;
wc.DownloadProgressChanged -= progChanged;
return fileName;
}
public int GetDownloadId() => _downloadId++;
public async Task DownloadFiles(int currentDownloadId, List<FileReplacementDto> fileReplacementDto, CancellationToken ct)
{
DownloadStarted?.Invoke();
try
{
await DownloadFilesInternal(currentDownloadId, fileReplacementDto, ct);
}
catch
{
CancelDownload(currentDownloadId);
}
finally
{
DownloadFinished?.Invoke();
}
}
private async Task DownloadFilesInternal(int currentDownloadId, List<FileReplacementDto> fileReplacementDto, CancellationToken ct)
{
Logger.Debug("Downloading files (Download ID " + currentDownloadId + ")");
List<DownloadFileDto> downloadFileInfoFromService = new List<DownloadFileDto>();
downloadFileInfoFromService.AddRange(await _mareHub!.InvokeAsync<List<DownloadFileDto>>(Api.InvokeGetFilesSizes, fileReplacementDto.Select(f => f.Hash).ToList(), ct));
Logger.Debug("Files with size 0 or less: " + string.Join(", ", downloadFileInfoFromService.Where(f => f.Size <= 0).Select(f => f.Hash)));
CurrentDownloads[currentDownloadId] = downloadFileInfoFromService.Distinct().Select(d => new DownloadFileTransfer(d))
.Where(d => d.CanBeTransferred).ToList();
foreach (var dto in downloadFileInfoFromService.Where(c => c.IsForbidden))
{
if (ForbiddenTransfers.All(f => f.Hash != dto.Hash))
{
ForbiddenTransfers.Add(new DownloadFileTransfer(dto));
}
}
await Parallel.ForEachAsync(CurrentDownloads[currentDownloadId].Where(f => f.CanBeTransferred), new ParallelOptions()
{
MaxDegreeOfParallelism = 5,
CancellationToken = ct
},
async (file, token) =>
{
var hash = file.Hash;
var tempFile = await DownloadFile(currentDownloadId, file.Hash, file.DownloadUri, token);
if (token.IsCancellationRequested)
{
File.Delete(tempFile);
Logger.Debug("Detected cancellation, removing " + currentDownloadId);
DownloadFinished?.Invoke();
CancelDownload(currentDownloadId);
return;
}
var tempFileData = await File.ReadAllBytesAsync(tempFile, token);
var extractedFile = LZ4Codec.Unwrap(tempFileData);
File.Delete(tempFile);
var filePath = Path.Combine(_pluginConfiguration.CacheFolder, file.Hash);
await File.WriteAllBytesAsync(filePath, extractedFile, token);
var fi = new FileInfo(filePath);
Func<DateTime> RandomDayFunc()
{
DateTime start = new DateTime(1995, 1, 1);
Random gen = new Random();
int range = (DateTime.Today - start).Days;
return () => start.AddDays(gen.Next(range));
}
fi.CreationTime = RandomDayFunc().Invoke();
fi.LastAccessTime = RandomDayFunc().Invoke();
fi.LastWriteTime = RandomDayFunc().Invoke();
try
{
_ = _fileDbManager.CreateCacheEntry(filePath);
}
catch (Exception ex)
{
Logger.Warn("Issue adding file to the DB");
Logger.Warn(ex.Message);
Logger.Warn(ex.StackTrace);
}
});
Logger.Debug("Download complete, removing " + currentDownloadId);
CancelDownload(currentDownloadId);
}
public async Task PushCharacterData(CharacterCacheDto character, List<string> visibleCharacterIds)
{
if (!IsConnected || SecretKey == "-") return;
Logger.Debug("Sending Character data to service " + ApiUri);
CancelUpload();
_uploadCancellationTokenSource = new CancellationTokenSource();
var uploadToken = _uploadCancellationTokenSource.Token;
Logger.Verbose("New Token Created");
List<string> unverifiedUploadHashes = new();
foreach (var item in character.FileReplacements.SelectMany(c => c.Value.Where(f => string.IsNullOrEmpty(f.FileSwapPath)).Select(v => v.Hash).Distinct()).Distinct().ToList())
{
if (!_verifiedUploadedHashes.Contains(item))
{
unverifiedUploadHashes.Add(item);
}
}
if (unverifiedUploadHashes.Any())
{
Logger.Debug("Verifying " + unverifiedUploadHashes.Count + " files");
var filesToUpload = await _mareHub!.InvokeAsync<List<UploadFileDto>>(Api.InvokeFileSendFiles, unverifiedUploadHashes, uploadToken);
foreach (var file in filesToUpload.Where(f => !f.IsForbidden))
{
try
{
CurrentUploads.Add(new UploadFileTransfer(file)
{
Total = new FileInfo(_fileDbManager.GetFileCacheByHash(file.Hash)!.ResolvedFilepath).Length
});
}
catch (Exception ex)
{
Logger.Warn("Tried to request file " + file.Hash + " but file was not present");
Logger.Warn(ex.StackTrace!);
}
}
foreach (var file in filesToUpload.Where(c => c.IsForbidden))
{
if (ForbiddenTransfers.All(f => f.Hash != file.Hash))
{
ForbiddenTransfers.Add(new UploadFileTransfer(file)
{
LocalFile = _fileDbManager.GetFileCacheByHash(file.Hash)?.ResolvedFilepath ?? string.Empty
});
}
}
var totalSize = CurrentUploads.Sum(c => c.Total);
Logger.Debug("Compressing and uploading files");
foreach (var file in CurrentUploads.Where(f => f.CanBeTransferred && !f.IsTransferred).ToList())
{
Logger.Debug("Compressing and uploading " + file);
var data = await GetCompressedFileData(file.Hash, uploadToken);
CurrentUploads.Single(e => e.Hash == data.Item1).Total = data.Item2.Length;
await UploadFile(data.Item2, file.Hash, uploadToken);
if (!uploadToken.IsCancellationRequested) continue;
Logger.Warn("Cancel in filesToUpload loop detected");
CurrentUploads.Clear();
break;
}
if (CurrentUploads.Any())
{
var compressedSize = CurrentUploads.Sum(c => c.Total);
Logger.Debug($"Compressed {totalSize} to {compressedSize} ({(compressedSize / (double)totalSize):P2})");
}
Logger.Debug("Upload tasks complete, waiting for server to confirm");
var anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
Logger.Debug("Uploads open: " + anyUploadsOpen);
while (anyUploadsOpen && !uploadToken.IsCancellationRequested)
{
anyUploadsOpen = await _mareHub!.InvokeAsync<bool>(Api.InvokeFileIsUploadFinished, uploadToken);
await Task.Delay(TimeSpan.FromSeconds(0.5), uploadToken);
Logger.Debug("Waiting for uploads to finish");
}
foreach (var item in unverifiedUploadHashes)
{
_verifiedUploadedHashes.Add(item);
}
CurrentUploads.Clear();
}
else
{
Logger.Debug("All files already verified");
}
if (!uploadToken.IsCancellationRequested)
{
Logger.Info("Pushing character data for " + character.GetHashCode() + " to " + string.Join(", ", visibleCharacterIds));
StringBuilder sb = new StringBuilder();
foreach (var item in character.FileReplacements)
{
sb.AppendLine($"FileReplacements for {item.Key}: {item.Value.Count}");
}
foreach (var item in character.GlamourerData)
{
sb.AppendLine($"GlamourerData for {item.Key}: {!string.IsNullOrEmpty(item.Value)}");
}
Logger.Debug("Chara data contained: " + Environment.NewLine + sb.ToString());
await _mareHub!.InvokeAsync(Api.InvokeUserPushCharacterDataToVisibleClients, character, visibleCharacterIds, uploadToken);
}
else
{
Logger.Warn("=== Upload operation was cancelled ===");
}
Logger.Verbose("Upload complete for " + character.GetHashCode());
_uploadCancellationTokenSource = null;
}
private async Task<(string, byte[])> GetCompressedFileData(string fileHash, CancellationToken uploadToken)
{
var fileCache = _fileDbManager.GetFileCacheByHash(fileHash)!.ResolvedFilepath;
return (fileHash, LZ4Codec.WrapHC(await File.ReadAllBytesAsync(fileCache, uploadToken), 0,
(int)new FileInfo(fileCache).Length));
}
private async Task UploadFile(byte[] compressedFile, string fileHash, CancellationToken uploadToken)
{
if (uploadToken.IsCancellationRequested) return;
async IAsyncEnumerable<byte[]> AsyncFileData([EnumeratorCancellation] CancellationToken token)
{
var chunkSize = 1024 * 512; // 512kb
using var ms = new MemoryStream(compressedFile);
var buffer = new byte[chunkSize];
int bytesRead;
while ((bytesRead = await ms.ReadAsync(buffer, 0, chunkSize, token)) > 0 && !token.IsCancellationRequested)
{
CurrentUploads.Single(f => f.Hash == fileHash).Transferred += bytesRead;
token.ThrowIfCancellationRequested();
yield return bytesRead == chunkSize ? buffer.ToArray() : buffer.Take(bytesRead).ToArray();
}
}
await _mareHub!.SendAsync(Api.SendFileUploadFileStreamAsync, fileHash, AsyncFileData(uploadToken), uploadToken);
}
public void CancelDownload(int downloadId)
{
while (CurrentDownloads.ContainsKey(downloadId))
{
CurrentDownloads.TryRemove(downloadId, out _);
}
}
}

View File

@@ -1,44 +1,43 @@
using System.Collections.Generic;
using System.Threading.Tasks;
using MareSynchronos.API;
using MareSynchronos.Utils;
using Microsoft.AspNetCore.SignalR.Client;
namespace MareSynchronos.WebAPI
namespace MareSynchronos.WebAPI;
public partial class ApiController
{
public partial class ApiController
public async Task DeleteAccount()
{
public async Task DeleteAccount()
{
_pluginConfiguration.ClientSecret.Remove(ApiUri);
_pluginConfiguration.Save();
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles);
await _mareHub!.SendAsync(Api.SendUserDeleteAccount);
await CreateConnections();
}
public async Task<List<string>> GetOnlineCharacters()
{
return await _mareHub!.InvokeAsync<List<string>>(Api.InvokeUserGetOnlineCharacters);
}
public async Task SendPairedClientAddition(string uid)
{
if (!IsConnected || SecretKey == "-") return;
await _mareHub!.SendAsync(Api.SendUserPairedClientAddition, uid);
}
public async Task SendPairedClientPauseChange(string uid, bool paused)
{
if (!IsConnected || SecretKey == "-") return;
await _mareHub!.SendAsync(Api.SendUserPairedClientPauseChange, uid, paused);
}
public async Task SendPairedClientRemoval(string uid)
{
if (!IsConnected || SecretKey == "-") return;
await _mareHub!.SendAsync(Api.SendUserPairedClientRemoval, uid);
}
_pluginConfiguration.ClientSecret.Remove(ApiUri);
_pluginConfiguration.Save();
await _mareHub!.SendAsync(Api.SendFileDeleteAllFiles);
await _mareHub!.SendAsync(Api.SendUserDeleteAccount);
await CreateConnections();
}
public async Task<List<string>> GetOnlineCharacters()
{
return await _mareHub!.InvokeAsync<List<string>>(Api.InvokeUserGetOnlineCharacters);
}
public async Task SendPairedClientAddition(string uid)
{
if (!IsConnected || SecretKey == "-") return;
await _mareHub!.SendAsync(Api.SendUserPairedClientAddition, uid);
}
public async Task SendPairedClientPauseChange(string uid, bool paused)
{
if (!IsConnected || SecretKey == "-") return;
await _mareHub!.SendAsync(Api.SendUserPairedClientPauseChange, uid, paused);
}
public async Task SendPairedClientRemoval(string uid)
{
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,45 +3,44 @@ 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)
{
public async Task AddOrUpdateForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
{
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile);
}
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddForbiddenFile, forbiddenFile);
}
public async Task DeleteForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
{
await _mareHub!.SendAsync(Api.SendAdminDeleteForbiddenFile, forbiddenFile);
}
public async Task DeleteForbiddenFileEntry(ForbiddenFileDto forbiddenFile)
{
await _mareHub!.SendAsync(Api.SendAdminDeleteForbiddenFile, forbiddenFile);
}
public async Task AddOrUpdateBannedUserEntry(BannedUserDto bannedUser)
{
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddBannedUser, bannedUser);
}
public async Task AddOrUpdateBannedUserEntry(BannedUserDto bannedUser)
{
await _mareHub!.SendAsync(Api.SendAdminUpdateOrAddBannedUser, bannedUser);
}
public async Task DeleteBannedUserEntry(BannedUserDto bannedUser)
{
await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser);
}
public async Task DeleteBannedUserEntry(BannedUserDto bannedUser)
{
await _mareHub!.SendAsync(Api.SendAdminDeleteBannedUser, bannedUser);
}
public async Task RefreshOnlineUsers()
{
AdminOnlineUsers = await _mareHub!.InvokeAsync<List<OnlineUserDto>>(Api.InvokeAdminGetOnlineUsers);
}
public async Task RefreshOnlineUsers()
{
AdminOnlineUsers = await _mareHub!.InvokeAsync<List<OnlineUserDto>>(Api.InvokeAdminGetOnlineUsers);
}
public List<OnlineUserDto> AdminOnlineUsers { get; set; } = new List<OnlineUserDto>();
public List<OnlineUserDto> AdminOnlineUsers { get; set; } = new List<OnlineUserDto>();
public void PromoteToModerator(string onlineUserUID)
{
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, true);
}
public void PromoteToModerator(string onlineUserUID)
{
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, true);
}
public void DemoteFromModerator(string onlineUserUID)
{
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false);
}
public void DemoteFromModerator(string onlineUserUID)
{
_mareHub!.SendAsync(Api.SendAdminChangeModeratorStatus, onlineUserUID, false);
}
}

View File

@@ -4,75 +4,84 @@ 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()
{
private void UserForcedReconnectCallback()
_ = CreateConnections();
}
private void UpdateLocalClientPairsCallback(ClientPairDto dto)
{
var entry = PairedClients.SingleOrDefault(e => e.OtherUID == dto.OtherUID);
if (dto.IsRemoved)
{
_ = CreateConnections();
PairedClients.RemoveAll(p => p.OtherUID == dto.OtherUID);
return;
}
if (entry == null)
{
PairedClients.Add(dto);
return;
}
private void UpdateLocalClientPairsCallback(ClientPairDto dto)
{
var entry = PairedClients.SingleOrDefault(e => e.OtherUID == dto.OtherUID);
if (dto.IsRemoved)
{
PairedClients.RemoveAll(p => p.OtherUID == dto.OtherUID);
return;
}
if (entry == null)
{
PairedClients.Add(dto);
return;
}
entry.IsPaused = dto.IsPaused;
entry.IsPausedFromOthers = dto.IsPausedFromOthers;
entry.IsSynced = dto.IsSynced;
}
entry.IsPaused = dto.IsPaused;
entry.IsPausedFromOthers = dto.IsPausedFromOthers;
entry.IsSynced = dto.IsSynced;
private Task ReceiveCharacterDataCallback(CharacterCacheDto character, string characterHash)
{
Logger.Verbose("Received DTO for " + characterHash);
CharacterReceived?.Invoke(null, new CharacterReceivedEventArgs(characterHash, character));
return Task.CompletedTask;
}
private void UpdateOrAddBannedUserCallback(BannedUserDto obj)
{
var user = AdminBannedUsers.SingleOrDefault(b => b.CharacterHash == obj.CharacterHash);
if (user == null)
{
AdminBannedUsers.Add(obj);
}
private Task ReceiveCharacterDataCallback(CharacterCacheDto character, string characterHash)
else
{
Logger.Verbose("Received DTO for " + characterHash);
CharacterReceived?.Invoke(null, new CharacterReceivedEventArgs(characterHash, character));
return Task.CompletedTask;
}
private void UpdateOrAddBannedUserCallback(BannedUserDto obj)
{
var user = AdminBannedUsers.SingleOrDefault(b => b.CharacterHash == obj.CharacterHash);
if (user == null)
{
AdminBannedUsers.Add(obj);
}
else
{
user.Reason = obj.Reason;
}
}
private void DeleteBannedUserCallback(BannedUserDto obj)
{
AdminBannedUsers.RemoveAll(a => a.CharacterHash == obj.CharacterHash);
}
private void UpdateOrAddForbiddenFileCallback(ForbiddenFileDto obj)
{
var user = AdminForbiddenFiles.SingleOrDefault(b => b.Hash == obj.Hash);
if (user == null)
{
AdminForbiddenFiles.Add(obj);
}
else
{
user.ForbiddenBy = obj.ForbiddenBy;
}
}
private void DeleteForbiddenFileCallback(ForbiddenFileDto obj)
{
AdminForbiddenFiles.RemoveAll(f => f.Hash == obj.Hash);
user.Reason = obj.Reason;
}
}
private void DeleteBannedUserCallback(BannedUserDto obj)
{
AdminBannedUsers.RemoveAll(a => a.CharacterHash == obj.CharacterHash);
}
private void UpdateOrAddForbiddenFileCallback(ForbiddenFileDto obj)
{
var user = AdminForbiddenFiles.SingleOrDefault(b => b.Hash == obj.Hash);
if (user == null)
{
AdminForbiddenFiles.Add(obj);
}
else
{
user.ForbiddenBy = obj.ForbiddenBy;
}
}
private void DeleteForbiddenFileCallback(ForbiddenFileDto obj)
{
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
}