Skip to content

Commit c8dac37

Browse files
Merge pull request #296 from Shazbot/ipc
Will fix
2 parents 7ae6a7c + 6033747 commit c8dac37

25 files changed

Lines changed: 1194 additions & 14 deletions

‎AssetEditor/App.xaml.cs‎

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
using System.Windows;
55
using System.Windows.Threading;
66
using AssetEditor.Services;
7+
using AssetEditor.Services.Ipc;
78
using AssetEditor.UiCommands;
89
using AssetEditor.ViewModels;
910
using AssetEditor.Views;
@@ -23,6 +24,7 @@ namespace AssetEditor
2324
public partial class App : Application, IAssetEditorMain
2425
{
2526
IServiceProvider _serviceProvider;
27+
AssetEditorIpcServer _ipcServer;
2628
public IServiceProvider ServiceProvider { get => _serviceProvider; }
2729

2830
protected override void OnStartup(StartupEventArgs e)
@@ -66,6 +68,8 @@ protected override void OnStartup(StartupEventArgs e)
6668

6769
ShowMainWindow();
6870

71+
_ipcServer = _serviceProvider.GetRequiredService<AssetEditorIpcServer>();
72+
_ipcServer.Start();
6973
_ = CheckVersion(uiCommandFactory);
7074
}
7175

@@ -99,6 +103,7 @@ void ShowMainWindow()
99103
ThemesController.SetTheme(applicationSettingsService.CurrentSettings.Theme);
100104

101105
var mainWindow = _serviceProvider.GetRequiredService<MainWindow>();
106+
MainWindow = mainWindow;
102107
mainWindow.DataContext = _serviceProvider.GetRequiredService<MainViewModel>();
103108
mainWindow.Closed += OnMainWindowClosed;
104109
mainWindow.Show();
@@ -111,13 +116,24 @@ void ShowMainWindow()
111116
SystemCommands.MaximizeWindow(mainWindow);
112117
}
113118

114-
private void OnMainWindowClosed(object sender, EventArgs e)
119+
private void OnMainWindowClosed(object sender, EventArgs e)
115120
{
121+
_ipcServer?.Dispose();
122+
_ipcServer = null;
123+
116124
foreach (Window window in Current.Windows)
117125
window.Close();
118126
Shutdown();
119127
}
120128

