From 4c09c434e2bc305f3faf793e06bb18089584acab Mon Sep 17 00:00:00 2001 From: Thorsten Sommer Date: Thu, 29 May 2025 16:12:20 +0200 Subject: [PATCH] Refactored Pandoc handling with improved process abstractions --- .../Dialogs/PandocDialog.razor.cs | 3 +- app/MindWork AI Studio/Tools/Pandoc.cs | 51 ++++++++++--------- .../Tools/PandocExecutable.cs | 3 ++ .../Tools/PandocInstallation.cs | 3 ++ .../Tools/PandocPreparedProcess.cs | 10 ++++ 5 files changed, 46 insertions(+), 24 deletions(-) create mode 100644 app/MindWork AI Studio/Tools/PandocExecutable.cs create mode 100644 app/MindWork AI Studio/Tools/PandocInstallation.cs create mode 100644 app/MindWork AI Studio/Tools/PandocPreparedProcess.cs diff --git a/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs b/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs index 1f206dad..8f0af30e 100644 --- a/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs @@ -46,7 +46,8 @@ public partial class PandocDialog : ComponentBase private async Task CheckPandocAvailabilityAsync() { - this.isPandocAvailable = await Pandoc.CheckAvailabilityAsync(this.RustService); + var pandocInstallation = await Pandoc.CheckAvailabilityAsync(this.RustService); + this.isPandocAvailable = pandocInstallation.IsAvailable; this.showSkeleton = false; await this.InvokeAsync(this.StateHasChanged); } diff --git a/app/MindWork AI Studio/Tools/Pandoc.cs b/app/MindWork AI Studio/Tools/Pandoc.cs index 50f605d2..6f4dc906 100644 --- a/app/MindWork AI Studio/Tools/Pandoc.cs +++ b/app/MindWork AI Studio/Tools/Pandoc.cs @@ -36,15 +36,19 @@ public static partial class Pandoc /// The format of the output file (e.g., pdf, docx, etc.). /// Additional arguments to pass to the pandoc command (optional). /// The ProcessStartInfo object configured to run pandoc with the specified parameters. - public static async Task PreparePandocProcess(RustService rustService, string inputFile, string outputFile, string inputFormat, string outputFormat, string? additionalArgs = null) => new() + public static async Task PreparePandocProcess(RustService rustService, string inputFile, string outputFile, string inputFormat, string outputFormat, string? additionalArgs = null) { - FileName = await PandocExecutablePath(rustService), - Arguments = $"{inputFile} -f {inputFormat} -t {outputFormat} {additionalArgs ?? string.Empty} -o {outputFile}", - RedirectStandardOutput = true, - RedirectStandardError = true, - UseShellExecute = false, - CreateNoWindow = true - }; + var pandocExecutable = await PandocExecutablePath(rustService); + return new (new ProcessStartInfo + { + FileName = pandocExecutable.Executable, + Arguments = $"{inputFile} -f {inputFormat} -t {outputFormat} {additionalArgs ?? string.Empty} -o {outputFile}", + RedirectStandardOutput = true, + RedirectStandardError = true, + UseShellExecute = false, + CreateNoWindow = true + }, pandocExecutable.IsLocalInstallation); + } /// /// Checks if pandoc is available on the system and can be started as a process or is present in AI Studio's data dir. @@ -52,11 +56,12 @@ public static partial class Pandoc /// Global rust service to access file system and data dir. /// Controls if snackbars are shown to the user. /// True, if pandoc is available and the minimum required version is met, else false. - public static async Task CheckAvailabilityAsync(RustService rustService, bool showMessages = true) + public static async Task CheckAvailabilityAsync(RustService rustService, bool showMessages = true) { try { - var startInfo = await PreparePandocProcess(rustService, string.Empty, string.Empty, string.Empty, string.Empty); + var preparedProcess = await PreparePandocProcess(rustService, string.Empty, string.Empty, string.Empty, string.Empty); + var startInfo = preparedProcess.StartInfo; startInfo.Arguments = "--version"; using var process = Process.Start(startInfo); @@ -66,7 +71,7 @@ public static partial class Pandoc await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Help, "The pandoc process could not be started.")); LOG.LogInformation("The pandoc process was not started, it was null"); - return false; + return new(false, "Was not able to start the pandoc process.", false, string.Empty, preparedProcess.IsLocal); } var output = await process.StandardOutput.ReadToEndAsync(); @@ -77,7 +82,7 @@ public static partial class Pandoc await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Error, "The pandoc process exited unexpectedly.")); LOG.LogError("The pandoc process was exited with code {ProcessExitCode}", process.ExitCode); - return false; + return new(false, "Pandoc is not available on the system or the process exited unexpectedly.", false, string.Empty, preparedProcess.IsLocal); } var versionMatch = PandocCmdRegex().Match(output); @@ -87,25 +92,25 @@ public static partial class Pandoc await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Terminal, "pandoc --version returned an invalid format.")); LOG.LogError("pandoc --version returned an invalid format:\n {Output}", output); - return false; + return new(false, "Pandoc is not available on the system or the version could not be parsed.", false, string.Empty, preparedProcess.IsLocal); } var versions = versionMatch.Groups[1].Value; var installedVersion = Version.Parse(versions); + var installedVersionString = installedVersion.ToString(); if (installedVersion >= MINIMUM_REQUIRED_VERSION) { if (showMessages) - await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.CheckCircle, $"Pandoc {installedVersion.ToString()} is installed.")); + await MessageBus.INSTANCE.SendSuccess(new(Icons.Material.Filled.CheckCircle, $"Pandoc {installedVersionString} is installed.")); - return true; + return new(true, string.Empty, true, installedVersionString, preparedProcess.IsLocal); } if (showMessages) - await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Build, $"Pandoc {installedVersion.ToString()} is installed, but it doesn't match the required version ({MINIMUM_REQUIRED_VERSION.ToString()}).")); + await MessageBus.INSTANCE.SendError(new (Icons.Material.Filled.Build, $"Pandoc {installedVersionString} is installed, but it doesn't match the required version ({MINIMUM_REQUIRED_VERSION.ToString()}).")); - LOG.LogInformation("Pandoc {Installed} is installed, but it does not match the required version ({Requirement})", installedVersion.ToString(), MINIMUM_REQUIRED_VERSION.ToString()); - return false; - + LOG.LogInformation("Pandoc {Installed} is installed, but it does not match the required version ({Requirement})", installedVersionString, MINIMUM_REQUIRED_VERSION.ToString()); + return new(true, $"Pandoc {installedVersionString} is installed, but it does not match the required version ({MINIMUM_REQUIRED_VERSION.ToString()}).", false, installedVersionString, preparedProcess.IsLocal); } catch (Exception e) { @@ -113,7 +118,7 @@ public static partial class Pandoc await MessageBus.INSTANCE.SendError(new (@Icons.Material.Filled.AppsOutage, "Pandoc is not installed.")); LOG.LogError("Pandoc is not installed and threw an exception: {Message}", e.Message); - return false; + return new(false, "Pandoc is not installed or could not be started.", false, string.Empty, false); } } @@ -305,7 +310,7 @@ public static partial class Pandoc /// /// Global rust service to access file system and data dir. /// Path to the pandoc executable. - private static async Task PandocExecutablePath(RustService rustService) + private static async Task PandocExecutablePath(RustService rustService) { // // First, we try to find the pandoc executable in the data directory. @@ -320,7 +325,7 @@ public static partial class Pandoc { var pandocPath = Path.Combine(subdirectory, executableName); if (File.Exists(pandocPath)) - return pandocPath; + return new(pandocPath, true); } } catch @@ -331,7 +336,7 @@ public static partial class Pandoc // // When no local installation was found, we assume that the pandoc executable is in the system PATH. // - return PandocExecutableName; + return new(PandocExecutableName, false); } private static async Task GetPandocDataFolder(RustService rustService) => Path.Join(await rustService.GetDataDirectory(), "pandoc"); diff --git a/app/MindWork AI Studio/Tools/PandocExecutable.cs b/app/MindWork AI Studio/Tools/PandocExecutable.cs new file mode 100644 index 00000000..862d8c43 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PandocExecutable.cs @@ -0,0 +1,3 @@ +namespace AIStudio.Tools; + +public readonly record struct PandocExecutable(string Executable, bool IsLocalInstallation); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PandocInstallation.cs b/app/MindWork AI Studio/Tools/PandocInstallation.cs new file mode 100644 index 00000000..41444447 --- /dev/null +++ b/app/MindWork AI Studio/Tools/PandocInstallation.cs @@ -0,0 +1,3 @@ +namespace AIStudio.Tools; + +public readonly record struct PandocInstallation(bool CheckWasSuccessful, string ErrorMessage, bool IsAvailable, string Version, bool IsLocalInstallation); \ No newline at end of file diff --git a/app/MindWork AI Studio/Tools/PandocPreparedProcess.cs b/app/MindWork AI Studio/Tools/PandocPreparedProcess.cs new file mode 100644 index 00000000..edfc0b7c --- /dev/null +++ b/app/MindWork AI Studio/Tools/PandocPreparedProcess.cs @@ -0,0 +1,10 @@ +using System.Diagnostics; + +namespace AIStudio.Tools; + +public class PandocPreparedProcess(ProcessStartInfo startInfo, bool isLocal) +{ + public ProcessStartInfo StartInfo => startInfo; + + public bool IsLocal => isLocal; +} \ No newline at end of file