diff --git a/app/MindWork AI Studio/Components/CodeBlock.razor.cs b/app/MindWork AI Studio/Components/CodeBlock.razor.cs index da6caa3b..1535bb00 100644 --- a/app/MindWork AI Studio/Components/CodeBlock.razor.cs +++ b/app/MindWork AI Studio/Components/CodeBlock.razor.cs @@ -16,9 +16,6 @@ public partial class CodeBlock : ComponentBase [CascadingParameter] public CodeTabs? ParentTabs { get; set; } - - private static readonly string DARK_BACKGROUND_COLOR = "#2d2d2d"; - private static readonly string DARK_FOREGROUND_COLOR = "#f8f8f2"; protected override void OnInitialized() { diff --git a/app/MindWork AI Studio/Components/CodeTabs.razor.cs b/app/MindWork AI Studio/Components/CodeTabs.razor.cs index 1640e51a..8c4abb9f 100644 --- a/app/MindWork AI Studio/Components/CodeTabs.razor.cs +++ b/app/MindWork AI Studio/Components/CodeTabs.razor.cs @@ -23,6 +23,6 @@ public partial class CodeTabs : ComponentBase private class CodeTabItem { public string Title { get; init; } = string.Empty; - public RenderFragment Fragment { get; init; } + public RenderFragment Fragment { get; init; } = null!; } } \ No newline at end of file diff --git a/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs b/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs index d1ca7392..9226074c 100644 --- a/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs +++ b/app/MindWork AI Studio/Dialogs/PandocDialog.razor.cs @@ -20,6 +20,7 @@ public partial class PandocDialog : ComponentBase [CascadingParameter] private IMudDialogInstance MudDialog { get; set; } = null!; + private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger("PandocDialog"); private static readonly string LICENCE_URI = "https://raw.githubusercontent.com/jgm/pandoc/master/COPYRIGHT"; private static string PANDOC_VERSION = "1.0.0"; @@ -110,7 +111,8 @@ public partial class PandocDialog : ComponentBase } catch (Exception ex) { - this.licenseText = "Error loading license text, please consider following the links to the GPL."; + this.licenseText = "Error loading license text, please consider following the links to read the GPL."; + LOG.LogError("Error loading GPL license text:\n{ErrorMessage}", ex.Message); } finally { diff --git a/app/MindWork AI Studio/Tools/Pandoc.cs b/app/MindWork AI Studio/Tools/Pandoc.cs index b989d2b9..d87c835c 100644 --- a/app/MindWork AI Studio/Tools/Pandoc.cs +++ b/app/MindWork AI Studio/Tools/Pandoc.cs @@ -9,16 +9,18 @@ namespace AIStudio.Tools; public static partial class Pandoc { - private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger("PluginFactory"); + private static readonly ILogger LOG = Program.LOGGER_FACTORY.CreateLogger("PandocService"); private static readonly string DOWNLOAD_URL = "https://github.com/jgm/pandoc/releases/download"; private static readonly string LATEST_URL = "https://github.com/jgm/pandoc/releases/latest"; private static readonly Version MINIMUM_REQUIRED_VERSION = new (3, 6); - private static readonly Version FALLBACK_VERSION = new (3, 6, 4); + private static readonly Version FALLBACK_VERSION = new (3, 7, 0, 1); private static readonly string CPU_ARCHITECTURE = "win-x64"; /// - /// Checks if pandoc is available on the system and can be started as a process + /// Checks if pandoc is available on the system and can be started as a process or present in AiStudio's data dir /// + /// 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) { @@ -30,16 +32,8 @@ public static partial class Pandoc await InstallAsync(rustService); return true; } - - var hasPandoc = false; - foreach (var subdirectory in subdirectories) - { - if (subdirectory.Contains("pandoc")) - hasPandoc = true; - } - if (hasPandoc) - return true; + if (HasPandoc(installDir)) return true; try { @@ -70,7 +64,7 @@ public static partial class Pandoc return false; } - var versionMatch = PandocRegex().Match(output); + var versionMatch = PandocCmdRegex().Match(output); if (!versionMatch.Success) { if (showMessages) @@ -78,11 +72,9 @@ public static partial class Pandoc LOG.LogError("pandoc --version returned an invalid format:\n {Output}", output); return false; } - var versions = versionMatch.Groups[1].Value.Split('.'); - var major = int.Parse(versions[0]); - var minor = int.Parse(versions[1]); - var installedVersion = new Version(major, minor); - + var versions = versionMatch.Groups[1].Value; + var installedVersion = Version.Parse(versions); + if (installedVersion >= MINIMUM_REQUIRED_VERSION) { if (showMessages) @@ -104,7 +96,36 @@ public static partial class Pandoc return false; } } + + private static bool HasPandoc(string pandocDirectory) + { + try + { + var subdirectories = Directory.GetDirectories(pandocDirectory); + + foreach (var subdirectory in subdirectories) + { + var pandocPath = Path.Combine(subdirectory, "pandoc.exe"); + if (File.Exists(pandocPath)) + { + return true; + } + } + + return false; + } + catch (Exception ex) + { + LOG.LogInformation("Pandoc is not installed in the data directory and might have thrown and error:\n{ErrorMessage}", ex.Message); + return false; + } + } + /// + /// Automatically decompresses the latest pandoc archive into AiStudio's data directory + /// + /// Global rust service to access file system and data dir + /// None public static async Task InstallAsync(RustService rustService) { var installDir = await GetPandocDataFolder(rustService); @@ -136,7 +157,10 @@ public static partial class Pandoc } else if (uri.Contains(".tar.gz")) { - Console.WriteLine("is zip"); + var tempTarPath = Path.Join(Path.GetTempPath(), "pandoc.tar.gz"); + await File.WriteAllBytesAsync(tempTarPath, fileBytes); + ZipFile.ExtractToDirectory(tempTarPath, installDir); + File.Delete(tempTarPath); } else { @@ -171,20 +195,25 @@ public static partial class Pandoc } } + /// + /// Asynchronously fetch the content from Pandoc's latest release page and extract the latest version number + /// + /// Version numbers can have the following formats: x.x, x.x.x or x.x.x.x + /// Latest Pandoc version number public static async Task FetchLatestVersionAsync() { using var client = new HttpClient(); var response = await client.GetAsync(LATEST_URL); if (!response.IsSuccessStatusCode) { - LOG.LogError("Code {StatusCode}: Could not fetch pandocs latest page:\n {Response}", response.StatusCode, response.RequestMessage); + LOG.LogError("Code {StatusCode}: Could not fetch Pandoc's latest page:\n {Response}", response.StatusCode, response.RequestMessage); await MessageBus.INSTANCE.SendWarning(new (Icons.Material.Filled.Warning, $"The latest pandoc version was not found, installing version {FALLBACK_VERSION.ToString()} instead.")); return FALLBACK_VERSION.ToString(); } var htmlContent = await response.Content.ReadAsStringAsync(); - var versionMatch = VersionRegex().Match(htmlContent); + var versionMatch = LatestVersionRegex().Match(htmlContent); if (!versionMatch.Success) { LOG.LogError("The latest version regex returned nothing:\n {Value}", versionMatch.Groups.ToString()); @@ -196,7 +225,10 @@ public static partial class Pandoc return version; } - // win arm not available + /// + /// Reads the systems architecture to find the correct archive + /// + /// Full URI to the right archive in Pandoc's repo public static async Task GenerateUriAsync() { var version = await FetchLatestVersionAsync(); @@ -212,6 +244,10 @@ public static partial class Pandoc }; } + /// + /// Reads the systems architecture to find the correct Pandoc installer + /// + /// Full URI to the right installer in Pandoc's repo public static async Task GenerateInstallerUriAsync() { var version = await FetchLatestVersionAsync(); @@ -232,15 +268,16 @@ public static partial class Pandoc } /// - /// Returns the name of the pandoc executable based on the running operating system + /// Reads the os platform to determine the used executable name /// + /// Name of the pandoc executable private static string GetPandocExecutableName() => RuntimeInformation.IsOSPlatform(OSPlatform.Windows) ? "pandoc.exe" : "pandoc"; private static async Task GetPandocDataFolder(RustService rustService) => Path.Join(await rustService.GetDataDirectory(), "pandoc"); - [GeneratedRegex(@"pandoc(?:\.exe)?\s*([0-9]+\.[0-9]+)")] - private static partial Regex PandocRegex(); + [GeneratedRegex(@"pandoc(?:\.exe)?\s*([0-9]+\.[0-9]+(?:\.[0-9]+)?(?:\.[0-9]+)?)")] + private static partial Regex PandocCmdRegex(); - [GeneratedRegex(@"pandoc(?:\.exe)?\s*([0-9]+\.[0-9]+\.[0-9]+(?:\.[0-9]+)?)")] - private static partial Regex VersionRegex(); + [GeneratedRegex(@"pandoc(?:\.exe)?\s*([0-9]+\.[0-9]+(?:\.[0-9]+)?(?:\.[0-9]+)?)")] + private static partial Regex LatestVersionRegex(); } \ No newline at end of file