diff --git a/app/MindWork AI Studio/Tools/PandocProcessBuilder.cs b/app/MindWork AI Studio/Tools/PandocProcessBuilder.cs index 6d0909f8..dd31e38b 100644 --- a/app/MindWork AI Studio/Tools/PandocProcessBuilder.cs +++ b/app/MindWork AI Studio/Tools/PandocProcessBuilder.cs @@ -17,6 +17,7 @@ public sealed class PandocProcessBuilder private static readonly RID CPU_ARCHITECTURE = RIDExtensions.GetCurrentRID(); private static readonly RID METADATA_ARCHITECTURE = META_DATA_ARCH.Architecture.ToRID(); private static readonly ILogger LOGGER = Program.LOGGER_FACTORY.CreateLogger(nameof(PandocProcessBuilder)); + private const string FLATPAK_PANDOC_PLUGIN_BIN_DIRECTORY = "/app/plugins/pandoc/bin"; // Tracks whether the first log has been written to avoid log spam on repeated calls: private static bool HAS_LOGGED_ONCE; @@ -220,7 +221,8 @@ public sealed class PandocProcessBuilder } } - foreach (var candidate in SystemPandocExecutableCandidates(PandocExecutableName)) + var runtimeInfo = await rustService.GetRuntimeInfo(); + foreach (var candidate in SystemPandocExecutableCandidates(PandocExecutableName, runtimeInfo.LinuxPackageType)) { if (!File.Exists(candidate)) continue; @@ -250,7 +252,7 @@ public sealed class PandocProcessBuilder /// public static string PandocExecutableName => CPU_ARCHITECTURE is RID.WIN_ARM64 or RID.WIN_X64 ? "pandoc.exe" : "pandoc"; - private static IEnumerable SystemPandocExecutableCandidates(string executableName) + private static IEnumerable SystemPandocExecutableCandidates(string executableName, string linuxPackageType) { var candidates = new List(); @@ -269,6 +271,9 @@ public sealed class PandocProcessBuilder break; case RID.LINUX_X64 or RID.LINUX_ARM64: + if (string.Equals(linuxPackageType, "flatpak", StringComparison.OrdinalIgnoreCase)) + AddCandidate(candidates, FLATPAK_PANDOC_PLUGIN_BIN_DIRECTORY, executableName); + AddCandidate(candidates, "/usr/local/bin", executableName); AddCandidate(candidates, "/usr/bin", executableName); AddCandidate(candidates, "/snap/bin", executableName); diff --git a/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md b/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md index 89155f28..a367b1a0 100644 --- a/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md +++ b/app/MindWork AI Studio/wwwroot/changelog/v26.6.1.md @@ -15,4 +15,5 @@ - Fixed workspace creation and renaming to prevent new workspaces from using an existing name. - Fixed an issue on Microsoft Windows where reading attached documents could briefly open a terminal window while processing files. - Fixed an issue where AI Studio could be started multiple times on Microsoft Windows by launching it from different virtual desktops. +- Fixed an issue where Flatpak installations could not find Pandoc from the bundled plugin extension. - Upgraded dependencies. \ No newline at end of file diff --git a/runtime/src/environment.rs b/runtime/src/environment.rs index e3f1955c..8da33ced 100644 --- a/runtime/src/environment.rs +++ b/runtime/src/environment.rs @@ -105,13 +105,18 @@ fn detect_linux_package_type() -> &'static str { } #[cfg(target_os = "linux")] -fn is_flatpak() -> bool { +pub(crate) fn is_flatpak() -> bool { env_var_has_value("FLATPAK_ID") || Path::new("/.flatpak-info").is_file() || env::var("container") .is_ok_and(|value| value.trim().eq_ignore_ascii_case("flatpak")) } +#[cfg(not(target_os = "linux"))] +pub(crate) fn is_flatpak() -> bool { + false +} + #[cfg(target_os = "linux")] fn is_appimage() -> bool { env_var_has_value("APPIMAGE") || env_var_has_value("APPDIR") diff --git a/runtime/src/pandoc.rs b/runtime/src/pandoc.rs index b49c0c28..b5fffc3d 100644 --- a/runtime/src/pandoc.rs +++ b/runtime/src/pandoc.rs @@ -5,12 +5,13 @@ use std::path::{Path, PathBuf}; use std::sync::OnceLock; use log::{info, warn}; use tokio::process::Command; -use crate::environment::DATA_DIRECTORY; +use crate::environment::{DATA_DIRECTORY, is_flatpak}; use crate::metadata::META_DATA; /// Tracks whether the RID mismatch warning has been logged. static HAS_LOGGED_RID_MISMATCH: OnceLock<()> = OnceLock::new(); static HAS_LOGGED_PANDOC_PATH: OnceLock<()> = OnceLock::new(); +const FLATPAK_PANDOC_PLUGIN_BIN_DIRECTORY: &str = "/app/plugins/pandoc/bin"; /// Microsoft documents CREATE_NO_WINDOW as a process creation flag with value 0x08000000. /// It starts console applications without opening a console window: @@ -186,8 +187,12 @@ impl PandocProcessBuilder { } fn system_pandoc_executable_candidates(executable_name: &str) -> Vec { + Self::system_pandoc_executable_candidates_for(env::consts::OS, executable_name, is_flatpak()) + } + + fn system_pandoc_executable_candidates_for(os: &str, executable_name: &str, include_flatpak_extension: bool) -> Vec { let mut candidates: Vec = Vec::new(); - match env::consts::OS { + match os { "windows" => { Self::push_env_candidate(&mut candidates, "LOCALAPPDATA", &["Pandoc", executable_name]); Self::push_env_candidate(&mut candidates, "ProgramFiles", &["Pandoc", executable_name]); @@ -199,6 +204,9 @@ impl PandocProcessBuilder { candidates.push(PathBuf::from("/usr/bin").join(executable_name)); }, "linux" => { + if include_flatpak_extension { + candidates.push(PathBuf::from(FLATPAK_PANDOC_PLUGIN_BIN_DIRECTORY).join(executable_name)); + } candidates.push(PathBuf::from("/usr/local/bin").join(executable_name)); candidates.push(PathBuf::from("/usr/bin").join(executable_name)); candidates.push(PathBuf::from("/snap/bin").join(executable_name)); @@ -281,4 +289,46 @@ impl PandocProcessBuilder { _ => "pandoc".to_string(), } } +} + +#[cfg(test)] +mod tests { + use super::{FLATPAK_PANDOC_PLUGIN_BIN_DIRECTORY, PandocProcessBuilder}; + use std::fs; + use std::path::PathBuf; + use tempfile::tempdir; + + #[test] + fn linux_candidates_include_flatpak_pandoc_extension_first_when_flatpak() { + let candidates = PandocProcessBuilder::system_pandoc_executable_candidates_for("linux", "pandoc", true); + let flatpak_candidate = PathBuf::from(FLATPAK_PANDOC_PLUGIN_BIN_DIRECTORY).join("pandoc"); + let usr_local_candidate = PathBuf::from("/usr/local/bin").join("pandoc"); + + let flatpak_index = candidates.iter().position(|candidate| candidate == &flatpak_candidate).unwrap(); + let usr_local_index = candidates.iter().position(|candidate| candidate == &usr_local_candidate).unwrap(); + + assert!(flatpak_index < usr_local_index); + } + + #[test] + fn linux_candidates_skip_flatpak_pandoc_extension_when_not_flatpak() { + let candidates = PandocProcessBuilder::system_pandoc_executable_candidates_for("linux", "pandoc", false); + let flatpak_candidate = PathBuf::from(FLATPAK_PANDOC_PLUGIN_BIN_DIRECTORY).join("pandoc"); + + assert!(!candidates.contains(&flatpak_candidate)); + } + + #[test] + fn local_pandoc_search_finds_data_directory_installation() { + let directory = tempdir().unwrap(); + let pandoc_directory = directory.path().join("pandoc").join("bin"); + fs::create_dir_all(&pandoc_directory).unwrap(); + let pandoc_path = pandoc_directory.join("pandoc"); + fs::File::create(&pandoc_path).unwrap(); + + assert_eq!( + PandocProcessBuilder::find_executable_in_dir(directory.path(), "pandoc").unwrap(), + pandoc_path + ); + } } \ No newline at end of file