From 34ae3922513aa7be94f12018c9469b4c260475e5 Mon Sep 17 00:00:00 2001 From: Aikawa Yataro Date: Fri, 21 Feb 2025 08:29:58 +0000 Subject: [PATCH] feat: add safe command termination --- src/Commands/Command.cs | 64 ++++++++++++++++++++++++++++++++--------- src/Native/Linux.cs | 15 ++++++++++ src/Native/MacOS.cs | 15 ++++++++++ src/Native/OS.cs | 7 +++++ src/Native/Windows.cs | 27 +++++++++++++++++ 5 files changed, 115 insertions(+), 13 deletions(-) diff --git a/src/Commands/Command.cs b/src/Commands/Command.cs index 3f61de17..d39a3bf4 100644 --- a/src/Commands/Command.cs +++ b/src/Commands/Command.cs @@ -37,6 +37,12 @@ namespace SourceGit.Commands public string Args { get; set; } = string.Empty; public bool RaiseError { get; set; } = true; public bool TraitErrorAsOutput { get; set; } = false; + public bool Cancelable { get; } = false; + + public Command(bool cancelable = false) + { + Cancelable = cancelable; + } public bool Exec() { @@ -109,12 +115,22 @@ namespace SourceGit.Commands return false; } - proc.BeginOutputReadLine(); - proc.BeginErrorReadLine(); - proc.WaitForExit(); + _proc = proc; - int exitCode = proc.ExitCode; - proc.Close(); + int exitCode; + try + { + proc.BeginOutputReadLine(); + proc.BeginErrorReadLine(); + proc.WaitForExit(); + + exitCode = proc.ExitCode; + proc.Close(); + } + finally + { + _proc = null; + } if (!isCancelled && exitCode != 0) { @@ -150,17 +166,37 @@ namespace SourceGit.Commands }; } - var rs = new ReadToEndResult() + _proc = proc; + + try { - StdOut = proc.StandardOutput.ReadToEnd(), - StdErr = proc.StandardError.ReadToEnd(), - }; + var rs = new ReadToEndResult() + { + StdOut = proc.StandardOutput.ReadToEnd(), + StdErr = proc.StandardError.ReadToEnd(), + }; - proc.WaitForExit(); - rs.IsSuccess = proc.ExitCode == 0; - proc.Close(); + proc.WaitForExit(); + rs.IsSuccess = proc.ExitCode == 0; + proc.Close(); - return rs; + return rs; + } + finally + { + _proc = null; + } + } + + public void TryCancel() + { + if (!this.Cancelable) + throw new Exception("Command is not cancelable!"); + + if (_proc is null) + return; + + Native.OS.TerminateSafely(_proc); } protected virtual void OnReadline(string line) @@ -226,5 +262,7 @@ namespace SourceGit.Commands [GeneratedRegex(@"\d+%")] private static partial Regex REG_PROGRESS(); + + private Process _proc = null; } } diff --git a/src/Native/Linux.cs b/src/Native/Linux.cs index a24f1b65..c0fbaadb 100644 --- a/src/Native/Linux.cs +++ b/src/Native/Linux.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Runtime.Versioning; using Avalonia; @@ -11,6 +12,14 @@ namespace SourceGit.Native [SupportedOSPlatform("linux")] internal class Linux : OS.IBackend { + private enum SIGNAL : int + { + TERM = 15 + } + + [DllImport("c")] + private static extern int kill(int pid, int sig); + public void SetupApp(AppBuilder builder) { builder.With(new X11PlatformOptions() { EnableIme = true }); @@ -97,6 +106,12 @@ namespace SourceGit.Native } } + public void TerminateSafely(Process process) + { + if (kill(process.Id, (int)SIGNAL.TERM) == 0) + process.WaitForExit(); + } + private string FindExecutable(string filename) { var pathVariable = Environment.GetEnvironmentVariable("PATH") ?? string.Empty; diff --git a/src/Native/MacOS.cs b/src/Native/MacOS.cs index 633ef5eb..81a31f0f 100644 --- a/src/Native/MacOS.cs +++ b/src/Native/MacOS.cs @@ -2,6 +2,7 @@ using System.Collections.Generic; using System.Diagnostics; using System.IO; +using System.Runtime.InteropServices; using System.Runtime.Versioning; using Avalonia; @@ -11,6 +12,14 @@ namespace SourceGit.Native [SupportedOSPlatform("macOS")] internal class MacOS : OS.IBackend { + private enum SIGNAL : int + { + TERM = 15 + } + + [DllImport("System")] + private static extern int kill(int pid, int sig); + public void SetupApp(AppBuilder builder) { builder.With(new MacOSPlatformOptions() @@ -88,5 +97,11 @@ namespace SourceGit.Native { Process.Start("open", $"\"{file}\""); } + + public void TerminateSafely(Process process) + { + if (kill(process.Id, (int)SIGNAL.TERM) == 0) + process.WaitForExit(); + } } } diff --git a/src/Native/OS.cs b/src/Native/OS.cs index 3a688654..f0c50d9a 100644 --- a/src/Native/OS.cs +++ b/src/Native/OS.cs @@ -23,6 +23,8 @@ namespace SourceGit.Native void OpenInFileManager(string path, bool select); void OpenBrowser(string url); void OpenWithDefaultEditor(string file); + + void TerminateSafely(Process process); } public static string DataDir @@ -168,6 +170,11 @@ namespace SourceGit.Native _backend.OpenWithDefaultEditor(file); } + public static void TerminateSafely(Process process) + { + _backend.TerminateSafely(process); + } + private static void UpdateGitVersion() { if (string.IsNullOrEmpty(_gitExecutable) || !File.Exists(_gitExecutable)) diff --git a/src/Native/Windows.cs b/src/Native/Windows.cs index 11b6bd13..8e32e73f 100644 --- a/src/Native/Windows.cs +++ b/src/Native/Windows.cs @@ -35,6 +35,11 @@ namespace SourceGit.Native public int cyBottomHeight; } + private enum CTRL_EVENT : int + { + CTRL_C = 0 + } + [DllImport("ntdll.dll")] private static extern int RtlGetVersion(ref RTL_OSVERSIONINFOEX lpVersionInformation); @@ -53,6 +58,12 @@ namespace SourceGit.Native [DllImport("shell32.dll", CharSet = CharSet.Unicode, SetLastError = false)] private static extern int SHOpenFolderAndSelectItems(IntPtr pidlFolder, int cild, IntPtr apidl, int dwFlags); + [DllImport("kernel32.dll")] + public static extern bool SetConsoleCtrlHandler(IntPtr handlerRoutine, bool add); + + [DllImport("kernel32.dll")] + private static extern bool GenerateConsoleCtrlEvent(int dwCtrlEvent, int dwProcessGroupId); + public void SetupApp(AppBuilder builder) { // Fix drop shadow issue on Windows 10 @@ -212,6 +223,22 @@ namespace SourceGit.Native Process.Start(start); } + public void TerminateSafely(Process process) + { + if (SetConsoleCtrlHandler(IntPtr.Zero, true)) + { + try + { + if (GenerateConsoleCtrlEvent((int)CTRL_EVENT.CTRL_C, process.Id)) + process.WaitForExit(); + } + finally + { + SetConsoleCtrlHandler(IntPtr.Zero, false); + } + } + } + private void FixWindowFrameOnWin10(Window w) { var platformHandle = w.TryGetPlatformHandle();