mirror of
https://github.com/chylex/Discord-History-Tracker.git
synced 2024-11-25 05:42:45 +01:00
Compare commits
1 Commits
031d521402
...
2875f5943b
Author | SHA1 | Date | |
---|---|---|---|
2875f5943b |
@ -1,14 +1,19 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Threading;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Server;
|
||||
using DHT.Server;
|
||||
using DHT.Server.Service;
|
||||
using DHT.Utils.Logging;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls;
|
||||
|
||||
sealed class ServerConfigurationPanelModel : BaseModel, IDisposable {
|
||||
private static readonly Log Log = Log.ForType<ServerConfigurationPanelModel>();
|
||||
|
||||
private string inputPort;
|
||||
|
||||
public string InputPort {
|
||||
@ -29,7 +34,7 @@ sealed class ServerConfigurationPanelModel : BaseModel, IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
public bool HasMadeChanges => ServerManager.Port.ToString() != InputPort || ServerManager.Token != InputToken;
|
||||
public bool HasMadeChanges => ServerConfiguration.Port.ToString() != InputPort || ServerConfiguration.Token != InputToken;
|
||||
|
||||
private bool isToggleServerButtonEnabled = true;
|
||||
|
||||
@ -38,59 +43,69 @@ sealed class ServerConfigurationPanelModel : BaseModel, IDisposable {
|
||||
set => Change(ref isToggleServerButtonEnabled, value);
|
||||
}
|
||||
|
||||
public string ToggleServerButtonText => serverManager.IsRunning ? "Stop Server" : "Start Server";
|
||||
|
||||
public event EventHandler<StatusBarModel.Status>? ServerStatusChanged;
|
||||
public string ToggleServerButtonText => server.IsRunning ? "Stop Server" : "Start Server";
|
||||
|
||||
private readonly Window window;
|
||||
private readonly ServerManager serverManager;
|
||||
private readonly ServerManager server;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public ServerConfigurationPanelModel() : this(null!, new ServerManager(State.Dummy)) {}
|
||||
public ServerConfigurationPanelModel() : this(null!, State.Dummy) {}
|
||||
|
||||
public ServerConfigurationPanelModel(Window window, ServerManager serverManager) {
|
||||
public ServerConfigurationPanelModel(Window window, State state) {
|
||||
this.window = window;
|
||||
this.serverManager = serverManager;
|
||||
this.inputPort = ServerManager.Port.ToString();
|
||||
this.inputToken = ServerManager.Token;
|
||||
}
|
||||
|
||||
public void Initialize() {
|
||||
ServerLauncher.ServerStatusChanged += ServerLauncherOnServerStatusChanged;
|
||||
this.server = state.Server;
|
||||
this.inputPort = ServerConfiguration.Port.ToString();
|
||||
this.inputToken = ServerConfiguration.Token;
|
||||
|
||||
server.StatusChanged += OnServerStatusChanged;
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
ServerLauncher.ServerStatusChanged -= ServerLauncherOnServerStatusChanged;
|
||||
server.StatusChanged -= OnServerStatusChanged;
|
||||
}
|
||||
|
||||
private void ServerLauncherOnServerStatusChanged(object? sender, EventArgs e) {
|
||||
ServerStatusChanged?.Invoke(this, serverManager.IsRunning ? StatusBarModel.Status.Ready : StatusBarModel.Status.Stopped);
|
||||
private void OnServerStatusChanged(object? sender, ServerManager.Status e) {
|
||||
Dispatcher.UIThread.InvokeAsync(UpdateServerStatus);
|
||||
}
|
||||
|
||||
private void UpdateServerStatus() {
|
||||
OnPropertyChanged(nameof(ToggleServerButtonText));
|
||||
}
|
||||
|
||||
private async Task StartServer() {
|
||||
IsToggleServerButtonEnabled = false;
|
||||
|
||||
try {
|
||||
await server.Start(ServerConfiguration.Port, ServerConfiguration.Token);
|
||||
} catch (Exception e) {
|
||||
Log.Error(e);
|
||||
await Dialog.ShowOk(window, "Internal Server Error", e.Message);
|
||||
}
|
||||
|
||||
UpdateServerStatus();
|
||||
IsToggleServerButtonEnabled = true;
|
||||
}
|
||||
|
||||
private void BeforeServerStart() {
|
||||
private async Task StopServer() {
|
||||
IsToggleServerButtonEnabled = false;
|
||||
ServerStatusChanged?.Invoke(this, StatusBarModel.Status.Starting);
|
||||
|
||||
try {
|
||||
await server.Stop();
|
||||
} catch (Exception e) {
|
||||
Log.Error(e);
|
||||
await Dialog.ShowOk(window, "Internal Server Error", e.Message);
|
||||
}
|
||||
|
||||
UpdateServerStatus();
|
||||
IsToggleServerButtonEnabled = true;
|
||||
}
|
||||
|
||||
private void StartServer() {
|
||||
BeforeServerStart();
|
||||
serverManager.Launch();
|
||||
}
|
||||
|
||||
private void StopServer() {
|
||||
IsToggleServerButtonEnabled = false;
|
||||
ServerStatusChanged?.Invoke(this, StatusBarModel.Status.Stopping);
|
||||
serverManager.Stop();
|
||||
}
|
||||
|
||||
public void OnClickToggleServerButton() {
|
||||
if (serverManager.IsRunning) {
|
||||
StopServer();
|
||||
public async Task OnClickToggleServerButton() {
|
||||
if (server.IsRunning) {
|
||||
await StopServer();
|
||||
}
|
||||
else {
|
||||
StartServer();
|
||||
await StartServer();
|
||||
}
|
||||
}
|
||||
|
||||
@ -98,19 +113,21 @@ sealed class ServerConfigurationPanelModel : BaseModel, IDisposable {
|
||||
InputToken = ServerUtils.GenerateRandomToken(20);
|
||||
}
|
||||
|
||||
public async void OnClickApplyChanges() {
|
||||
public async Task OnClickApplyChanges() {
|
||||
if (!ushort.TryParse(InputPort, out ushort port)) {
|
||||
await Dialog.ShowOk(window, "Invalid Port", "Port must be a number between 0 and 65535.");
|
||||
return;
|
||||
}
|
||||
|
||||
BeforeServerStart();
|
||||
serverManager.Relaunch(port, InputToken);
|
||||
ServerConfiguration.Port = port;
|
||||
ServerConfiguration.Token = inputToken;
|
||||
await StartServer();
|
||||
|
||||
OnPropertyChanged(nameof(HasMadeChanges));
|
||||
}
|
||||
|
||||
public void OnClickCancelChanges() {
|
||||
InputPort = ServerManager.Port.ToString();
|
||||
InputToken = ServerManager.Token;
|
||||
InputPort = ServerConfiguration.Port.ToString();
|
||||
InputToken = ServerConfiguration.Token;
|
||||
}
|
||||
}
|
||||
|
@ -40,7 +40,7 @@
|
||||
<StackPanel Orientation="Horizontal" Margin="6 3">
|
||||
<StackPanel Orientation="Vertical" Width="65">
|
||||
<TextBlock Classes="label">Status</TextBlock>
|
||||
<TextBlock FontSize="12" Margin="0 3 0 0" Text="{Binding StatusText}" />
|
||||
<TextBlock FontSize="12" Margin="0 3 0 0" Text="{Binding ServerStatusText}" />
|
||||
</StackPanel>
|
||||
<Rectangle />
|
||||
<StackPanel Orientation="Vertical">
|
||||
|
@ -1,45 +1,46 @@
|
||||
using System;
|
||||
using Avalonia.Threading;
|
||||
using DHT.Server;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Service;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Main.Controls;
|
||||
|
||||
sealed class StatusBarModel : BaseModel {
|
||||
sealed class StatusBarModel : BaseModel, IDisposable {
|
||||
public DatabaseStatistics DatabaseStatistics { get; }
|
||||
|
||||
private Status status = Status.Stopped;
|
||||
private ServerManager.Status serverStatus;
|
||||
|
||||
public Status CurrentStatus {
|
||||
get => status;
|
||||
set {
|
||||
status = value;
|
||||
OnPropertyChanged(nameof(StatusText));
|
||||
}
|
||||
}
|
||||
public string ServerStatusText => serverStatus switch {
|
||||
ServerManager.Status.Starting => "STARTING",
|
||||
ServerManager.Status.Started => "READY",
|
||||
ServerManager.Status.Stopping => "STOPPING",
|
||||
ServerManager.Status.Stopped => "STOPPED",
|
||||
_ => ""
|
||||
};
|
||||
|
||||
public string StatusText {
|
||||
get {
|
||||
return CurrentStatus switch {
|
||||
Status.Starting => "STARTING",
|
||||
Status.Ready => "READY",
|
||||
Status.Stopping => "STOPPING",
|
||||
Status.Stopped => "STOPPED",
|
||||
_ => ""
|
||||
};
|
||||
}
|
||||
}
|
||||
private readonly State state;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public StatusBarModel() : this(new DatabaseStatistics()) {}
|
||||
public StatusBarModel() : this(State.Dummy) {}
|
||||
|
||||
public StatusBarModel(DatabaseStatistics databaseStatistics) {
|
||||
this.DatabaseStatistics = databaseStatistics;
|
||||
public StatusBarModel(State state) {
|
||||
this.state = state;
|
||||
this.DatabaseStatistics = state.Db.Statistics;
|
||||
|
||||
state.Server.StatusChanged += OnServerStatusChanged;
|
||||
serverStatus = state.Server.IsRunning ? ServerManager.Status.Started : ServerManager.Status.Stopped;
|
||||
}
|
||||
|
||||
public enum Status {
|
||||
Starting,
|
||||
Ready,
|
||||
Stopping,
|
||||
Stopped
|
||||
public void Dispose() {
|
||||
state.Server.StatusChanged += OnServerStatusChanged;
|
||||
}
|
||||
|
||||
private void OnServerStatusChanged(object? sender, ServerManager.Status e) {
|
||||
Dispatcher.UIThread.InvokeAsync(() => {
|
||||
serverStatus = e;
|
||||
OnPropertyChanged(nameof(ServerStatusText));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -8,12 +8,15 @@ using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Main.Screens;
|
||||
using DHT.Desktop.Server;
|
||||
using DHT.Server;
|
||||
using DHT.Utils.Logging;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
namespace DHT.Desktop.Main;
|
||||
|
||||
sealed class MainWindowModel : BaseModel, IAsyncDisposable {
|
||||
private const string DefaultTitle = "Discord History Tracker";
|
||||
|
||||
private static readonly Log Log = Log.ForType<MainWindowModel>();
|
||||
|
||||
public string Title { get; private set; } = DefaultTitle;
|
||||
|
||||
@ -63,11 +66,11 @@ sealed class MainWindowModel : BaseModel, IAsyncDisposable {
|
||||
}
|
||||
|
||||
if (args.ServerPort != null) {
|
||||
ServerManager.Port = args.ServerPort.Value;
|
||||
ServerConfiguration.Port = args.ServerPort.Value;
|
||||
}
|
||||
|
||||
if (args.ServerToken != null) {
|
||||
ServerManager.Token = args.ServerToken;
|
||||
ServerConfiguration.Token = args.ServerToken;
|
||||
}
|
||||
}
|
||||
|
||||
@ -82,15 +85,23 @@ sealed class MainWindowModel : BaseModel, IAsyncDisposable {
|
||||
await state.DisposeAsync();
|
||||
}
|
||||
|
||||
state = welcomeScreenModel.Db == null ? null : new State(welcomeScreenModel.Db);
|
||||
|
||||
if (state == null) {
|
||||
if (welcomeScreenModel.Db == null) {
|
||||
state = null;
|
||||
Title = DefaultTitle;
|
||||
mainContentScreenModel = null;
|
||||
mainContentScreen = null;
|
||||
CurrentScreen = welcomeScreen;
|
||||
}
|
||||
else {
|
||||
state = new State(welcomeScreenModel.Db);
|
||||
|
||||
try {
|
||||
await state.Server.Start(ServerConfiguration.Port, ServerConfiguration.Token);
|
||||
} catch (Exception ex) {
|
||||
Log.Error(ex);
|
||||
await Dialog.ShowOk(window, "Internal Server Error", ex.Message);
|
||||
}
|
||||
|
||||
Title = Path.GetFileName(state.Db.Path) + " - " + DefaultTitle;
|
||||
mainContentScreenModel = new MainContentScreenModel(window, state);
|
||||
await mainContentScreenModel.Initialize();
|
||||
|
@ -2,7 +2,6 @@ using System;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Main.Controls;
|
||||
using DHT.Desktop.Server;
|
||||
using DHT.Server;
|
||||
using DHT.Utils.Models;
|
||||
|
||||
@ -15,17 +14,13 @@ sealed class AdvancedPageModel : BaseModel, IDisposable {
|
||||
private readonly State state;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public AdvancedPageModel() : this(null!, State.Dummy, new ServerManager(State.Dummy)) {}
|
||||
public AdvancedPageModel() : this(null!, State.Dummy) {}
|
||||
|
||||
public AdvancedPageModel(Window window, State state, ServerManager serverManager) {
|
||||
public AdvancedPageModel(Window window, State state) {
|
||||
this.window = window;
|
||||
this.state = state;
|
||||
|
||||
ServerConfigurationModel = new ServerConfigurationPanelModel(window, serverManager);
|
||||
}
|
||||
|
||||
public void Initialize() {
|
||||
ServerConfigurationModel.Initialize();
|
||||
ServerConfigurationModel = new ServerConfigurationPanelModel(window, state);
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
|
@ -54,7 +54,7 @@ sealed class TrackingPageModel : BaseModel {
|
||||
}
|
||||
|
||||
public async Task<bool> OnClickCopyTrackingScript() {
|
||||
string url = $"http://127.0.0.1:{ServerManager.Port}/get-tracking-script?token={HttpUtility.UrlEncode(ServerManager.Token)}";
|
||||
string url = $"http://127.0.0.1:{ServerConfiguration.Port}/get-tracking-script?token={HttpUtility.UrlEncode(ServerConfiguration.Token)}";
|
||||
string script = (await Resources.ReadTextAsync("tracker-loader.js")).Trim().Replace("{url}", url);
|
||||
|
||||
var clipboard = window.Clipboard;
|
||||
|
@ -110,7 +110,7 @@ sealed class ViewerPageModel : BaseModel, IDisposable {
|
||||
TemporaryFiles.Add(fullPath);
|
||||
|
||||
Directory.CreateDirectory(rootPath);
|
||||
await WriteViewerFile(fullPath, new LiveViewerExportStrategy(ServerManager.Port, ServerManager.Token));
|
||||
await WriteViewerFile(fullPath, new LiveViewerExportStrategy(ServerConfiguration.Port, ServerConfiguration.Token));
|
||||
|
||||
Process.Start(new ProcessStartInfo(fullPath) { UseShellExecute = true });
|
||||
}
|
||||
|
@ -1,12 +1,9 @@
|
||||
using System;
|
||||
using System.Threading.Tasks;
|
||||
using Avalonia.Controls;
|
||||
using DHT.Desktop.Dialogs.Message;
|
||||
using DHT.Desktop.Main.Controls;
|
||||
using DHT.Desktop.Main.Pages;
|
||||
using DHT.Desktop.Server;
|
||||
using DHT.Server;
|
||||
using DHT.Server.Service;
|
||||
using DHT.Utils.Logging;
|
||||
|
||||
namespace DHT.Desktop.Main.Screens;
|
||||
@ -49,18 +46,10 @@ sealed class MainContentScreenModel : IDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
private readonly Window window;
|
||||
private readonly ServerManager serverManager;
|
||||
|
||||
[Obsolete("Designer")]
|
||||
public MainContentScreenModel() : this(null!, State.Dummy) {}
|
||||
|
||||
public MainContentScreenModel(Window window, State state) {
|
||||
this.window = window;
|
||||
this.serverManager = new ServerManager(state);
|
||||
|
||||
ServerLauncher.ServerManagementExceptionCaught += ServerLauncherOnServerManagementExceptionCaught;
|
||||
|
||||
DatabasePageModel = new DatabasePageModel(window, state);
|
||||
DatabasePage = new DatabasePage { DataContext = DatabasePageModel };
|
||||
|
||||
@ -73,7 +62,7 @@ sealed class MainContentScreenModel : IDisposable {
|
||||
ViewerPageModel = new ViewerPageModel(window, state);
|
||||
ViewerPage = new ViewerPage { DataContext = ViewerPageModel };
|
||||
|
||||
AdvancedPageModel = new AdvancedPageModel(window, state, serverManager);
|
||||
AdvancedPageModel = new AdvancedPageModel(window, state);
|
||||
AdvancedPage = new AdvancedPage { DataContext = AdvancedPageModel };
|
||||
|
||||
#if DEBUG
|
||||
@ -83,37 +72,17 @@ sealed class MainContentScreenModel : IDisposable {
|
||||
DebugPage = null;
|
||||
#endif
|
||||
|
||||
StatusBarModel = new StatusBarModel(state.Db.Statistics);
|
||||
|
||||
AdvancedPageModel.ServerConfigurationModel.ServerStatusChanged += OnServerStatusChanged;
|
||||
DatabaseClosed += OnDatabaseClosed;
|
||||
|
||||
StatusBarModel.CurrentStatus = serverManager.IsRunning ? StatusBarModel.Status.Ready : StatusBarModel.Status.Stopped;
|
||||
StatusBarModel = new StatusBarModel(state);
|
||||
}
|
||||
|
||||
public async Task Initialize() {
|
||||
await TrackingPageModel.Initialize();
|
||||
AdvancedPageModel.Initialize();
|
||||
serverManager.Launch();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
ServerLauncher.ServerManagementExceptionCaught -= ServerLauncherOnServerManagementExceptionCaught;
|
||||
AttachmentsPageModel.Dispose();
|
||||
ViewerPageModel.Dispose();
|
||||
serverManager.Dispose();
|
||||
}
|
||||
|
||||
private void OnServerStatusChanged(object? sender, StatusBarModel.Status e) {
|
||||
StatusBarModel.CurrentStatus = e;
|
||||
}
|
||||
|
||||
private void OnDatabaseClosed(object? sender, EventArgs e) {
|
||||
serverManager.Stop();
|
||||
}
|
||||
|
||||
private async void ServerLauncherOnServerManagementExceptionCaught(object? sender, Exception ex) {
|
||||
Log.Error(ex);
|
||||
await Dialog.ShowOk(window, "Internal Server Error", ex.Message);
|
||||
AdvancedPageModel.Dispose();
|
||||
StatusBarModel.Dispose();
|
||||
}
|
||||
}
|
||||
|
8
app/Desktop/Server/ServerConfiguration.cs
Normal file
8
app/Desktop/Server/ServerConfiguration.cs
Normal file
@ -0,0 +1,8 @@
|
||||
using DHT.Server.Service;
|
||||
|
||||
namespace DHT.Desktop.Server;
|
||||
|
||||
static class ServerConfiguration {
|
||||
public static ushort Port { get; set; } = ServerUtils.FindAvailablePort(50000, 60000);
|
||||
public static string Token { get; set; } = ServerUtils.GenerateRandomToken(20);
|
||||
}
|
@ -1,50 +0,0 @@
|
||||
using System;
|
||||
using DHT.Server;
|
||||
using DHT.Server.Service;
|
||||
|
||||
namespace DHT.Desktop.Server;
|
||||
|
||||
sealed class ServerManager : IDisposable {
|
||||
public static ushort Port { get; set; } = ServerUtils.FindAvailablePort(50000, 60000);
|
||||
public static string Token { get; set; } = ServerUtils.GenerateRandomToken(20);
|
||||
|
||||
private static ServerManager? instance;
|
||||
|
||||
public bool IsRunning => ServerLauncher.IsRunning;
|
||||
|
||||
private readonly State state;
|
||||
|
||||
public ServerManager(State state) {
|
||||
if (state != State.Dummy) {
|
||||
if (instance != null) {
|
||||
throw new InvalidOperationException("Only one instance of ServerManager can exist at the same time!");
|
||||
}
|
||||
|
||||
instance = this;
|
||||
}
|
||||
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public void Launch() {
|
||||
ServerLauncher.Relaunch(Port, Token, state.Db);
|
||||
}
|
||||
|
||||
public void Relaunch(ushort port, string token) {
|
||||
Port = port;
|
||||
Token = token;
|
||||
Launch();
|
||||
}
|
||||
|
||||
public void Stop() {
|
||||
ServerLauncher.Stop();
|
||||
}
|
||||
|
||||
public void Dispose() {
|
||||
Stop();
|
||||
|
||||
if (instance == this) {
|
||||
instance = null;
|
||||
}
|
||||
}
|
||||
}
|
@ -1,126 +0,0 @@
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Threading;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Utils.Logging;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DHT.Server.Service;
|
||||
|
||||
public static class ServerLauncher {
|
||||
private static readonly Log Log = Log.ForType(typeof(ServerLauncher));
|
||||
|
||||
private static IWebHost? Server { get; set; } = null;
|
||||
|
||||
public static bool IsRunning { get; private set; }
|
||||
public static event EventHandler? ServerStatusChanged;
|
||||
public static event EventHandler<Exception>? ServerManagementExceptionCaught;
|
||||
|
||||
private static Thread? ManagementThread { get; set; } = null;
|
||||
private static readonly Mutex ManagementThreadLock = new();
|
||||
private static readonly BlockingCollection<IMessage> Messages = new(new ConcurrentQueue<IMessage>());
|
||||
|
||||
private static void EnqueueMessage(IMessage message) {
|
||||
ManagementThreadLock.WaitOne();
|
||||
|
||||
try {
|
||||
if (ManagementThread == null) {
|
||||
ManagementThread = new Thread(RunManagementThread) {
|
||||
Name = "DHT server management thread",
|
||||
IsBackground = true
|
||||
};
|
||||
ManagementThread.Start();
|
||||
}
|
||||
|
||||
Messages.Add(message);
|
||||
} finally {
|
||||
ManagementThreadLock.ReleaseMutex();
|
||||
}
|
||||
}
|
||||
|
||||
[SuppressMessage("ReSharper", "FunctionNeverReturns")]
|
||||
private static void RunManagementThread() {
|
||||
foreach (IMessage message in Messages.GetConsumingEnumerable()) {
|
||||
try {
|
||||
switch (message) {
|
||||
case IMessage.StartServer start:
|
||||
StopServerFromManagementThread();
|
||||
StartServerFromManagementThread(start.Port, start.Token, start.Db);
|
||||
break;
|
||||
case IMessage.StopServer:
|
||||
StopServerFromManagementThread();
|
||||
break;
|
||||
}
|
||||
} catch (Exception e) {
|
||||
ServerManagementExceptionCaught?.Invoke(null, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static void StartServerFromManagementThread(ushort port, string token, IDatabaseFile db) {
|
||||
Log.Info("Starting server on port " + port + "...");
|
||||
|
||||
void AddServices(IServiceCollection services) {
|
||||
services.AddSingleton(typeof(IDatabaseFile), db);
|
||||
services.AddSingleton(typeof(ServerParameters), new ServerParameters(port, token));
|
||||
}
|
||||
|
||||
void SetKestrelOptions(KestrelServerOptions options) {
|
||||
options.Limits.MaxRequestBodySize = null;
|
||||
options.Limits.MinResponseDataRate = null;
|
||||
options.ListenLocalhost(port, static listenOptions => listenOptions.Protocols = HttpProtocols.Http1);
|
||||
}
|
||||
|
||||
Server = new WebHostBuilder()
|
||||
.ConfigureServices(AddServices)
|
||||
.UseKestrel(SetKestrelOptions)
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
Server.Start();
|
||||
|
||||
Log.Info("Server started");
|
||||
IsRunning = true;
|
||||
ServerStatusChanged?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
|
||||
private static void StopServerFromManagementThread() {
|
||||
if (Server != null) {
|
||||
Log.Info("Stopping server...");
|
||||
Server.StopAsync().Wait();
|
||||
Server.Dispose();
|
||||
Server = null;
|
||||
|
||||
Log.Info("Server stopped");
|
||||
IsRunning = false;
|
||||
ServerStatusChanged?.Invoke(null, EventArgs.Empty);
|
||||
}
|
||||
}
|
||||
|
||||
public static void Relaunch(ushort port, string token, IDatabaseFile db) {
|
||||
EnqueueMessage(new IMessage.StartServer(port, token, db));
|
||||
}
|
||||
|
||||
public static void Stop() {
|
||||
EnqueueMessage(new IMessage.StopServer());
|
||||
}
|
||||
|
||||
private interface IMessage {
|
||||
public sealed class StartServer : IMessage {
|
||||
public ushort Port { get; }
|
||||
public string Token { get; }
|
||||
public IDatabaseFile Db { get; }
|
||||
|
||||
public StartServer(ushort port, string token, IDatabaseFile db) {
|
||||
this.Port = port;
|
||||
this.Token = token;
|
||||
this.Db = db;
|
||||
}
|
||||
}
|
||||
|
||||
public sealed class StopServer : IMessage {}
|
||||
}
|
||||
}
|
107
app/Server/Service/ServerManager.cs
Normal file
107
app/Server/Service/ServerManager.cs
Normal file
@ -0,0 +1,107 @@
|
||||
using System;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Utils.Logging;
|
||||
using Microsoft.AspNetCore.Hosting;
|
||||
using Microsoft.AspNetCore.Server.Kestrel.Core;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
|
||||
namespace DHT.Server.Service;
|
||||
|
||||
public sealed class ServerManager {
|
||||
private static readonly Log Log = Log.ForType(typeof(ServerManager));
|
||||
|
||||
private IWebHost? server;
|
||||
public bool IsRunning => server != null;
|
||||
|
||||
public event EventHandler<Status>? StatusChanged;
|
||||
|
||||
public enum Status {
|
||||
Starting,
|
||||
Started,
|
||||
Stopping,
|
||||
Stopped
|
||||
}
|
||||
|
||||
private readonly IDatabaseFile db;
|
||||
private readonly SemaphoreSlim semaphore = new (1, 1);
|
||||
|
||||
internal ServerManager(IDatabaseFile db) {
|
||||
this.db = db;
|
||||
}
|
||||
|
||||
public async Task Start(ushort port, string token) {
|
||||
await semaphore.WaitAsync();
|
||||
try {
|
||||
await StartInternal(port, token);
|
||||
} finally {
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
public async Task Stop() {
|
||||
await semaphore.WaitAsync();
|
||||
try {
|
||||
await StopInternal();
|
||||
} finally {
|
||||
semaphore.Release();
|
||||
}
|
||||
}
|
||||
|
||||
private async Task StartInternal(ushort port, string token) {
|
||||
await StopInternal();
|
||||
|
||||
StatusChanged?.Invoke(this, Status.Starting);
|
||||
|
||||
void AddServices(IServiceCollection services) {
|
||||
services.AddSingleton(typeof(IDatabaseFile), db);
|
||||
services.AddSingleton(typeof(ServerParameters), new ServerParameters(port, token));
|
||||
}
|
||||
|
||||
void SetKestrelOptions(KestrelServerOptions options) {
|
||||
options.Limits.MaxRequestBodySize = null;
|
||||
options.Limits.MinResponseDataRate = null;
|
||||
options.ListenLocalhost(port, static listenOptions => listenOptions.Protocols = HttpProtocols.Http1);
|
||||
}
|
||||
|
||||
var newServer = new WebHostBuilder()
|
||||
.ConfigureServices(AddServices)
|
||||
.UseKestrel(SetKestrelOptions)
|
||||
.UseStartup<Startup>()
|
||||
.Build();
|
||||
|
||||
Log.Info("Starting server on port " + port + "...");
|
||||
|
||||
try {
|
||||
await newServer.StartAsync();
|
||||
} catch (Exception) {
|
||||
Log.Error("Server could not start");
|
||||
StatusChanged?.Invoke(this, Status.Stopped);
|
||||
throw;
|
||||
}
|
||||
|
||||
Log.Info("Server started");
|
||||
|
||||
server = newServer;
|
||||
|
||||
StatusChanged?.Invoke(this, Status.Started);
|
||||
}
|
||||
|
||||
private async Task StopInternal() {
|
||||
if (server == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
StatusChanged?.Invoke(this, Status.Stopping);
|
||||
|
||||
Log.Info("Stopping server...");
|
||||
await server.StopAsync();
|
||||
Log.Info("Server stopped");
|
||||
|
||||
server.Dispose();
|
||||
server = null;
|
||||
|
||||
StatusChanged?.Invoke(this, Status.Stopped);
|
||||
}
|
||||
}
|
@ -2,6 +2,7 @@ using System;
|
||||
using System.Threading.Tasks;
|
||||
using DHT.Server.Database;
|
||||
using DHT.Server.Download;
|
||||
using DHT.Server.Service;
|
||||
|
||||
namespace DHT.Server;
|
||||
|
||||
@ -10,10 +11,12 @@ public sealed class State : IAsyncDisposable {
|
||||
|
||||
public IDatabaseFile Db { get; }
|
||||
public Downloader Downloader { get; }
|
||||
public ServerManager Server { get; }
|
||||
|
||||
public State(IDatabaseFile db) {
|
||||
Db = db;
|
||||
Downloader = new Downloader(db);
|
||||
Server = new ServerManager(db);
|
||||
}
|
||||
|
||||
public async ValueTask DisposeAsync() {
|
||||
|
Loading…
Reference in New Issue
Block a user