Skip to content

Commit ef7b47f

Browse files
committed
[Shell] Added option to start/stop world/auth server from within the editor
1 parent b4d1c78 commit ef7b47f

15 files changed

+344
-2
lines changed

WoWDatabaseEditor.Common/WDE.Common/Services/Processes/IProcessService.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public interface IProcess
1010
{
1111
bool IsRunning { get; }
1212
void Kill();
13+
event Action<int>? OnExit;
1314
}
1415

1516
[UniqueProvider]
504 Bytes
Loading
533 Bytes
Loading
565 Bytes
Loading
624 Bytes
Loading

WoWDatabaseEditor/Managers/StatusBar.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
using WDE.Module.Attributes;
1313
using WDE.MVVM;
1414
using WoWDatabaseEditorCore.Services.ProblemsTool;
15+
using WoWDatabaseEditorCore.Services.ServerExecutable;
1516
using WoWDatabaseEditorCore.ViewModels;
1617

1718
namespace WoWDatabaseEditorCore.Managers
@@ -23,20 +24,25 @@ public partial class StatusBar : ObservableBase, IStatusBar
2324
private readonly TasksViewModel tasksViewModel;
2425
private readonly IMainThread mainThread;
2526
private readonly IPersonalGuidRangeService guidRangeService;
27+
private readonly IServerExecutableService serverExecutableService;
2628
private readonly Lazy<IClipboardService> clipboardService;
2729
private readonly Lazy<IMessageBoxService> messageBoxService;
2830

31+
public IServerExecutableService ServerExecutableService => serverExecutableService;
32+
2933
public StatusBar(Lazy<IDocumentManager> documentManager,
3034
TasksViewModel tasksViewModel,
3135
IEventAggregator eventAggregator,
3236
IMainThread mainThread,
3337
IPersonalGuidRangeService guidRangeService,
38+
IServerExecutableService serverExecutableService,
3439
Lazy<IClipboardService> clipboardService,
3540
Lazy<IMessageBoxService> messageBoxService)
3641
{
3742
this.tasksViewModel = tasksViewModel;
3843
this.mainThread = mainThread;
3944
this.guidRangeService = guidRangeService;
45+
this.serverExecutableService = serverExecutableService;
4046
this.clipboardService = clipboardService;
4147
this.messageBoxService = messageBoxService;
4248

WoWDatabaseEditor/Services/Processes/ProcessService.cs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,13 @@ public class ProcessData : IProcess
1919
public ProcessData(Process process)
2020
{
2121
this.process = process;
22+
process.EnableRaisingEvents = true;
23+
process.Exited += ProcessOnExited;
24+
}
25+
26+
private void ProcessOnExited(object? sender, EventArgs e)
27+
{
28+
OnExit?.Invoke(process.ExitCode);
2229
}
2330

2431
public bool IsRunning => !process.HasExited;
@@ -30,6 +37,8 @@ public void Kill()
3037
process.Kill();
3138
}
3239
}
40+
41+
public event Action<int>? OnExit;
3342
}
3443

