From 77f6b1a8f278898607e3ff511f7c115c0e5d5e66 Mon Sep 17 00:00:00 2001 From: MikiraSora Date: Mon, 21 Oct 2024 15:10:18 +0800 Subject: [PATCH] split ArgProcesser --- OngekiFumenEditor/App.xaml.cs | 5 +- OngekiFumenEditor/AppBootstrapper.cs | 61 ++- .../ArgProcesser/DefaultArgProcessManager.cs | 54 ++ .../ArgProcesser/IProgramArgProcessManager.cs | 2 +- .../Attributes/OptionBindingAttrbute.cs | 102 ++-- .../DefaultCommandExecutor.cs} | 495 ++++++++---------- .../CommandExecutor/ICommandExecutor.cs | 13 + .../FumenConverter/FumenConvertOption.cs | 2 +- .../Models/AcbGenerateOption.cs | 2 +- .../Models/JacketGenerateOption.cs | 2 +- .../PreviewSvgGenerator/GenerateOption.cs | 2 +- OngekiFumenEditor/Startup.cs | 23 +- OngekiFumenEditor/Utils/IPCHelper.cs | 34 +- 13 files changed, 450 insertions(+), 347 deletions(-) create mode 100644 OngekiFumenEditor/Kernel/ArgProcesser/DefaultArgProcessManager.cs rename OngekiFumenEditor/Kernel/{ArgProcesser => CommandExecutor}/Attributes/OptionBindingAttrbute.cs (93%) rename OngekiFumenEditor/Kernel/{ArgProcesser/DefaultImp/DefaultArgProcessManager.cs => CommandExecutor/DefaultCommandExecutor.cs} (66%) create mode 100644 OngekiFumenEditor/Kernel/CommandExecutor/ICommandExecutor.cs diff --git a/OngekiFumenEditor/App.xaml.cs b/OngekiFumenEditor/App.xaml.cs index a037296e..3733928b 100644 --- a/OngekiFumenEditor/App.xaml.cs +++ b/OngekiFumenEditor/App.xaml.cs @@ -18,11 +18,14 @@ namespace OngekiFumenEditor /// public partial class App : Application { - public App() + public bool IsGUIMode { get; } + + public App(bool isGUIMode = true) { AppDomain.CurrentDomain.AssemblyResolve += OnSatelliteAssemblyResolve; // 设置工作目录为执行文件所在的目录 Directory.SetCurrentDirectory(Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location)); + IsGUIMode = isGUIMode; } private Assembly OnSatelliteAssemblyResolve(object sender, ResolveEventArgs args) diff --git a/OngekiFumenEditor/AppBootstrapper.cs b/OngekiFumenEditor/AppBootstrapper.cs index 217668a3..d661386d 100644 --- a/OngekiFumenEditor/AppBootstrapper.cs +++ b/OngekiFumenEditor/AppBootstrapper.cs @@ -9,6 +9,7 @@ using System.Security.Principal; using System.Text; using System.Text.Json; +using System.Threading; using System.Threading.Tasks; using System.Windows; using System.Windows.Controls; @@ -19,6 +20,7 @@ using Gemini.Modules.Output; using OngekiFumenEditor.Kernel.ArgProcesser; using OngekiFumenEditor.Kernel.Audio; +using OngekiFumenEditor.Kernel.CommandExecutor; using OngekiFumenEditor.Kernel.EditorLayout; using OngekiFumenEditor.Kernel.Scheduler; using OngekiFumenEditor.Modules.AudioPlayerToolViewer; @@ -158,7 +160,38 @@ private bool CheckIfAdminPermission() return principal.IsInRole(WindowsBuiltInRole.Administrator); } - protected override async void OnStartup(object sender, StartupEventArgs e) + protected override void OnStartup(object sender, StartupEventArgs e) + { + var isGUIMode = (App.Current as App)?.IsGUIMode ?? false; + + if (isGUIMode) + { + OnStartupForGUI(sender, e); + } + else + { + OnStartupForCMD(sender, e); + } + } + + private async void OnStartupForCMD(object sender, StartupEventArgs e) + { + await IoC.Get().Init(); + + var executor = IoC.Get(); + + try + { + Application.Current.Shutdown(await executor.Execute(e.Args)); + } + catch (Exception ex) + { + Log.LogError($"Unhandled exception processing arguments:\n{ex.Message}"); + Application.Current.Shutdown(1); + } + } + + private async void OnStartupForGUI(object sender, StartupEventArgs e) { InitExceptionCatcher(); LogBaseInfos(); @@ -166,9 +199,13 @@ protected override async void OnStartup(object sender, StartupEventArgs e) await IoC.Get().Init(); - try { + try + { + //process command args await IoC.Get().ProcessArgs(e.Args); - } catch (Exception ex) { + } + catch (Exception ex) + { await Console.Error.WriteLineAsync($"Unhandled exception processing arguments:\n{ex.Message}"); Application.Current.Shutdown(-1); return; @@ -186,11 +223,6 @@ protected override async void OnStartup(object sender, StartupEventArgs e) curProc.PriorityBoostEnabled = true; } - ShowStartupGUI(); - } - - private void OnStartupForGUI() - { //overwrite ViewLocator var locateForModel = ViewLocator.LocateForModel; ViewLocator.LocateForModel = (model, hostControl, ctx) => @@ -227,11 +259,6 @@ private void OnStartupForGUI() window.AllowDrop = true; window.Drop += MainWindow_Drop; } - } - - public async void ShowStartupGUI() - { - OnStartupForGUI(); await DisplayRootViewForAsync(); var showSplashWindow = IoC.Get().Documents.IsEmpty() && @@ -257,14 +284,14 @@ private void InitIPCServer() { //if (ProgramSetting.Default.EnableMultiInstances) // return; - ipcThread = new AbortableThread(async cancelToken => + ipcThread = new AbortableThread(cancelToken => { while (!cancelToken.IsCancellationRequested) { if (!IPCHelper.IsSelfHost()) { //如果自己不是host那就检查另一个host死了没,来个随机sleep那样的话可以避免多个实例撞车 - await Task.Delay(MathUtils.Random(0, 1000)); + Thread.Sleep(MathUtils.Random(0, 1000)); if (!IPCHelper.IsHostAlive()) { //似了就继承大业 @@ -275,14 +302,14 @@ private void InitIPCServer() try { - var line = IPCHelper.ReadLineAsync(cancelToken)?.Trim(); + var line = IPCHelper.ReadLine(cancelToken)?.Trim(); if (string.IsNullOrWhiteSpace(line)) continue; Log.LogDebug($"Recv line by IPC:{line}"); if (line.StartsWith("CMD:")) { var args = JsonSerializer.Deserialize(line[4..]).Args; - await Application.Current.Dispatcher.InvokeAsync(() => + Application.Current.Dispatcher.Invoke(() => IoC.Get().ProcessArgs(args)); } } diff --git a/OngekiFumenEditor/Kernel/ArgProcesser/DefaultArgProcessManager.cs b/OngekiFumenEditor/Kernel/ArgProcesser/DefaultArgProcessManager.cs new file mode 100644 index 00000000..2ce5927d --- /dev/null +++ b/OngekiFumenEditor/Kernel/ArgProcesser/DefaultArgProcessManager.cs @@ -0,0 +1,54 @@ +using Caliburn.Micro; +using OngekiFumenEditor.Base; +using OngekiFumenEditor.Kernel.Audio; +using OngekiFumenEditor.Modules.FumenVisualEditor; +using OngekiFumenEditor.Modules.PreviewSvgGenerator; +using OngekiFumenEditor.Parser; +using OngekiFumenEditor.Properties; +using OngekiFumenEditor.Utils; +using OngekiFumenEditor.Utils.Logs.DefaultImpls; +using System; +using System.Collections.Generic; +using System.CommandLine; +using System.CommandLine.Parsing; +using System.ComponentModel.Composition; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Threading.Tasks; +using System.Windows; +using OngekiFumenEditor.Modules.FumenConverter; +using OngekiFumenEditor.Modules.FumenConverter.Kernel; +using OngekiFumenEditor.Modules.OptionGeneratorTools.Base; +using OngekiFumenEditor.Modules.OptionGeneratorTools.Kernel; +using OngekiFumenEditor.Modules.OptionGeneratorTools.Models; +using Expression = System.Linq.Expressions.Expression; +using OngekiFumenEditor.Kernel.CommandExecutor.Attributes; + +namespace OngekiFumenEditor.Kernel.ArgProcesser +{ + [Export(typeof(IProgramArgProcessManager))] + internal class DefaultArgProcessManager : IProgramArgProcessManager + { + public async Task ProcessArgs(string[] args) + { + if (args.Length == 0) + return; + + //if args[0] is openable file likes .ogkr/.nyagekiProj/.nyageki ... + if (args.IsOnlyOne(out var filePath)) + { + if (File.Exists(filePath)) + { + Log.LogInfo($"arg.filePath: {filePath}"); + + _ = Application.Current.Dispatcher.Invoke(async () => + { + if (await DocumentOpenHelper.TryOpenAsDocument(filePath)) + Application.Current?.MainWindow?.Focus(); + }); + } + } + } + } +} \ No newline at end of file diff --git a/OngekiFumenEditor/Kernel/ArgProcesser/IProgramArgProcessManager.cs b/OngekiFumenEditor/Kernel/ArgProcesser/IProgramArgProcessManager.cs index d77d2425..25c68606 100644 --- a/OngekiFumenEditor/Kernel/ArgProcesser/IProgramArgProcessManager.cs +++ b/OngekiFumenEditor/Kernel/ArgProcesser/IProgramArgProcessManager.cs @@ -5,5 +5,5 @@ namespace OngekiFumenEditor.Kernel.ArgProcesser public interface IProgramArgProcessManager { Task ProcessArgs(string[] args); - } + } } diff --git a/OngekiFumenEditor/Kernel/ArgProcesser/Attributes/OptionBindingAttrbute.cs b/OngekiFumenEditor/Kernel/CommandExecutor/Attributes/OptionBindingAttrbute.cs similarity index 93% rename from OngekiFumenEditor/Kernel/ArgProcesser/Attributes/OptionBindingAttrbute.cs rename to OngekiFumenEditor/Kernel/CommandExecutor/Attributes/OptionBindingAttrbute.cs index e90e3e2f..807bf4bb 100644 --- a/OngekiFumenEditor/Kernel/ArgProcesser/Attributes/OptionBindingAttrbute.cs +++ b/OngekiFumenEditor/Kernel/CommandExecutor/Attributes/OptionBindingAttrbute.cs @@ -1,52 +1,52 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Resources; -using System.Text; -using System.Threading.Tasks; -using OngekiFumenEditor.Properties; -using OngekiFumenEditor.Utils; - -namespace OngekiFumenEditor.Kernel.ArgProcesser.Attributes -{ - public abstract class OptionBindingAttrbuteBase : Attribute - { - public OptionBindingAttrbuteBase(string name, string description, object defaultValue, Type type) - { - Name = name; - Description = description; - DefaultValue = defaultValue; - Type = type; - } - - public string Name { get; set; } - public string Description { get; set; } - public object DefaultValue { get; set; } - public Type Type { get; } - public bool Require { get; set; } - } - - [AttributeUsage(AttributeTargets.Property)] - public class OptionBindingAttrbute : OptionBindingAttrbuteBase - { - public OptionBindingAttrbute(string name, string description, T defaultValue) : base(name, description, defaultValue, typeof(T)) - { - - } - } - - [AttributeUsage(AttributeTargets.Property)] - public class LocalizableOptionBindingAttribute : OptionBindingAttrbute - { - public LocalizableOptionBindingAttribute(string name, string resourceKey, T defaultValue, bool require = false) - : base(name, Resources.ResourceManager.GetString(resourceKey) ?? string.Empty, defaultValue) - { - Require = require; -#if DEBUG - // ReSharper disable once ConditionIsAlwaysTrueOrFalse - if (string.IsNullOrWhiteSpace(Description)) - Log.LogDebug($"Invalid resource key '{resourceKey}' for option '{name}'"); -#endif - } - } +using System; +using System.Collections.Generic; +using System.Linq; +using System.Resources; +using System.Text; +using System.Threading.Tasks; +using OngekiFumenEditor.Properties; +using OngekiFumenEditor.Utils; + +namespace OngekiFumenEditor.Kernel.CommandExecutor.Attributes +{ + public abstract class OptionBindingAttrbuteBase : Attribute + { + public OptionBindingAttrbuteBase(string name, string description, object defaultValue, Type type) + { + Name = name; + Description = description; + DefaultValue = defaultValue; + Type = type; + } + + public string Name { get; set; } + public string Description { get; set; } + public object DefaultValue { get; set; } + public Type Type { get; } + public bool Require { get; set; } + } + + [AttributeUsage(AttributeTargets.Property)] + public class OptionBindingAttrbute : OptionBindingAttrbuteBase + { + public OptionBindingAttrbute(string name, string description, T defaultValue) : base(name, description, defaultValue, typeof(T)) + { + + } + } + + [AttributeUsage(AttributeTargets.Property)] + public class LocalizableOptionBindingAttribute : OptionBindingAttrbute + { + public LocalizableOptionBindingAttribute(string name, string resourceKey, T defaultValue, bool require = false) + : base(name, Resources.ResourceManager.GetString(resourceKey) ?? string.Empty, defaultValue) + { + Require = require; +#if DEBUG + // ReSharper disable once ConditionIsAlwaysTrueOrFalse + if (string.IsNullOrWhiteSpace(Description)) + Log.LogDebug($"Invalid resource key '{resourceKey}' for option '{name}'"); +#endif + } + } } \ No newline at end of file diff --git a/OngekiFumenEditor/Kernel/ArgProcesser/DefaultImp/DefaultArgProcessManager.cs b/OngekiFumenEditor/Kernel/CommandExecutor/DefaultCommandExecutor.cs similarity index 66% rename from OngekiFumenEditor/Kernel/ArgProcesser/DefaultImp/DefaultArgProcessManager.cs rename to OngekiFumenEditor/Kernel/CommandExecutor/DefaultCommandExecutor.cs index 03566018..f92d9800 100644 --- a/OngekiFumenEditor/Kernel/ArgProcesser/DefaultImp/DefaultArgProcessManager.cs +++ b/OngekiFumenEditor/Kernel/CommandExecutor/DefaultCommandExecutor.cs @@ -1,264 +1,231 @@ -using Caliburn.Micro; -using OngekiFumenEditor.Base; -using OngekiFumenEditor.Kernel.ArgProcesser.Attributes; -using OngekiFumenEditor.Kernel.Audio; -using OngekiFumenEditor.Modules.FumenVisualEditor; -using OngekiFumenEditor.Modules.PreviewSvgGenerator; -using OngekiFumenEditor.Parser; -using OngekiFumenEditor.Properties; -using OngekiFumenEditor.Utils; -using OngekiFumenEditor.Utils.Logs.DefaultImpls; -using System; -using System.Collections.Generic; -using System.CommandLine; -using System.CommandLine.Parsing; -using System.ComponentModel.Composition; -using System.IO; -using System.Linq; -using System.Reflection; -using System.Threading.Tasks; -using System.Windows; -using OngekiFumenEditor.Modules.FumenConverter; -using OngekiFumenEditor.Modules.FumenConverter.Kernel; -using OngekiFumenEditor.Modules.OptionGeneratorTools.Base; -using OngekiFumenEditor.Modules.OptionGeneratorTools.Kernel; -using OngekiFumenEditor.Modules.OptionGeneratorTools.Models; -using Expression = System.Linq.Expressions.Expression; - -namespace OngekiFumenEditor.Kernel.ArgProcesser.DefaultImp -{ - [Export(typeof(IProgramArgProcessManager))] - internal class DefaultArgProcessManager : IProgramArgProcessManager - { - void Exit(int code = 0) => ErrorExit(string.Empty, true, code); - - void ErrorExit(string message, bool noDialog, int code = 0) - { - if (!string.IsNullOrWhiteSpace(message)) - { - if (noDialog) - { - var prevColor = Console.ForegroundColor; - Console.ForegroundColor = ConsoleColor.Red; - Console.Error.WriteLine($"{Resources.CliInputErrorHeader}: {message}"); - Console.ForegroundColor = prevColor; - } - else { - MessageBox.Show(message, Resources.Error, MessageBoxButton.OK, MessageBoxImage.Stop); - } - } - - Application.Current.Shutdown(code); - } - - public async Task ProcessArgs(string[] args) - { - if (args.Length == 0) - return; - - if (args.Length == 1) - { - var filePath = args[0]; - - if (File.Exists(filePath)) - { - Log.LogInfo($"arg.filePath: {filePath}"); - - _ = Application.Current.Dispatcher.Invoke(async () => - { - if (await DocumentOpenHelper.TryOpenAsDocument(filePath)) - Application.Current?.MainWindow?.Focus(); - }); - - return; - } - } - - var rootCommand = new RootCommand("CommandLine for OngekiFumenEditor"); - rootCommand.AddCommand(GenerateVerbCommands("svg", Resources.ProgramCommandDescriptionSvg, ProcessSvgCommand)); - rootCommand.AddCommand(GenerateVerbCommands("convert", Resources.ProgramCommandConvert, ProcessConvertCommand)); - rootCommand.AddCommand(GenerateVerbCommands("jacket", Resources.ProgramCommandJacket, ProcessJacketCommand)); - rootCommand.AddCommand(GenerateVerbCommands("acb", Resources.ProgramCommandAcb, ProcessAcbCommand)); - - var verbosityOption = new Option(new[] {"--verbose", "-v"}, Resources.ProgramOptionDescriptionVerbose); - verbosityOption.AddValidator(res => - { - if (res.GetValueOrDefault()) - Log.Instance.AddOutputIfNotExist(); - }); - rootCommand.AddGlobalOption(verbosityOption); - - await rootCommand.InvokeAsync(args); - - Exit(); - } - - private Command GenerateVerbCommands(string verb, string description, Func callbackFunc) where T : new() - { - var command = new Command(verb, description); - foreach (var option in GenerateOptionsByAttributes()) - command.AddOption(option); - - command.SetHandler(async ctx => - { - var opt = Generate(command, ctx.ParseResult); - await callbackFunc(opt); - }); - return command; - } - - private async Task ProcessSvgCommand(GenerateOption opt) - { - if (CheckRelativePaths(opt.AudioFilePath, opt.InputFumenFilePath, opt.OutputFilePath)) - return; - - try - { - using var fumenFileStream = File.OpenRead(opt.InputFumenFilePath); - var fumenDeserializer = IoC.Get().GetDeserializer(opt.InputFumenFilePath); - if (fumenDeserializer is null) - throw new NotSupportedException($"{Resources.DeserializeFumenFileFail}{opt.InputFumenFilePath}"); - var fumen = await fumenDeserializer.DeserializeAsync(fumenFileStream); - - //calculate duration - if (File.Exists(opt.AudioFilePath)) - { - var audioPlayer = await IoC.Get().LoadAudioAsync(opt.AudioFilePath); - opt.Duration = audioPlayer.Duration; - } - else - { - //只能通过谱面来计算 - var maxTGrid = fumen.GetAllDisplayableObjects().OfType().Max(x => x.TGrid); - maxTGrid += new GridOffset(5, 0); - var duration = TGridCalculator.ConvertTGridToAudioTime(maxTGrid, fumen.BpmList); - opt.Duration = duration; - } - - _ = await IoC.Get().GenerateSvgAsync(fumen, opt); - Log.LogInfo(Resources.GenerateSvgSuccess); - } - catch (Exception e) - { - Log.LogError(Resources.CallGenerateSvgAsyncFail, e); - Exit(1); - } - - Exit(); - } - - private async Task ProcessConvertCommand(FumenConvertOption opt) - { - if (CheckRelativePaths(opt.InputFumenFilePath, opt.OutputFumenFilePath)) - return; - - var result = await FumenConverterWrapper.Generate(opt); - if (!result.IsSuccess) { - await Console.Error.WriteLineAsync($"{Resources.ConvertFail} {result.Message}"); - Exit(1); - return; - } - - Exit(); - } - - private async Task ProcessJacketCommand(JacketGenerateOption arg) - { - if (CheckRelativePaths(arg.InputImageFilePath, arg.OutputAssetbundleFolderPath)) - return; - - GenerateResult result; - try { - result = await JacketGenerateWrapper.Generate(arg); - } - catch (Exception e) { - result = new(false, e.Message); - } - - if (!result.IsSuccess) { - await Console.Error.WriteLineAsync($"{Resources.GenerateJacketFileFail} {result.Message}"); - Exit(1); - return; - } - - Exit(); - } - - private async Task ProcessAcbCommand(AcbGenerateOption arg) - { - if (CheckRelativePaths(arg.InputAudioFilePath, arg.OutputFolderPath)) - return; - - GenerateResult result; - try { - result = await AcbGeneratorFuckWrapper.Generate(arg); - } - catch (Exception e) { - result = new(false, e.Message); - } - - if (!result.IsSuccess) { - await Console.Error.WriteLineAsync($"{Resources.GenerateAudioFileFail} {result.Message}"); - Exit(1); - return; - } - - Exit(); - } - - private bool CheckRelativePaths(params string[] paths) - { - if (paths.Any(path => !Path.IsPathRooted(path))) { - ErrorExit(Resources.CliArgumentNotAbsolutePath, true, 2); - return true; - } - - return false; - } - - #region Option generation - IEnumerable