diff --git a/.github/workflows/rust_code_check.yml b/.github/workflows/rust_code_check.yml index e6040679..1ba8eb02 100644 --- a/.github/workflows/rust_code_check.yml +++ b/.github/workflows/rust_code_check.yml @@ -32,6 +32,14 @@ jobs: sudo apt-get update sudo apt-get install -y libwebkit2gtk-4.1-dev libappindicator3-dev librsvg2-dev patchelf xdg-utils libtracker-sparql-3.0-dev + # On Windows, we need to generate bindings for 'searchapi.h' using bindgen. + # And bindgen relies on 'libclang' + # https://rust-lang.github.io/rust-bindgen/requirements.html#windows + - name: Install dependencies (Windows only) + if: startsWith(matrix.platform, 'windows-latest') + shell: bash + run: winget install LLVM.LLVM --silent --accept-package-agreements --accept-source-agreements + - name: Add pizza engine as a dependency working-directory: src-tauri shell: bash diff --git a/docs/content.en/docs/release-notes/_index.md b/docs/content.en/docs/release-notes/_index.md index fc2a126b..e0a3f7a5 100644 --- a/docs/content.en/docs/release-notes/_index.md +++ b/docs/content.en/docs/release-notes/_index.md @@ -36,7 +36,7 @@ Information about release notes of Coco App is provided here. - fix: resolve deeplink login issue #881 - fix: use kill_on_drop() to avoid zombie proc in error case #887 - fix: settings window rendering/loading issue 889 - +- fix: ensure search paths are indexed #896 ### ✈️ Improvements diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index eeafb8a9..4adcd225 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -432,6 +432,26 @@ version = "0.22.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "72b3254f16251a8381aa12e40e3c4d2f0199f8c6508fbecb9d91f575e0fbb8c6" +[[package]] +name = "bindgen" +version = "0.72.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "993776b509cfb49c750f11b8f07a46fa23e0a1386ffc01fb1e7d343efc387895" +dependencies = [ + "bitflags 2.9.4", + "cexpr", + "clang-sys", + "itertools 0.12.1", + "log", + "prettyplease", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 2.0.106", +] + [[package]] name = "bit_field" version = "0.10.3" @@ -747,6 +767,15 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6d43a04d8753f35258c91f8ec639f792891f748a1edbd759cf1dcea3382ad83c" +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "cfb" version = "0.7.3" @@ -820,7 +849,7 @@ dependencies = [ "num-traits", "serde", "wasm-bindgen", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -833,6 +862,17 @@ dependencies = [ "inout", ] +[[package]] +name = "clang-sys" +version = "1.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0b023947811758c97c59bf9d1c188fd619ad4718dcaa767947df1cadb14f39f4" +dependencies = [ + "glob", + "libc", + "libloading 0.8.8", +] + [[package]] name = "coco" version = "0.7.1" @@ -842,19 +882,22 @@ dependencies = [ "async-recursion", "async-trait", "base64 0.13.1", + "bindgen", "bitflags 2.9.4", "borrowme", "camino", "cfg-if", "chinese-number", "chrono", + "configparser", "derive_more 2.0.1", "dirs 5.0.1", "enigo", "function_name", "futures", "futures-util", - "gio 0.20.12", + "gio 0.21.2", + "glib 0.21.2", "hostname", "http 1.3.1", "hyper 0.14.32", @@ -919,6 +962,7 @@ dependencies = [ "walkdir", "which", "windows 0.61.3", + "windows-sys 0.61.0", "zip 4.6.1", ] @@ -976,6 +1020,12 @@ dependencies = [ "crossbeam-utils", ] +[[package]] +name = "configparser" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e57e3272f0190c3f1584272d613719ba5fc7df7f4942fe542e63d949cf3a649b" + [[package]] name = "const-random" version = "0.1.18" @@ -1394,7 +1444,7 @@ dependencies = [ "libc", "option-ext", "redox_users 0.5.2", - "windows-sys 0.60.2", + "windows-sys 0.61.0", ] [[package]] @@ -2283,6 +2333,23 @@ dependencies = [ "smallvec", ] +[[package]] +name = "gio" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ed68efc12b748a771be2dccc49480d8584004382967c98323245fc3c38b74a42" +dependencies = [ + "futures-channel", + "futures-core", + "futures-io", + "futures-util", + "gio-sys 0.21.2", + "glib 0.21.2", + "libc", + "pin-project-lite", + "smallvec", +] + [[package]] name = "gio-sys" version = "0.18.1" @@ -2309,6 +2376,19 @@ dependencies = [ "windows-sys 0.59.0", ] +[[package]] +name = "gio-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "171ed2f6dd927abbe108cfd9eebff2052c335013f5879d55bab0dc1dee19b706" +dependencies = [ + "glib-sys 0.21.2", + "gobject-sys 0.21.2", + "libc", + "system-deps 7.0.5", + "windows-sys 0.61.0", +] + [[package]] name = "glib" version = "0.18.5" @@ -2353,6 +2433,27 @@ dependencies = [ "smallvec", ] +[[package]] +name = "glib" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8527f9a139b7ba936fe1cbb8710d7801d145a4e81534f2ae6e6be255b582f3b5" +dependencies = [ + "bitflags 2.9.4", + "futures-channel", + "futures-core", + "futures-executor", + "futures-task", + "futures-util", + "gio-sys 0.21.2", + "glib-macros 0.21.2", + "glib-sys 0.21.2", + "gobject-sys 0.21.2", + "libc", + "memchr", + "smallvec", +] + [[package]] name = "glib-macros" version = "0.18.5" @@ -2380,6 +2481,19 @@ dependencies = [ "syn 2.0.106", ] +[[package]] +name = "glib-macros" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "55eda916eecdae426d78d274a17b48137acdca6fba89621bd3705f2835bc719f" +dependencies = [ + "heck 0.5.0", + "proc-macro-crate 3.3.0", + "proc-macro2", + "quote", + "syn 2.0.106", +] + [[package]] name = "glib-sys" version = "0.18.1" @@ -2400,6 +2514,16 @@ dependencies = [ "system-deps 7.0.5", ] +[[package]] +name = "glib-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d09d3d0fddf7239521674e57b0465dfbd844632fec54f059f7f56112e3f927e1" +dependencies = [ + "libc", + "system-deps 7.0.5", +] + [[package]] name = "glob" version = "0.3.3" @@ -2446,6 +2570,17 @@ dependencies = [ "system-deps 7.0.5", ] +[[package]] +name = "gobject-sys" +version = "0.21.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "538e41d8776173ec107e7b0f2aceced60abc368d7e1d81c1f0e2ecd35f59080d" +dependencies = [ + "glib-sys 0.21.2", + "libc", + "system-deps 7.0.5", +] + [[package]] name = "gtk" version = "0.18.2" @@ -4799,6 +4934,16 @@ version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "925383efa346730478fb4838dbe9137d2a47675ad789c546d150a6e1dd4ab31c" +[[package]] +name = "prettyplease" +version = "0.2.37" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "479ca8adacdd7ce8f1fb39ce9ecccbfe93a3f1344b3d0d97f20bc0196208f62b" +dependencies = [ + "proc-macro2", + "syn 2.0.106", +] + [[package]] name = "proc-macro-crate" version = "1.3.1" @@ -7336,27 +7481,26 @@ dependencies = [ [[package]] name = "tracker-rs" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cc48e3b6e7a94b6c90b6905f157f2a875dfb5866cf19429476fd803c9c84eafc" +checksum = "4f6db4619caf27601f2ee31ce19fe9ddd6f7ebdedf6de3eef5399707f820b671" dependencies = [ - "bitflags 2.9.4", - "gio 0.20.12", - "glib 0.20.12", - "glib-sys 0.20.10", + "gio 0.21.2", + "glib 0.21.2", + "glib-sys 0.21.2", "libc", "tracker-sys", ] [[package]] name = "tracker-sys" -version = "0.6.1" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8814f2bd279c6fa3eb22921a5394df6109c6ec18f805e5973fa633a4e9a57785" +checksum = "b57f6d0182a3d2a149ca85cdea826849f2c3f329f39a8870432c65c8949b0d4f" dependencies = [ - "gio-sys 0.20.10", - "glib-sys 0.20.10", - "gobject-sys 0.20.10", + "gio-sys 0.21.2", + "glib-sys 0.21.2", + "gobject-sys 0.21.2", "libc", "system-deps 7.0.5", ] @@ -8039,7 +8183,7 @@ dependencies = [ "windows-collections", "windows-core 0.61.2", "windows-future", - "windows-link", + "windows-link 0.1.3", "windows-numerics", ] @@ -8095,7 +8239,7 @@ checksum = "c0fdd3ddb90610c7638aa2b3a3ab2904fb9e5cdbecc643ddb3647212781c4ae3" dependencies = [ "windows-implement 0.60.0", "windows-interface 0.59.1", - "windows-link", + "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", ] @@ -8107,7 +8251,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fc6a41e98427b19fe4b73c550f060b59fa592d7d686537eebf9385621bfbad8e" dependencies = [ "windows-core 0.61.2", - "windows-link", + "windows-link 0.1.3", "windows-threading", ] @@ -8207,6 +8351,12 @@ version = "0.1.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5e6ad25900d524eaabdbbb96d20b4311e1e7ae1699af4fb28c17ae66c80d798a" +[[package]] +name = "windows-link" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "45e46c0661abb7180e7b9c281db115305d49ca1709ab8242adf09666d2173c65" + [[package]] name = "windows-numerics" version = "0.2.0" @@ -8214,7 +8364,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9150af68066c4c5c07ddc0ce30421554771e528bde427614c61038bc2c92c2b1" dependencies = [ "windows-core 0.61.2", - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -8223,7 +8373,7 @@ version = "0.5.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5b8a9ed28765efc97bbc954883f4e6796c33a06546ebafacbabee9696967499e" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows-result 0.3.4", "windows-strings 0.4.2", ] @@ -8243,7 +8393,7 @@ version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56f42bd332cc6c8eac5af113fc0c1fd6a8fd2aa08a0119358686e5160d0586c6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -8262,7 +8412,7 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "87fa48cc5d406560701792be122a10132491cff9d0aeb23583cc2dcafc847319" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -8271,7 +8421,7 @@ version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56e6c93f3a0c3b36176cb1327a4958a0353d5d166c2a35cb268ace15e91d3b57" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -8319,6 +8469,15 @@ dependencies = [ "windows-targets 0.53.3", ] +[[package]] +name = "windows-sys" +version = "0.61.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e201184e40b2ede64bc2ea34968b28e33622acdbbf37104f0e4a33f7abe657aa" +dependencies = [ + "windows-link 0.2.0", +] + [[package]] name = "windows-targets" version = "0.42.2" @@ -8371,7 +8530,7 @@ version = "0.53.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d5fe6031c4041849d7c496a8ded650796e7b6ecc19df1a431c1a363342e5dc91" dependencies = [ - "windows-link", + "windows-link 0.1.3", "windows_aarch64_gnullvm 0.53.0", "windows_aarch64_msvc 0.53.0", "windows_i686_gnu 0.53.0", @@ -8388,7 +8547,7 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b66463ad2e0ea3bbf808b7f1d371311c80e115c0b71d60efc142cafbcfb057a6" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] @@ -8397,7 +8556,7 @@ version = "0.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e04a5c6627e310a23ad2358483286c7df260c964eb2d003d8efd6d0f4e79265c" dependencies = [ - "windows-link", + "windows-link 0.1.3", ] [[package]] diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index 78edb1f8..6d5be89a 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -15,6 +15,7 @@ crate-type = ["staticlib", "cdylib", "rlib"] [build-dependencies] tauri-build = { version = "2", features = ["default"] } +cfg-if = "1.0.1" [features] default = ["desktop"] @@ -103,7 +104,6 @@ zip = "4.0.0" url = "2.5.2" camino = "1.1.10" tokio-stream = { version = "0.1.17", features = ["io-util"] } -cfg-if = "1.0.1" sysinfo = "0.35.2" indexmap = { version = "2.10.0", features = ["serde"] } strum = { version = "0.27.2", features = ["derive"] } @@ -111,6 +111,7 @@ sys-locale = "0.3.2" tauri-plugin-prevent-default = "1" oneshot = "0.1.11" bitflags = "2.9.3" +cfg-if = "1.0.1" [target."cfg(target_os = \"macos\")".dependencies] tauri-nspanel = { git = "https://github.com/ahkohd/tauri-nspanel", branch = "v2" } @@ -121,9 +122,11 @@ objc2-application-services = { version = "0.3.1", features = ["HIServices"] } objc2-core-graphics = { version = "=0.3.1", features = ["CGEvent"] } [target."cfg(target_os = \"linux\")".dependencies] -gio = "0.20.12" -tracker-rs = "0.6.1" +gio = "0.21.2" +glib = "0.21.2" +tracker-rs = "0.7" which = "8.0.0" +configparser = "3.1.0" [target."cfg(any(target_os = \"macos\", windows, target_os = \"linux\"))".dependencies] tauri-plugin-single-instance = { version = "2.0.0", features = ["deep-link"] } @@ -149,4 +152,8 @@ semver = { version = "1", features = ["serde"] } [target."cfg(target_os = \"windows\")".dependencies] enigo="0.3" -windows = { version = "0.61.3", features = ["Win32_Foundation", "Win32_System_Com", "Win32_System_Ole", "Win32_System_Search", "Win32_UI_Shell_PropertiesSystem", "Win32_Data"] } +windows = { version = "0.61", features = ["Win32_Foundation", "Win32_System_Com", "Win32_System_Ole", "Win32_System_Search", "Win32_UI_Shell_PropertiesSystem", "Win32_Data"] } +windows-sys = { version = "0.61", features = ["Win32", "Win32_System", "Win32_System_Com"] } + +[target."cfg(target_os = \"windows\")".build-dependencies] +bindgen = "0.72.1" diff --git a/src-tauri/build.rs b/src-tauri/build.rs index 04e96bd8..83d632ea 100644 --- a/src-tauri/build.rs +++ b/src-tauri/build.rs @@ -11,4 +11,32 @@ fn main() { // // unexpected condition name: `ci` println!("cargo::rustc-check-cfg=cfg(ci)"); + + // Bindgen searchapi.h on Windows as the windows create does not provide + // bindings for it + cfg_if::cfg_if! { + if #[cfg(target_os = "windows")] { + use std::env; + use std::path::PathBuf; + + let wrapper_header = r#"#include + #include "#; + + let searchapi_bindings = bindgen::Builder::default() + .header_contents("wrapper.h", wrapper_header) + .generate() + .expect("failed to generate bindings for "); + + let out_path = PathBuf::from(env::var("OUT_DIR").unwrap()); + searchapi_bindings + .write_to_file(out_path.join("searchapi_bindings.rs")) + .expect("couldn't write bindings to ") + + // Looks like there is no need to link the library that contains the + // implementation of functions declared in 'searchapi.h' manually as + // the FFI bindings work (without doing that). + // + // This is wield, I do not expect the linker will link it automatically. + } + } } diff --git a/src-tauri/src/extension/built_in/file_search/config.rs b/src-tauri/src/extension/built_in/file_search/config.rs index 09fa8292..df31b535 100644 --- a/src-tauri/src/extension/built_in/file_search/config.rs +++ b/src-tauri/src/extension/built_in/file_search/config.rs @@ -1,5 +1,6 @@ //! File Search configuration entries definition and getter/setter functions. +use crate::extension::built_in::file_search::implementation::apply_config; use serde::Deserialize; use serde::Serialize; use serde_json::Value; @@ -23,7 +24,7 @@ static HOME_DIR: LazyLock = LazyLock::new(|| { .expect("User home directory should be encoded with UTF-8") }); -#[derive(Debug, Clone, Serialize, Deserialize, Copy)] +#[derive(Debug, Clone, Serialize, Deserialize, Copy, PartialEq)] pub enum SearchBy { Name, NameAndContents, @@ -197,13 +198,19 @@ pub async fn set_file_system_config( .store(TAURI_STORE_FILE_SYSTEM_CONFIG) .map_err(|e| e.to_string())?; - store.set(TAURI_STORE_KEY_SEARCH_PATHS, config.search_paths); - store.set(TAURI_STORE_KEY_EXCLUDE_PATHS, config.exclude_paths); - store.set(TAURI_STORE_KEY_FILE_TYPES, config.file_types); + store.set(TAURI_STORE_KEY_SEARCH_PATHS, config.search_paths.as_slice()); + store.set( + TAURI_STORE_KEY_EXCLUDE_PATHS, + config.exclude_paths.as_slice(), + ); + store.set(TAURI_STORE_KEY_FILE_TYPES, config.file_types.as_slice()); store.set( TAURI_STORE_KEY_SEARCH_BY, serde_json::to_value(config.search_by).unwrap(), ); + // Apply the config when we know that this set operation won't fail + apply_config(&config)?; + Ok(()) } diff --git a/src-tauri/src/extension/built_in/file_search/implementation/linux/gnome.rs b/src-tauri/src/extension/built_in/file_search/implementation/linux/gnome.rs index 50319192..9a5a64cf 100644 --- a/src-tauri/src/extension/built_in/file_search/implementation/linux/gnome.rs +++ b/src-tauri/src/extension/built_in/file_search/implementation/linux/gnome.rs @@ -10,7 +10,12 @@ use crate::{ common::document::{Document, OnOpened}, extension::built_in::file_search::config::SearchBy, }; +use camino::Utf8Path; use gio::Cancellable; +use gio::Settings; +use gio::prelude::SettingsExtManual; +use glib::GString; +use glib::collections::strv::StrV; use tracker::{SparqlConnection, SparqlCursor, prelude::SparqlCursorExtManual}; /// The service that we will connect to. @@ -238,6 +243,103 @@ pub(crate) async fn hits( Ok(result_hits) } +fn ensure_path_in_recursive_indexing_scope(list: &mut StrV, path: &str) { + for item in list.iter() { + let item_path = Utf8Path::new(item.as_str()); + let path = Utf8Path::new(path); + + // It is already covered or listed + if path.starts_with(item_path) { + return; + } + } + list.push( + GString::from_utf8_checked(path.as_bytes().to_vec()) + .expect("search_path_str contains an interior NUL"), + ); +} + +fn ensure_path_and_descendants_not_in_single_indexing_scope(list: &mut StrV, path: &str) { + // Indexes to the items that should be removed + let mut item_to_remove = Vec::new(); + for (idx, item) in list.iter().enumerate() { + let item_path = Utf8Path::new(item.as_str()); + let path = Utf8Path::new(path); + + if item_path.starts_with(path) { + item_to_remove.push(idx); + } + } + + // Reverse the indexes so that the remove operation won't invalidate them. + for idx in item_to_remove.into_iter().rev() { + list.remove(idx); + } +} + +pub(crate) fn apply_config(config: &FileSearchConfig) -> Result<(), String> { + // Tracker provides the following configuration entries to allow users to + // tweak the indexing scope: + // + // 1. ignored-directories: A list of names, directories with such names will be ignored. + // ['po', 'CVS', 'core-dumps', 'lost+found'] + // 2. ignored-directories-with-content: Avoid any directory containing a file blocklisted here + // ['.trackerignore', '.git', '.hg', '.nomedia'] + // 3. ignored-files: List of file patterns to avoid + // ['*~', '*.o', '*.la', '*.lo', '*.loT', '*.in', '*.m4', '*.rej', ...] + // 4. index-recursive-directories: List of directories to index recursively + // ['&DESKTOP', '&DOCUMENTS', '&MUSIC', '&PICTURES', '&VIDEOS'] + // 5. index-single-directories: List of directories to index without inspecting subfolders, + // ['$HOME', '&DOWNLOAD'] + // + // The first 3 entries specify patterns, in order to use them, we have to walk + // through the whole directory tree listed in search paths, which is impractical. + // So we only use the last 2 entries. + // + // + // Just want to mention that setting search path to "/home" could break Tracker: + // + // ```text + // Unknown target graph for uri:'file:///home' and mime:'inode/directory' + // ``` + // + // See the related bug reports: + // + // https://gitlab.gnome.org/GNOME/localsearch/-/issues/313 + // https://bugs.launchpad.net/bugs/2077181 + // + // + // There is nothing we can do. + + const TRACKER_SETTINGS_SCHEMA: &str = "org.freedesktop.Tracker3.Miner.Files"; + const KEY_INDEX_RECURSIVE_DIRECTORIES: &str = "index-recursive-directories"; + const KEY_INDEX_SINGLE_DIRECTORIES: &str = "index-single-directories"; + + let search_paths = &config.search_paths; + + let settings = Settings::new(TRACKER_SETTINGS_SCHEMA); + let mut recursive_list: StrV = settings.strv(KEY_INDEX_RECURSIVE_DIRECTORIES); + let mut single_list: StrV = settings.strv(KEY_INDEX_SINGLE_DIRECTORIES); + + for search_path in search_paths { + // We want our search path to be included in the recursive directories or + // any directory within the list covers it. + ensure_path_in_recursive_indexing_scope(&mut recursive_list, search_path); + // We want our search path and its any descendants are not listed in + // the index directories list. + ensure_path_and_descendants_not_in_single_indexing_scope(&mut single_list, search_path); + } + + settings + .set_strv(KEY_INDEX_RECURSIVE_DIRECTORIES, recursive_list) + .expect("key is not read-only"); + settings + .set_strv(KEY_INDEX_SINGLE_DIRECTORIES, single_list) + .expect("key is not be read-only"); + + Ok(()) +} + #[cfg(test)] mod tests { use super::*; diff --git a/src-tauri/src/extension/built_in/file_search/implementation/linux/kde.rs b/src-tauri/src/extension/built_in/file_search/implementation/linux/kde.rs index 7b766f18..d30260d4 100644 --- a/src-tauri/src/extension/built_in/file_search/implementation/linux/kde.rs +++ b/src-tauri/src/extension/built_in/file_search/implementation/linux/kde.rs @@ -8,6 +8,9 @@ use crate::common::document::{DataSourceReference, Document}; use crate::extension::LOCAL_QUERY_SOURCE_TYPE; use crate::extension::OnOpened; use crate::util::file::sync_get_file_icon; +use camino::Utf8Path; +use configparser::ini::Ini; +use configparser::ini::WriteOptions; use futures::stream::Stream; use futures::stream::StreamExt; use std::os::fd::OwnedFd; @@ -175,3 +178,131 @@ fn execute_baloosearch_query( Ok((iter, child)) } + +pub(crate) fn apply_config(config: &FileSearchConfig) -> Result<(), String> { + // Users can tweak Baloo via its configuration file, below are the fields that + // we need to modify: + // + // * Indexing-Enabled: turn indexing on or off + // * only basic indexing: If true, Baloo only indexes file names + // * folders: directories to index + // * exclude folders: directories to skip + // + // ```ini + // [Basic Settings] + // Indexing-Enabled=true + // + // [General] + // only basic indexing=true + // folders[$e]=$HOME/ + // exclude folders[$e]=$HOME/FolderA/,$HOME/FolderB/ + // ``` + + const SECTION_GENERAL: &str = "General"; + const KEY_INCLUDE_FOLDERS: &str = "folders[$e]"; + const KEY_EXCLUDE_FOLDERS: &str = "exclude folders[$e]"; + const FOLDERS_SEPARATOR: &str = ","; + + let rc_file_path = { + let mut home = dirs::home_dir() + .expect("cannot find the home directory, Coco should never run in such a environment"); + home.push(".config/baloofilerc"); + home + }; + + // Parse and load the rc file, it is in format INI + // + // Use `new_cs()`, the case-sensitive version of constructor as the config + // file contains uppercase letters, so it is case-sensitive. + let mut baloo_config = Ini::new_cs(); + if rc_file_path.try_exists().map_err(|e| e.to_string())? { + let _ = baloo_config.load(rc_file_path.as_path())?; + } + + // Ensure indexing is enabled + let _ = baloo_config.setstr("Basic Settings", "Indexing-Enabled", Some("true")); + + // Let baloo index file content if we need that + if config.search_by == SearchBy::NameAndContents { + let _ = baloo_config.setstr(SECTION_GENERAL, "only basic indexing", Some("false")); + } + + let mut include_folders = { + match baloo_config.get(SECTION_GENERAL, KEY_INCLUDE_FOLDERS) { + Some(str) => str + .split(FOLDERS_SEPARATOR) + .map(|str| str.to_string()) + .collect::>(), + None => Vec::new(), + } + }; + + let mut exclude_folders = { + match baloo_config.get(SECTION_GENERAL, KEY_EXCLUDE_FOLDERS) { + Some(str) => str + .split(FOLDERS_SEPARATOR) + .map(|str| str.to_string()) + .collect::>(), + None => Vec::new(), + } + }; + + fn ensure_path_included_include_folders( + include_folders: &mut Vec, + search_path: &Utf8Path, + ) { + for include_folder in include_folders.iter() { + let include_folder = Utf8Path::new(include_folder.as_str()); + if search_path.starts_with(include_folder) { + return; + } + } + + include_folders.push(search_path.as_str().to_string()); + } + + fn ensure_path_and_descendants_not_excluded( + exclude_folders: &mut Vec, + search_path: &Utf8Path, + ) { + let mut items_to_remove = Vec::new(); + for (idx, exclude_folder) in exclude_folders.iter().enumerate() { + let exclude_folder = Utf8Path::new(exclude_folder); + + if exclude_folder.starts_with(search_path) { + items_to_remove.push(idx); + } + } + + for idx in items_to_remove.into_iter().rev() { + exclude_folders.remove(idx); + } + } + + for search_path in config.search_paths.iter() { + let search_path = Utf8Path::new(search_path.as_str()); + + ensure_path_included_include_folders(&mut include_folders, search_path); + ensure_path_and_descendants_not_excluded(&mut exclude_folders, search_path); + } + + let include_folders_str: String = include_folders.as_slice().join(FOLDERS_SEPARATOR); + let exclude_folders_str: String = exclude_folders.as_slice().join(FOLDERS_SEPARATOR); + + let _ = baloo_config.set( + SECTION_GENERAL, + KEY_INCLUDE_FOLDERS, + Some(include_folders_str), + ); + let _ = baloo_config.set( + SECTION_GENERAL, + KEY_EXCLUDE_FOLDERS, + Some(exclude_folders_str), + ); + + baloo_config + .pretty_write(rc_file_path.as_path(), &WriteOptions::new()) + .map_err(|e| e.to_string())?; + + Ok(()) +} diff --git a/src-tauri/src/extension/built_in/file_search/implementation/linux/mod.rs b/src-tauri/src/extension/built_in/file_search/implementation/linux/mod.rs index 41009f40..321f2a94 100644 --- a/src-tauri/src/extension/built_in/file_search/implementation/linux/mod.rs +++ b/src-tauri/src/extension/built_in/file_search/implementation/linux/mod.rs @@ -5,6 +5,11 @@ use super::super::config::FileSearchConfig; use crate::common::document::Document; use crate::util::LinuxDesktopEnvironment; use crate::util::get_linux_desktop_environment; +use std::ops::Deref; +use std::sync::LazyLock; + +static DESKTOP_ENVIRONMENT: LazyLock> = + LazyLock::new(|| get_linux_desktop_environment()); /// Dispatch to implementations powered by different backends. pub(crate) async fn hits( @@ -13,7 +18,7 @@ pub(crate) async fn hits( size: usize, config: &FileSearchConfig, ) -> Result, String> { - let de = get_linux_desktop_environment(); + let de = DESKTOP_ENVIRONMENT.deref(); match de { Some(LinuxDesktopEnvironment::Gnome) => gnome::hits(query_string, from, size, config).await, Some(LinuxDesktopEnvironment::Kde) => kde::hits(query_string, from, size, config).await, @@ -27,3 +32,19 @@ pub(crate) async fn hits( } } } + +pub(crate) fn apply_config(config: &FileSearchConfig) -> Result<(), String> { + let de = DESKTOP_ENVIRONMENT.deref(); + match de { + Some(LinuxDesktopEnvironment::Gnome) => gnome::apply_config(config), + Some(LinuxDesktopEnvironment::Kde) => kde::apply_config(config), + Some(LinuxDesktopEnvironment::Unsupported { + xdg_current_desktop: _, + }) => { + return Err("file search is not supported on this desktop environment".into()); + } + None => { + return Err("could not determine Linux desktop environment".into()); + } + } +} diff --git a/src-tauri/src/extension/built_in/file_search/implementation/macos.rs b/src-tauri/src/extension/built_in/file_search/implementation/macos.rs index 35b77af9..ad14f6db 100644 --- a/src-tauri/src/extension/built_in/file_search/implementation/macos.rs +++ b/src-tauri/src/extension/built_in/file_search/implementation/macos.rs @@ -165,3 +165,26 @@ fn execute_mdfind_query( Ok((iter, child)) } + +pub(crate) fn apply_config(_: &FileSearchConfig) -> Result<(), String> { + // By default, macOS indexes all the files within a volume if indexing is + // enabled. So, to ensure our search paths are indexed by Spotlight, + // theoretically, we can do the following things: + // + // 1. Ensure indexing is enabled on the volumes where our search paths reside. + // However, we cannot do this as doing so requires `sudo`. + // + // 2. Ensure the search paths are not excluded from indexing scope. Users can + // stop Spotlight from indexing a directory by: + // 1. adding it to the "Privacy" list in 'System Settings'. Coco cannot + // modify this list, since the only way to change it is manually + // through System Settings. + // 2. Renaming directory name, adding a `.noindex` file extension to it. + // I don't want to use this trick, users won't feel comfortable and it + // could break at any time. + // 3. Creating a `.metadata_never_index` file within the directory (no longer works + // since macOS Mojave) + // + // There is nothing we can do. + Ok(()) +} diff --git a/src-tauri/src/extension/built_in/file_search/implementation/mod.rs b/src-tauri/src/extension/built_in/file_search/implementation/mod.rs index 8200a271..55bd0e7a 100644 --- a/src-tauri/src/extension/built_in/file_search/implementation/mod.rs +++ b/src-tauri/src/extension/built_in/file_search/implementation/mod.rs @@ -1,23 +1,36 @@ -#[cfg(target_os = "linux")] -mod linux; -#[cfg(target_os = "macos")] -mod macos; -#[cfg(target_os = "windows")] -mod windows; +use cfg_if::cfg_if; -// `hits()` function is platform-specific, export the corresponding impl. -#[cfg(target_os = "linux")] -pub(crate) use linux::hits; -#[cfg(target_os = "macos")] -pub(crate) use macos::hits; -#[cfg(target_os = "windows")] -pub(crate) use windows::hits; +// * hits: the implementation of search +// +// * apply_config: Routines that should be performed to keep "other things" +// synchronous with the passed configuration. +// Currently, "other things" only include system indexer's setting entries. +cfg_if! { + if #[cfg(target_os = "linux")] { + mod linux; + pub(crate) use linux::hits; + pub(crate) use linux::apply_config; + } else if #[cfg(target_os = "macos")] { + mod macos; + pub(crate) use macos::hits; + pub(crate) use macos::apply_config; + } else if #[cfg(target_os = "windows")] { + mod windows; + pub(crate) use windows::hits; + pub(crate) use windows::apply_config; + } +} -use super::config::FileSearchConfig; -use camino::Utf8Path; +cfg_if! { + if #[cfg(not(target_os = "windows"))] { + use super::config::FileSearchConfig; + use camino::Utf8Path; + } +} /// If `file_path` should be removed from the search results given the filter /// conditions specified in `config`. +#[cfg(not(target_os = "windows"))] // Not used on Windows pub(crate) fn should_be_filtered_out( config: &FileSearchConfig, file_path: &str, @@ -74,7 +87,8 @@ pub(crate) fn should_be_filtered_out( false } -#[cfg(test)] +// should_be_filtered_out() is not defined for Windows +#[cfg(all(test, not(target_os = "windows")))] mod tests { use super::super::config::SearchBy; use super::*; diff --git a/src-tauri/src/extension/built_in/file_search/implementation/windows/crawl_scope_manager/mod.rs b/src-tauri/src/extension/built_in/file_search/implementation/windows/crawl_scope_manager/mod.rs new file mode 100644 index 00000000..bb63c2e1 --- /dev/null +++ b/src-tauri/src/extension/built_in/file_search/implementation/windows/crawl_scope_manager/mod.rs @@ -0,0 +1,234 @@ +//! Wraps Windows `ISearchCrawlScopeManager` + +mod searchapi_h_bindings; + +use searchapi_h_bindings::CLSID_CSEARCH_MANAGER; +use searchapi_h_bindings::IID_ISEARCH_MANAGER; +use searchapi_h_bindings::{ + HRESULT, ISearchCatalogManager, ISearchCatalogManagerVtbl, ISearchCrawlScopeManager, + ISearchCrawlScopeManagerVtbl, ISearchManager, +}; +use std::ffi::OsStr; +use std::ffi::OsString; +use std::os::windows::ffi::OsStrExt; +use std::path::Path; +use std::path::PathBuf; +use std::ptr::null_mut; +use windows::core::w; +use windows_sys::Win32::Foundation::S_OK; +use windows_sys::Win32::System::Com::{ + CLSCTX_LOCAL_SERVER, COINIT_APARTMENTTHREADED, CoCreateInstance, CoInitializeEx, CoUninitialize, +}; + +#[derive(Debug, thiserror::Error)] +#[error("{msg}, function [{function}], HRESULT [{hresult}]")] +pub(crate) struct WindowSearchApiError { + function: &'static str, + hresult: HRESULT, + msg: String, +} + +/// See doc of [`Rule`]. +#[derive(Debug, PartialEq)] +pub(crate) enum RuleMode { + Inclusion, + Exclusion, +} + +/// A rule adds or removes one or more paths to/from the Windows Search index. +#[derive(Debug)] +pub(crate) struct Rule { + /// A path or path pattern (wildcard supported, only for exclusion rule) that + /// specifies the paths that this rule applies to. + /// + /// The rules used by Windows Search actually specify URLs rather than paths, + /// but we only care about paths, i.e., URLs with schema `file://` + pub(crate) paths: PathBuf, + /// Add or remove paths to/from the index. + pub(crate) mode: RuleMode, +} + +/// A wrapper around Window's `ISearchCrawlScopeManager` type +pub(crate) struct CrawlScopeManager { + i_search_crawl_scope_manager: *mut ISearchCrawlScopeManager, +} + +impl CrawlScopeManager { + fn vtable(&self) -> *mut ISearchCrawlScopeManagerVtbl { + unsafe { (*self.i_search_crawl_scope_manager).lpVtbl } + } + + pub(crate) fn new() -> Result { + unsafe { + // 1. Initialize the COM library, use Apartment-threading as Self is not Send/Sync + let hr = CoInitializeEx(null_mut(), COINIT_APARTMENTTHREADED as u32); + if hr != S_OK { + return Err(WindowSearchApiError { + function: "CoInitializeEx()", + hresult: hr, + msg: "failed to initialize the COM library".into(), + }); + } + + // 2. Create an instance of the CSearchManager. + let mut search_manager: *mut ISearchManager = null_mut(); + let hr = CoCreateInstance( + &CLSID_CSEARCH_MANAGER, // CLSID of the object + null_mut(), // No outer unknown + CLSCTX_LOCAL_SERVER, // Server context + &IID_ISEARCH_MANAGER, // IID of the interface we want + &mut search_manager as *mut _ as *mut _, // Pointer to receive the interface + ); + if hr != S_OK { + return Err(WindowSearchApiError { + function: "CoCreateInstance()", + hresult: hr, + msg: "failed to initialize ISearchManager".into(), + }); + } + assert!(!search_manager.is_null()); + + let search_manger_vtable = (*search_manager).lpVtbl; + let search_manager_fn_get_catalog = (*search_manger_vtable).GetCatalog.unwrap(); + let mut search_catalog_manager: *mut ISearchCatalogManager = null_mut(); + let string_literal_system_index = w!("SystemIndex"); + let hr: HRESULT = search_manager_fn_get_catalog( + search_manager, + string_literal_system_index.0, + &mut search_catalog_manager as *mut *mut ISearchCatalogManager, + ); + if hr != S_OK { + return Err(WindowSearchApiError { + function: "ISearchManager::GetCatalog()", + hresult: hr, + msg: "failed to initialize ISearchCatalogManager".into(), + }); + } + assert!(!search_catalog_manager.is_null()); + + let search_catalog_manager_vtable: *mut ISearchCatalogManagerVtbl = + (*search_catalog_manager).lpVtbl; + let fn_get_crawl_scope_manager = (*search_catalog_manager_vtable) + .GetCrawlScopeManager + .unwrap(); + let mut search_crawl_scope_manager: *mut ISearchCrawlScopeManager = null_mut(); + let hr = + fn_get_crawl_scope_manager(search_catalog_manager, &mut search_crawl_scope_manager); + if hr != S_OK { + return Err(WindowSearchApiError { + function: "ISearchCatalogManager::GetCrawlScopeManager()", + hresult: hr, + msg: "failed to initialize ISearchCrawlScopeManager".into(), + }); + } + assert!(!search_crawl_scope_manager.is_null()); + + Ok(Self { + i_search_crawl_scope_manager: search_crawl_scope_manager, + }) + } + } + + /// Does nothing unless you [`commit()`] the changes. + pub(crate) fn add_rule(&mut self, rule: Rule) -> Result<(), WindowSearchApiError> { + unsafe { + let vtable = self.vtable(); + + let fn_add_rule = (*vtable).AddUserScopeRule.unwrap(); + + let url: Vec = encode_path(&rule.paths); + let inclusion = (rule.mode == RuleMode::Inclusion) as i32; + let override_child_rules = true as i32; + let follow_flag = 0x1_u32; /* FF_INDEXCOMPLEXURLS */ + + let hr = fn_add_rule( + self.i_search_crawl_scope_manager, + url.as_ptr(), + inclusion, + override_child_rules, + follow_flag, + ); + + if hr != S_OK { + return Err(WindowSearchApiError { + function: "ISearchCrawlScopeManager::AddUserScopeRule()", + hresult: hr, + msg: "failed to add scope rule".into(), + }); + } + + Ok(()) + } + } + + pub(crate) fn is_path_included + ?Sized>( + &self, + path: &P, + ) -> Result { + unsafe { + let vtable = self.vtable(); + let fn_included_in_crawl_scope = (*vtable).IncludedInCrawlScope.unwrap(); + let path: Vec = encode_path(path); + + let mut included: i32 = 0 /* false */; + + let hr = fn_included_in_crawl_scope( + self.i_search_crawl_scope_manager, + path.as_ptr(), + &mut included, + ); + if hr != S_OK { + return Err(WindowSearchApiError { + function: "ISearchCrawlScopeManager::IncludedInCrawlScope()", + hresult: hr, + msg: "failed to call IncludedInCrawlScope()".into(), + }); + } + + Ok(included == 1) + } + } + + pub(crate) fn commit(&self) -> Result<(), WindowSearchApiError> { + unsafe { + let vtable = self.vtable(); + let fn_commit = (*vtable).SaveAll.unwrap(); + let hr = fn_commit(self.i_search_crawl_scope_manager); + if hr != S_OK { + return Err(WindowSearchApiError { + function: "ISearchCrawlScopeManager::SaveAll()", + hresult: hr, + msg: "failed to commit the changes".into(), + }); + } + + Ok(()) + } + } +} + +impl Drop for CrawlScopeManager { + fn drop(&mut self) { + unsafe { + CoUninitialize(); + } + } +} + +fn encode_path + ?Sized>(path: &P) -> Vec { + let mut buffer = OsString::new(); + + // schema + buffer.push("file:///"); + buffer.push(path.as_ref().as_os_str()); + + osstr_to_wstr(&buffer) +} + +fn osstr_to_wstr + ?Sized>(str: &S) -> Vec { + let os_str: &OsStr = str.as_ref(); + let mut chars = os_str.encode_wide().collect::>(); + chars.push(0 /* NUL */); + + chars +} diff --git a/src-tauri/src/extension/built_in/file_search/implementation/windows/crawl_scope_manager/searchapi_h_bindings.rs b/src-tauri/src/extension/built_in/file_search/implementation/windows/crawl_scope_manager/searchapi_h_bindings.rs new file mode 100644 index 00000000..ab77a08f --- /dev/null +++ b/src-tauri/src/extension/built_in/file_search/implementation/windows/crawl_scope_manager/searchapi_h_bindings.rs @@ -0,0 +1,30 @@ +//! Rust binding of the types and functions declared in 'searchapi.h' + +#![allow(unused)] +#![allow(non_camel_case_types)] +#![allow(non_snake_case)] +#![allow(non_upper_case_globals)] +#![allow(unsafe_op_in_unsafe_fn)] +#![allow(unnecessary_transmutes)] + +include!(concat!(env!("OUT_DIR"), "/searchapi_bindings.rs")); + +// The bindings.rs contains a GUID type as well, we use the one provided by +// the windows_sys crate here. +use windows_sys::core::GUID as WIN_SYS_GUID; + +// https://github.com/search?q=CLSID_CSearchManager+language%3AC&type=code&l=C +pub(crate) static CLSID_CSEARCH_MANAGER: WIN_SYS_GUID = WIN_SYS_GUID { + data1: 0x7d096c5f, + data2: 0xac08, + data3: 0x4f1f, + data4: [0xbe, 0xb7, 0x5c, 0x22, 0xc5, 0x17, 0xce, 0x39], +}; + +// https://github.com/search?q=IID_ISearchManager+language%3AC&type=code +pub(crate) static IID_ISEARCH_MANAGER: WIN_SYS_GUID = WIN_SYS_GUID { + data1: 0xAB310581, + data2: 0xac80, + data3: 0x11d1, + data4: [0x8d, 0xf3, 0x00, 0xc0, 0x4f, 0xb6, 0xef, 0x69], +}; diff --git a/src-tauri/src/extension/built_in/file_search/implementation/windows.rs b/src-tauri/src/extension/built_in/file_search/implementation/windows/mod.rs similarity index 90% rename from src-tauri/src/extension/built_in/file_search/implementation/windows.rs rename to src-tauri/src/extension/built_in/file_search/implementation/windows/mod.rs index 485d7d0a..77c26285 100644 --- a/src-tauri/src/extension/built_in/file_search/implementation/windows.rs +++ b/src-tauri/src/extension/built_in/file_search/implementation/windows/mod.rs @@ -3,6 +3,8 @@ //! https://github.com/IRONAGE-Park/rag-sample/blob/3f0ad8c8012026cd3a7e453d08f041609426cb91/src/native/windows.rs //! is the starting point of this implementation. +mod crawl_scope_manager; + use super::super::EXTENSION_ID; use super::super::config::FileSearchConfig; use super::super::config::SearchBy; @@ -10,6 +12,8 @@ use crate::common::document::{DataSourceReference, Document}; use crate::extension::LOCAL_QUERY_SOURCE_TYPE; use crate::extension::OnOpened; use crate::util::file::sync_get_file_icon; +use std::borrow::Borrow; +use std::path::PathBuf; use windows::{ Win32::System::{ Com::{CLSCTX_INPROC_SERVER, CoCreateInstance}, @@ -468,6 +472,85 @@ pub(crate) async fn hits( Ok(hits) } +pub(crate) fn apply_config(config: &FileSearchConfig) -> Result<(), String> { + // To ensure Windows Search indexer index the paths we specified in the + // config, we will: + // + // 1. Add an inclusion rule for every search path to ensure indexer index + // them + // 2. For the exclude paths, we exclude them from the crawl scope if they + // were not included in the scope before we update the scope. Otherwise, + // we cannot exclude them as doing that could potentially break other + // apps (by removing the indexes they rely on). + // + // Windows APIs are pretty smart. They won't blindly add an inclusion rule if + // the path you are trying to include is already included. The same applies + // to exclusion rules as well. Since Windows APIs handle these checks for us, + // we don't need to worry about them. + + use crawl_scope_manager::CrawlScopeManager; + use crawl_scope_manager::Rule; + use crawl_scope_manager::RuleMode; + use std::borrow::Cow; + + /// Windows APIs need the path to contain a tailing '\' + fn add_tailing_backslash(path: &str) -> Cow<'_, str> { + if path.ends_with(r#"\"#) { + Cow::Borrowed(path) + } else { + let mut owned = path.to_string(); + owned.push_str(r#"\"#); + + Cow::Owned(owned) + } + } + + let mut manager = CrawlScopeManager::new().map_err(|e| e.to_string())?; + + let search_paths = &config.search_paths; + let exclude_paths = &config.exclude_paths; + + // indexes to `exclude_paths` of the paths we need to exclude + let mut paths_to_exclude: Vec = Vec::new(); + for (idx, exclude_path) in exclude_paths.into_iter().enumerate() { + let exclude_path = add_tailing_backslash(&exclude_path); + let exclude_path: &str = exclude_path.borrow(); + + if !manager + .is_path_included(exclude_path) + .map_err(|e| e.to_string())? + { + paths_to_exclude.push(idx); + } + } + + for search_path in search_paths { + let inclusion_rule = Rule { + paths: PathBuf::from(add_tailing_backslash(&search_path).into_owned()), + mode: RuleMode::Inclusion, + }; + + manager + .add_rule(inclusion_rule) + .map_err(|e| e.to_string())?; + } + + for idx in paths_to_exclude { + let exclusion_rule = Rule { + paths: PathBuf::from(add_tailing_backslash(&exclude_paths[idx]).into_owned()), + mode: RuleMode::Exclusion, + }; + + manager + .add_rule(exclusion_rule) + .map_err(|e| e.to_string())?; + } + + manager.commit().map_err(|e| e.to_string())?; + + Ok(()) +} + // Skip these tests in our CI, they fail with the following error // "SQL is invalid: "0x80041820"" // diff --git a/src-tauri/src/extension/built_in/mod.rs b/src-tauri/src/extension/built_in/mod.rs index c03d1be9..226aef75 100644 --- a/src-tauri/src/extension/built_in/mod.rs +++ b/src-tauri/src/extension/built_in/mod.rs @@ -16,6 +16,8 @@ use crate::extension::{ ExtensionBundleIdBorrowed, PLUGIN_JSON_FILE_NAME, alter_extension_json_file, }; use anyhow::Context; +use file_search::config::FileSearchConfig; +use file_search::implementation::apply_config as file_search_apply_config; use std::path::{Path, PathBuf}; use tauri::{AppHandle, Manager}; @@ -227,6 +229,8 @@ pub(super) async fn init_built_in_extension( search_source_registry .register_source(file_system_search) .await; + let file_search_config = FileSearchConfig::get(tauri_app_handle); + file_search_apply_config(&file_search_config)?; log::debug!("built-in extension [{}] initialized", extension.id); } @@ -329,6 +333,8 @@ pub(crate) async fn enable_built_in_extension( bundle_id, update_extension, )?; + let file_search_config = FileSearchConfig::get(tauri_app_handle); + file_search_apply_config(&file_search_config)?; return Ok(()); }