3544
public IProcess RunAndForget(string path, string arguments, string? workingDirectory, bool noWindow,
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
using WDE.Module.Attributes;
2+
3+
namespace WoWDatabaseEditorCore.Services.ServerExecutable;
4+
5+
[UniqueProvider]
6+
public interface IServerExecutableConfiguration
7+
{
8+
string? WorldServerPath { get; }
9+
string? AuthServerPath { get; }
10+
void Update(string? worldServerPath, string? authServerPath);
11+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
using AsyncAwaitBestPractices.MVVM;
2+
3+
namespace WoWDatabaseEditorCore.Services.ServerExecutable;
4+
5+
public interface IServerExecutableService
6+
{
7+
IAsyncCommand ToggleWorldServer { get; }
8+
bool IsWorldServerRunning { get; }
9+
10+
IAsyncCommand ToggleAuthServer { get; }
11+
bool IsAuthServerRunning { get; }
12+
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using WDE.Common.Services;
2+
using WDE.Module.Attributes;
3+
4+
namespace WoWDatabaseEditorCore.Services.ServerExecutable;
5+
6+
[SingleInstance]
7+
[AutoRegister]
8+
public class ServerExecutableConfiguration : IServerExecutableConfiguration
9+
{
10+
private readonly IUserSettings userSettings;
11+
public string? WorldServerPath { get; set; }
12+
public string? AuthServerPath { get; set; }
13+
14+
public ServerExecutableConfiguration(IUserSettings userSettings)
15+
{
16+
this.userSettings = userSettings;
17+
var data = userSettings.Get<Data>();
18+
WorldServerPath = data.WorldServerPath;
19+
AuthServerPath = data.AuthServerPath;
20+
}
21+
22+
public void Update(string? worldServerPath, string? authServerPath)
23+
{
24+
worldServerPath = string.IsNullOrWhiteSpace(worldServerPath) ? null : worldServerPath;
25+
authServerPath = string.IsNullOrWhiteSpace(authServerPath) ? null : authServerPath;
26+
WorldServerPath = worldServerPath;
27+
AuthServerPath = authServerPath;
28+
userSettings.Update(new Data(){WorldServerPath = worldServerPath, AuthServerPath = authServerPath});
29+
}
30+
31+
private struct Data : ISettings
32+
{
33+
public string? WorldServerPath { get; set; }
34+
public string? AuthServerPath { get; set; }
35+
}
36+
}
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
using System.IO;
2+
using System.Runtime.InteropServices;
3+
using System.Windows.Input;
4+
using Prism.Commands;
5+
using PropertyChanged.SourceGenerator;
6+
using WDE.Common;
7+
using WDE.Common.Managers;
8+
using WDE.Common.Utils;
9+
using WDE.Module.Attributes;
10+
using WDE.MVVM;
11+
12+
namespace WoWDatabaseEditorCore.Services.ServerExecutable;
13+
14+
[AutoRegister]
15+
public partial class ServerExecutableConfigurationPanelViewModel : ObservableBase, IConfigurable
16+
{
17+
private readonly IWindowManager windowManager;
18+
private readonly IServerExecutableConfiguration configuration;
19+
public ICommand Save { get; }
20+
public string Name => "World server executable";
21+
public string? ShortDescription => "You can configure your world and auth server paths for easy start/stop button access in the statusbar";
22+
public bool IsRestartRequired => false;
23+
public ConfigurableGroup Group => ConfigurableGroup.Advanced;
24+
25+
public bool IsModified => worldServerPath != configuration.WorldServerPath || authServerPath != configuration.AuthServerPath;
26+
[Notify] [AlsoNotify(nameof(IsModified))] private string? worldServerPath;
27+
[Notify] [AlsoNotify(nameof(IsModified))] private string? authServerPath;
28+
29+
public ICommand PickWorldPath { get; }
30+
public ICommand PickAuthPath { get; }
31+
32+
public ServerExecutableConfigurationPanelViewModel(IWindowManager windowManager,
33+
IServerExecutableConfiguration configuration)
34+
{
35+
this.windowManager = windowManager;
36+
this.configuration = configuration;
37+
worldServerPath = configuration.WorldServerPath;
38+
authServerPath = configuration.AuthServerPath;
39+
Save = new DelegateCommand(() =>
40+
{
41+
configuration.Update(worldServerPath, authServerPath);
42+
RaisePropertyChanged(nameof(IsModified));
43+
});
44+
45+
string filter = "All files|*";
46+
if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
47+
filter = "Windows executable|exe|All files|*";
48+
49+
PickWorldPath = new AsyncAutoCommand(async () =>
50+
{
51+
var path = await windowManager.ShowOpenFileDialog(filter, File.Exists(WorldServerPath) ? Directory.GetParent(WorldServerPath)?.FullName : null);
52+
if (path != null && File.Exists(path))
53+
WorldServerPath = path;
54+
});
55+
56+
PickAuthPath = new AsyncAutoCommand(async () =>
57+
{
58+
var path = await windowManager.ShowOpenFileDialog(filter, File.Exists(AuthServerPath) ? Directory.GetParent(AuthServerPath)?.FullName : null);
59+
if (path != null && File.Exists(path))
60+
AuthServerPath = path;
61+
});
62+
}
63+
}
Lines changed: 119 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,119 @@
1+
using System;
2+
using System.IO;
3+
using System.Threading.Tasks;
4+
using AsyncAwaitBestPractices.MVVM;
5+
using PropertyChanged.SourceGenerator;
6+
using WDE.Common.Managers;
7+
using WDE.Common.Services.MessageBox;
8+
using WDE.Common.Services.Processes;
9+
using WDE.Module.Attributes;
10+
using WDE.MVVM;
11+
12+
namespace WoWDatabaseEditorCore.Services.ServerExecutable;
13+
14+
[AutoRegister]
15+
[SingleInstance]
16+
public partial class ServerExecutableService : ObservableBase, IServerExecutableService
17+
{
18+
private readonly IProcessService processService;
19+
private readonly IMessageBoxService messageBoxService;
20+
private readonly IServerExecutableConfiguration configuration;
21+
private readonly Lazy<IStatusBar> statusBar;
22+
[Notify] private bool isWorldServerRunning;
23+
[Notify] private bool isAuthServerRunning;
24+
public IAsyncCommand ToggleWorldServer { get; }
25+
public IAsyncCommand ToggleAuthServer { get; }
26+
27+
private IProcess? worldProcess;
28+
private IProcess? authProcess;
29+
30+
public ServerExecutableService(IProcessService processService,
31+
IMessageBoxService messageBoxService,
32+
IServerExecutableConfiguration configuration,
33+
Lazy<IStatusBar> statusBar)
34+
{
35+
this.processService = processService;
36+
this.messageBoxService = messageBoxService;
37+
this.configuration = configuration;
38+
this.statusBar = statusBar;
39+
ToggleWorldServer = new AsyncCommand(async () =>
40+
{
41+
if (string.IsNullOrWhiteSpace(configuration.WorldServerPath) ||
42+
!File.Exists(configuration.WorldServerPath))
43+
{
44+
await ShowConfigureDialog();
45+
return;
46+
}
47+
48+
if (worldProcess != null && worldProcess.IsRunning)
49+
{
50+
worldProcess.Kill();
51+
IsWorldServerRunning = false;
52+
worldProcess = null;
53+
}
54+
else
55+
{
56+
worldProcess = processService.RunAndForget(
57+
configuration.WorldServerPath,
58+
"", Directory.GetParent(configuration.WorldServerPath)?.FullName, true);
59+
worldProcess.OnExit += WorldProcessOnOnExit;
60+
IsWorldServerRunning = true;
61+
}
62+
});
63+
64+
ToggleAuthServer = new AsyncCommand(async () =>
65+
{
66+
if (string.IsNullOrWhiteSpace(configuration.AuthServerPath) ||
67+
!File.Exists(configuration.AuthServerPath))
68+
{
69+
await ShowConfigureDialog();
70+
return;
71+
}
72+
73+
if (authProcess != null && authProcess.IsRunning)
74+
{
75+
authProcess.Kill();
76+
IsAuthServerRunning = false;
77+
authProcess = null;
78+
}
79+
else
80+
{
81+
authProcess = processService.RunAndForget(
82+
configuration.AuthServerPath,
83+
"", Directory.GetParent(configuration.AuthServerPath)?.FullName, true);
84+
authProcess.OnExit += AuthProcessOnOnExit;
85+
IsAuthServerRunning = true;
86+
}
87+
});
88+
}
89+
90+
private Task ShowConfigureDialog()
91+
{
92+
return messageBoxService.ShowDialog(new MessageBoxFactory<bool>()
93+
.SetTitle("Configuration error")
94+
.SetMainInstruction("Setup server path first")
95+
.SetContent("In order to use quick executable start, configure the server paths in the settings")
96+
.WithOkButton(true)
97+
.Build());
98+
}
99+
100+
private void AuthProcessOnOnExit(int code)
101+
{
102+
if (authProcess != null)
103+
authProcess.OnExit -= AuthProcessOnOnExit;
104+
if (code != 0)
105+
statusBar.Value.PublishNotification(new PlainNotification(NotificationType.Warning, "Auth server exited with code " + code));
106+
IsAuthServerRunning = false;
107+
authProcess = null;
108+
}
109+
110+
private void WorldProcessOnOnExit(int code)
111+
{
112+
if (worldProcess != null)
113+
worldProcess.OnExit -= WorldProcessOnOnExit;
114+
if (code != 0)
115+
statusBar.Value.PublishNotification(new PlainNotification(NotificationType.Warning, "World server exited with code " + code));
116+
IsWorldServerRunning = false;
117+
worldProcess = null;
118+
}
119+
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<UserControl xmlns="https://github.com/avaloniaui"
2+
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
3+
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
4+
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
5+
xmlns:serverExecutable="clr-namespace:WoWDatabaseEditorCore.Services.ServerExecutable;assembly=WoWDatabaseEditorCore"
6+
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
7+
x:DataType="serverExecutable:ServerExecutableConfigurationPanelViewModel"
8+
x:Class="WoWDatabaseEditorCore.Avalonia.Services.ServerExecutable.ServerExecutableConfigurationPanelView">
9+
<Grid RowDefinitions="Auto,5,Auto" ColumnDefinitions="Auto,5,*,5,Auto">
10+
<TextBlock VerticalAlignment="Center">World server:</TextBlock>
11+
<TextBox Text="{CompiledBinding WorldServerPath}"
12+
Grid.Row="0" Grid.Column="2" />
13+
<Button Grid.Row="0" Grid.Column="4" Command="{CompiledBinding PickWorldPath}" Content="..." />
14+
15+
<TextBlock VerticalAlignment="Center" Grid.Row="2">Auth server:</TextBlock>
16+
<TextBox Text="{CompiledBinding AuthServerPath}"
17+
Grid.Row="2" Grid.Column="2" />
18+
<Button Grid.Row="2" Grid.Column="4" Command="{CompiledBinding PickAuthPath}" Content="..." />
19+
</Grid>
20+
</UserControl>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
using Avalonia;
2+
using Avalonia.Controls;
3+
using Avalonia.Markup.Xaml;
4+
5+
namespace WoWDatabaseEditorCore.Avalonia.Services.ServerExecutable;
6+
7+
public class ServerExecutableConfigurationPanelView : UserControl
8+
{
9+
public ServerExecutableConfigurationPanelView()
10+
{
11+
InitializeComponent();
12+
}
13+
14+
private void InitializeComponent()
15+
{
16+
AvaloniaXamlLoader.Load(this);
17+
}
18+
}

0 commit comments

Comments
 (0)