129+
protected override void OnExit(ExitEventArgs e)
130+
{
131+
_ipcServer?.Dispose();
132+
_ipcServer = null;
133+
base.OnExit(e);
134+
}
135+
136+
121137
private static async Task CheckVersion(IUiCommandFactory uiCommandFactory)
122138
{
123139
var newerReleases = await VersionChecker.GetNewerReleases();

‎AssetEditor/DependencyInjectionContainer.cs‎

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
using AssetEditor.Services;
2+
using AssetEditor.Services.Ipc;
23
using AssetEditor.UiCommands;
34
using AssetEditor.ViewModels;
45
using AssetEditor.Views;
@@ -30,6 +31,12 @@ public override void Register(IServiceCollection serviceCollection)
3031
serviceCollection.AddTransient<PrintScopesCommand>();
3132
serviceCollection.AddTransient<OpenEditorCommand>();
3233
serviceCollection.AddTransient<TogglePackFileExplorerCommand>();
34+
serviceCollection.AddTransient<IExternalPackFileLookup, ExternalPackFileLookup>();
35+
serviceCollection.AddTransient<IExternalPackLoader, ExternalPackLoader>();
36+
serviceCollection.AddTransient<IIpcUserNotifier, IpcUserNotifier>();
37+
serviceCollection.AddTransient<IExternalFileOpenExecutor, ExternalFileOpenExecutor>();
38+
serviceCollection.AddTransient<IIpcRequestHandler, IpcRequestHandler>();
39+
serviceCollection.AddSingleton<AssetEditorIpcServer>();
3340

3441
serviceCollection.AddTransient<SettingsWindow>();
3542
serviceCollection.AddTransient<SettingsViewModel>();
Lines changed: 212 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,212 @@
1+
using System;
2+
using System.IO;
3+
using System.IO.Pipes;
4+
using System.Text;
5+
using System.Text.Json;
6+
using System.Text.Json.Serialization;
7+
using System.Threading;
8+
using System.Threading.Tasks;
9+
using Microsoft.Extensions.DependencyInjection;
10+
using Serilog;
11+
using Shared.Core.ErrorHandling;
12+
13+
namespace AssetEditor.Services.Ipc
14+
{
15+
public class AssetEditorIpcServer : IDisposable
16+
{
17+
public const string PipeName = "TheAssetEditor.Ipc";
18+
19+
private static readonly JsonSerializerOptions SerializerOptions = new()
20+
{
21+
PropertyNameCaseInsensitive = true,
22+
PropertyNamingPolicy = JsonNamingPolicy.CamelCase,
23+
DefaultIgnoreCondition = JsonIgnoreCondition.WhenWritingNull
24+
};
25+
26+
private readonly ILogger _logger = Logging.Create<AssetEditorIpcServer>();
27+
private readonly IServiceScopeFactory _scopeFactory;
28+
private readonly object _syncLock = new();
29+
30+
private CancellationTokenSource _cancellationTokenSource;
31+
private Task _serverTask;
32+
private NamedPipeServerStream _activePipe;
33+
private bool _disposed;
34+
35+
public AssetEditorIpcServer(IServiceScopeFactory scopeFactory)
36+
{
37+
_scopeFactory = scopeFactory;
38+
}
39+
40+
public void Start()
41+
{
42+
lock (_syncLock)
43+
{
44+
if (_disposed)
45+
throw new ObjectDisposedException(nameof(AssetEditorIpcServer));
46+
47+
if (_serverTask != null)
48+
return;
49+
50+
_cancellationTokenSource = new CancellationTokenSource();
51+
_serverTask = Task.Run(() => RunServerLoopAsync(_cancellationTokenSource.Token));
52+
}
53+
}
54+
55+
private async Task RunServerLoopAsync(CancellationToken cancellationToken)
56+
{
57+
_logger.Here().Information($"Starting IPC named pipe server on {PipeName}");
58+
59+
while (cancellationToken.IsCancellationRequested == false)
60+
{
61+
NamedPipeServerStream pipe = null;
62+
try
63+
{
64+
pipe = new NamedPipeServerStream(PipeName, PipeDirection.InOut, 1, PipeTransmissionMode.Byte, PipeOptions.Asynchronous);
65+
SetActivePipe(pipe);
66+
67+
await pipe.WaitForConnectionAsync(cancellationToken);
68+
69+
var response = await ProcessRequestAsync(pipe, cancellationToken);
70+
await WriteResponseAsync(pipe, response);
71+
}
72+
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
73+
{
74+
break;
75+
}
76+
catch (ObjectDisposedException) when (cancellationToken.IsCancellationRequested)
77+
{
78+
break;
79+
}
80+
catch (Exception ex)
81+
{
82+
_logger.Here().Error(ex, "Unhandled exception in IPC server loop");
83+
}
84+
finally
85+
{
86+
ClearActivePipe(pipe);
87+
pipe?.Dispose();
88+
}
89+
}
90+
91+
_logger.Here().Information("IPC named pipe server stopped");
92+
}
93+
94+
private async Task<IpcResponse> ProcessRequestAsync(NamedPipeServerStream pipe, CancellationToken cancellationToken)
95+
{
96+
using var reader = new StreamReader(pipe, new UTF8Encoding(false), detectEncodingFromByteOrderMarks: false, bufferSize: 1024, leaveOpen: true);
97+
var line = await reader.ReadLineAsync();
98+
99+
if (string.IsNullOrWhiteSpace(line))
100+
return IpcResponse.Failure("Empty request");
101+
102+
IpcRequest request;
103+
try
104+
{
105+
request = JsonSerializer.Deserialize<IpcRequest>(line, SerializerOptions);
106+
}
107+
catch (JsonException)
108+
{
109+
return IpcResponse.Failure("Invalid JSON");
110+
}
111+
112+
if (request == null)
113+
return IpcResponse.Failure("Invalid JSON");
114+
115+
using var scope = _scopeFactory.CreateScope();
116+
var handler = scope.ServiceProvider.GetRequiredService<IIpcRequestHandler>();
117+
118+
try
119+
{
120+
return await handler.HandleAsync(request, cancellationToken);
121+
}
122+
catch (OperationCanceledException) when (cancellationToken.IsCancellationRequested)
123+
{
124+
return IpcResponse.Failure("Canceled");
125+
}
126+
catch (Exception ex)
127+
{
128+
_logger.Here().Error(ex, "IPC request handling failed");
129+
return IpcResponse.Failure("Internal server error");
130+
}
131+
}
132+
133+
private static async Task WriteResponseAsync(NamedPipeServerStream pipe, IpcResponse response)
134+
{
135+
using var writer = new StreamWriter(pipe, new UTF8Encoding(false), bufferSize: 1024, leaveOpen: true)
136+
{
137+
AutoFlush = true
138+
};
139+
140+
var json = JsonSerializer.Serialize(response, SerializerOptions);
141+
await writer.WriteLineAsync(json);
142+
}
143+
144+
private void SetActivePipe(NamedPipeServerStream pipe)
145+
{
146+
lock (_syncLock)
147+
{
148+
_activePipe = pipe;
149+
}
150+
}
151+
152+
private void ClearActivePipe(NamedPipeServerStream pipe)
153+
{
154+
lock (_syncLock)
155+
{
156+
if (ReferenceEquals(_activePipe, pipe))
157+
_activePipe = null;
158+
}
159+
}
160+
161+
public void Dispose()
162+
{
163+
CancellationTokenSource cancellationTokenSource;
164+
Task serverTask;
165+
NamedPipeServerStream activePipe;
166+
167+
lock (_syncLock)
168+
{
169+
if (_disposed)
170+
return;
171+
172+
_disposed = true;
173+
cancellationTokenSource = _cancellationTokenSource;
174+
serverTask = _serverTask;
175+
activePipe = _activePipe;
176+
177+
_cancellationTokenSource = null;
178+
_serverTask = null;
179+
_activePipe = null;
180+
}
181+
182+
try
183+
{
184+
cancellationTokenSource?.Cancel();
185+
}
186+
catch
187+
{
188+
}
189+
190+
try
191+
{
192+
activePipe?.Dispose();
193+
}
194+
catch
195+
{
196+
}
197+
198+
if (serverTask != null)
199+
{
200+
try
201+
{
202+
_ = serverTask.Wait(TimeSpan.FromSeconds(2));
203+
}
204+
catch
205+
{
206+
}
207+
}
208+
209+
cancellationTokenSource?.Dispose();
210+
}
211+
}
212+
}

0 commit comments

Comments
 (0)