更新客户端渲染,更新了壳

This commit is contained in:
QWQLwToo
2026-07-06 23:05:40 +08:00
parent e7dd87bf7e
commit 31d778710b
1311 changed files with 172662 additions and 1582 deletions
@@ -0,0 +1,259 @@
using System.Collections.Concurrent;
using System.Diagnostics;
using YMhut.Box.Core.App;
using YMhut.Box.Core.Logging;
using YMhut.Box.Core.Platform;
using YMhut.Box.Core.Plugins;
using YMhut.Box.Core.Plugins.Runtime;
namespace YMhut.Box.WinUI.Services;
public sealed class TauriPluginProcessService(AppPaths paths, ILogService? logService = null) : IPluginRuntimeLauncher, IDisposable
{
private readonly ConcurrentDictionary<string, PluginRuntimeSession> _sessions = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, Process> _processes = new(StringComparer.OrdinalIgnoreCase);
private readonly ConcurrentDictionary<string, List<ShellOutputEvent>> _output = new(StringComparer.OrdinalIgnoreCase);
private long _sequence;
private bool _disposed;
public async Task<PluginRuntimeSession> LaunchAsync(PluginRuntimeLaunchRequest request, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
var sessionId = Guid.NewGuid().ToString("N");
var startedAt = DateTimeOffset.Now;
var host = ResolveHostExecutable();
var session = new PluginRuntimeSession(
sessionId,
request.PluginId,
request.SurfaceId,
request.RuntimeKind,
null,
PluginRuntimeSessionStatus.Starting,
startedAt);
_sessions[sessionId] = session;
_output[sessionId] = [];
if (host is null)
{
session = session with
{
Status = PluginRuntimeSessionStatus.Failed,
EndedAt = DateTimeOffset.Now,
LastError = "Tauri plugin host executable was not found."
};
_sessions[sessionId] = session;
await WriteLogAsync("Error", request.PluginId, "Tauri plugin host missing", session.LastError, cancellationToken).ConfigureAwait(false);
return session;
}
var startInfo = new ProcessStartInfo
{
FileName = host,
WorkingDirectory = Path.GetDirectoryName(host) ?? AppContext.BaseDirectory,
UseShellExecute = false,
RedirectStandardOutput = true,
RedirectStandardError = true,
CreateNoWindow = false
};
startInfo.ArgumentList.Add("--session");
startInfo.ArgumentList.Add(sessionId);
startInfo.ArgumentList.Add("--plugin-id");
startInfo.ArgumentList.Add(request.PluginId);
startInfo.ArgumentList.Add("--surface-id");
startInfo.ArgumentList.Add(request.SurfaceId);
startInfo.ArgumentList.Add("--runtime-kind");
startInfo.ArgumentList.Add(request.RuntimeKind.ToString());
startInfo.ArgumentList.Add("--plugin-root");
startInfo.ArgumentList.Add(request.PluginRoot);
startInfo.ArgumentList.Add("--manifest");
startInfo.ArgumentList.Add(request.ManifestPath);
startInfo.ArgumentList.Add("--entry");
startInfo.ArgumentList.Add(request.Entry);
if (!string.IsNullOrWhiteSpace(request.CommandId))
{
startInfo.ArgumentList.Add("--command");
startInfo.ArgumentList.Add(request.CommandId);
}
try
{
var process = Process.Start(startInfo) ?? throw new InvalidOperationException("Unable to start Tauri plugin host.");
process.EnableRaisingEvents = true;
process.OutputDataReceived += (_, args) => CaptureOutput(sessionId, ShellOutputStream.Stdout, args.Data);
process.ErrorDataReceived += (_, args) => CaptureOutput(sessionId, ShellOutputStream.Stderr, args.Data);
process.Exited += (_, _) => CompleteSession(sessionId, process);
process.BeginOutputReadLine();
process.BeginErrorReadLine();
_processes[sessionId] = process;
session = session with
{
ProcessId = process.Id,
Status = PluginRuntimeSessionStatus.Running
};
_sessions[sessionId] = session;
await WriteLogAsync("Information", request.PluginId, "Tauri plugin host launched", $"session={sessionId}; pid={process.Id}", cancellationToken).ConfigureAwait(false);
return session;
}
catch (Exception exception)
{
session = session with
{
Status = PluginRuntimeSessionStatus.Failed,
EndedAt = DateTimeOffset.Now,
LastError = exception.Message
};
_sessions[sessionId] = session;
await WriteLogAsync("Error", request.PluginId, "Tauri plugin host launch failed", exception.Message, cancellationToken).ConfigureAwait(false);
return session;
}
}
public async Task StopAsync(string sessionId, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (_processes.TryRemove(sessionId, out var process))
{
try
{
if (!process.HasExited)
{
process.Kill(entireProcessTree: true);
}
}
catch (Exception exception)
{
CaptureOutput(sessionId, ShellOutputStream.System, $"Stop failed: {exception.Message}");
}
finally
{
process.Dispose();
}
}
if (_sessions.TryGetValue(sessionId, out var session))
{
_sessions[sessionId] = session with
{
Status = PluginRuntimeSessionStatus.Cancelled,
EndedAt = DateTimeOffset.Now
};
await WriteLogAsync("Information", session.PluginId, "Tauri plugin host stopped", $"session={sessionId}", cancellationToken).ConfigureAwait(false);
}
}
public Task<PluginRuntimeSession?> GetSessionAsync(string sessionId, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
_sessions.TryGetValue(sessionId, out var session);
return Task.FromResult(session);
}
public Task<IReadOnlyList<ShellOutputEvent>> ReadRecentOutputAsync(string sessionId, int take = 200, CancellationToken cancellationToken = default)
{
cancellationToken.ThrowIfCancellationRequested();
if (!_output.TryGetValue(sessionId, out var lines))
{
return Task.FromResult<IReadOnlyList<ShellOutputEvent>>([]);
}
lock (lines)
{
return Task.FromResult<IReadOnlyList<ShellOutputEvent>>(lines.TakeLast(Math.Max(0, take)).ToArray());
}
}
public void Dispose()
{
if (_disposed)
{
return;
}
_disposed = true;
foreach (var sessionId in _processes.Keys.ToArray())
{
try
{
StopAsync(sessionId).GetAwaiter().GetResult();
}
catch
{
}
}
}
private void CompleteSession(string sessionId, Process process)
{
try
{
int? exitCode = process.HasExited ? process.ExitCode : null;
if (_sessions.TryGetValue(sessionId, out var session))
{
_sessions[sessionId] = session with
{
Status = exitCode == 0 ? PluginRuntimeSessionStatus.Exited : PluginRuntimeSessionStatus.Failed,
EndedAt = DateTimeOffset.Now,
ExitCode = exitCode
};
_ = WriteLogAsync(
exitCode == 0 ? "Information" : "Error",
session.PluginId,
"Tauri plugin host exited",
$"session={sessionId}; exitCode={exitCode}",
CancellationToken.None);
}
}
finally
{
_processes.TryRemove(sessionId, out _);
process.Dispose();
}
}
private void CaptureOutput(string sessionId, ShellOutputStream stream, string? line)
{
if (line is null)
{
return;
}
var output = new ShellOutputEvent(sessionId, stream, line, DateTimeOffset.Now, Interlocked.Increment(ref _sequence));
var lines = _output.GetOrAdd(sessionId, _ => []);
lock (lines)
{
lines.Add(output);
if (lines.Count > 5000)
{
lines.RemoveRange(0, lines.Count - 5000);
}
}
}
private async Task WriteLogAsync(string level, string pluginId, string message, string? detail, CancellationToken cancellationToken)
{
await (logService?.WriteAsync(level, $"plugin:{pluginId}:runtime", message, detail, cancellationToken) ?? Task.CompletedTask).ConfigureAwait(false);
}
private string? ResolveHostExecutable()
{
var candidates = new List<string>
{
Path.Combine(AppContext.BaseDirectory, "tauri-host", "ymhut-box-plugin-tauri-host.exe"),
Path.Combine(AppContext.BaseDirectory, "tauri-host", "YMhut.Box.PluginTauriHost.exe"),
Path.Combine(paths.Root, "Runtime", "tauri-host", "ymhut-box-plugin-tauri-host.exe")
};
var directory = new DirectoryInfo(AppContext.BaseDirectory);
while (directory is not null)
{
candidates.Add(Path.Combine(directory.FullName, "src", "YMhut.Box.PluginTauriHost", "src-tauri", "target", "release", "ymhut-box-plugin-tauri-host.exe"));
candidates.Add(Path.Combine(directory.FullName, "src", "YMhut.Box.PluginTauriHost", "src-tauri", "target", "debug", "ymhut-box-plugin-tauri-host.exe"));
directory = directory.Parent;
}
return candidates.FirstOrDefault(File.Exists);
}
}