Async file scan and adding additional paths
This commit is contained in:
parent
d8aa3cf267
commit
e5efca4689
13
Cargo.lock
generated
13
Cargo.lock
generated
@ -968,6 +968,18 @@ dependencies = [
|
|||||||
"mio",
|
"mio",
|
||||||
"pin-project-lite",
|
"pin-project-lite",
|
||||||
"slab",
|
"slab",
|
||||||
|
"tokio-macros",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "tokio-macros"
|
||||||
|
version = "2.5.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "6e06d43f1345a3bcd39f6a56dbb7dcab2ba47e68e8ac134855e7e2bdbaf8cab8"
|
||||||
|
dependencies = [
|
||||||
|
"proc-macro2",
|
||||||
|
"quote",
|
||||||
|
"syn",
|
||||||
]
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
@ -1151,6 +1163,7 @@ dependencies = [
|
|||||||
"gtk4-layer-shell",
|
"gtk4-layer-shell",
|
||||||
"relm4",
|
"relm4",
|
||||||
"relm4-components",
|
"relm4-components",
|
||||||
|
"tokio",
|
||||||
"tracker",
|
"tracker",
|
||||||
"walkdir",
|
"walkdir",
|
||||||
]
|
]
|
||||||
|
@ -11,5 +11,6 @@ gtk = { version = "0.10.0", package = "gtk4" }
|
|||||||
gtk4-layer-shell = "0.6.1"
|
gtk4-layer-shell = "0.6.1"
|
||||||
relm4 = "0.10.0"
|
relm4 = "0.10.0"
|
||||||
relm4-components = "0.10.0"
|
relm4-components = "0.10.0"
|
||||||
|
tokio = { version = "1.0", features = ["rt", "time", "macros"] }
|
||||||
tracker = "0.2.2"
|
tracker = "0.2.2"
|
||||||
walkdir = "2.5.0"
|
walkdir = "2.5.0"
|
||||||
|
@ -10,10 +10,17 @@ fn main() {
|
|||||||
.build();
|
.build();
|
||||||
|
|
||||||
app.connect_activate(|app| {
|
app.connect_activate(|app| {
|
||||||
|
let mut file_search_plugin = plugins::file_search::FileSearchPlugin::new();
|
||||||
|
|
||||||
|
match file_search_plugin.add_search_path("/home/javi/working-files/DJ Music/") {
|
||||||
|
Err(e) => eprintln!("{}", e),
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
|
||||||
// Create the core launcher
|
// Create the core launcher
|
||||||
let launcher = WaycastLauncher::new()
|
let launcher = WaycastLauncher::new()
|
||||||
.add_plugin(Box::new(plugins::drun::DrunPlugin {}))
|
.add_plugin(Box::new(plugins::drun::DrunPlugin {}))
|
||||||
.add_plugin(Box::new(plugins::file_search::FileSearchPlugin::new()))
|
.add_plugin(Box::new(file_search_plugin))
|
||||||
.init();
|
.init();
|
||||||
|
|
||||||
// Create and show the GTK UI
|
// Create and show the GTK UI
|
||||||
|
@ -1,8 +1,10 @@
|
|||||||
use directories::UserDirs;
|
use directories::UserDirs;
|
||||||
use gio::prelude::FileExt;
|
use gio::prelude::FileExt;
|
||||||
use glib::object::Cast;
|
use glib::object::Cast;
|
||||||
use std::cell::RefCell;
|
use std::path::{Path, PathBuf};
|
||||||
use std::path::PathBuf;
|
use std::sync::Arc;
|
||||||
|
use std::time::Duration;
|
||||||
|
use tokio::sync::Mutex;
|
||||||
use walkdir::{DirEntry, WalkDir};
|
use walkdir::{DirEntry, WalkDir};
|
||||||
|
|
||||||
use crate::{LaunchError, LauncherListItem, LauncherPlugin};
|
use crate::{LaunchError, LauncherListItem, LauncherPlugin};
|
||||||
@ -66,26 +68,98 @@ impl LauncherListItem for FileEntry {
|
|||||||
// $HOME/Documents/wallpapers
|
// $HOME/Documents/wallpapers
|
||||||
// Then we should just keep $HOME/Documents since wallpapers
|
// Then we should just keep $HOME/Documents since wallpapers
|
||||||
// will be included in it anyways
|
// will be included in it anyways
|
||||||
|
|
||||||
|
pub fn default_search_list() -> Vec<PathBuf> {
|
||||||
|
if let Some(ud) = UserDirs::new() {
|
||||||
|
let mut paths: Vec<PathBuf> = Vec::new();
|
||||||
|
let user_dirs = [
|
||||||
|
ud.document_dir(),
|
||||||
|
ud.picture_dir(),
|
||||||
|
ud.audio_dir(),
|
||||||
|
ud.video_dir(),
|
||||||
|
];
|
||||||
|
|
||||||
|
for d in user_dirs {
|
||||||
|
if let Some(path) = d {
|
||||||
|
paths.push(path.to_path_buf());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return paths;
|
||||||
|
}
|
||||||
|
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
pub struct FileSearchPlugin {
|
pub struct FileSearchPlugin {
|
||||||
search_paths: Vec<PathBuf>,
|
search_paths: Vec<PathBuf>,
|
||||||
skip_dirs: Vec<String>,
|
skip_dirs: Vec<String>,
|
||||||
// Running list of files in memory
|
// Running list of files in memory
|
||||||
files: RefCell<Vec<FileEntry>>,
|
files: Arc<Mutex<Vec<FileEntry>>>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FileSearchPlugin {
|
impl FileSearchPlugin {
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
return FileSearchPlugin {
|
return FileSearchPlugin {
|
||||||
search_paths: Vec::new(),
|
search_paths: default_search_list(),
|
||||||
skip_dirs: vec![
|
skip_dirs: vec![
|
||||||
String::from("vendor"),
|
String::from("vendor"),
|
||||||
String::from("node_modules"),
|
String::from("node_modules"),
|
||||||
String::from("cache"),
|
String::from("cache"),
|
||||||
String::from("zig-cache"),
|
String::from("zig-cache"),
|
||||||
],
|
],
|
||||||
files: RefCell::new(Vec::new()),
|
files: Arc::new(Mutex::new(Vec::new())),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn add_search_path<P: AsRef<Path>>(&mut self, path: P) -> Result<(), String> {
|
||||||
|
let p = path.as_ref();
|
||||||
|
|
||||||
|
if !p.exists() {
|
||||||
|
return Err(format!("Path does not exist: {}", p.display()));
|
||||||
|
}
|
||||||
|
|
||||||
|
if !p.is_dir() {
|
||||||
|
return Err(format!("Path is not a directory: {}", p.display()));
|
||||||
|
}
|
||||||
|
|
||||||
|
self.search_paths.push(p.to_path_buf());
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
async fn init_with_timeout(&self, timeout: Duration) {
|
||||||
|
let files_clone = Arc::clone(&self.files);
|
||||||
|
let skip_dirs_clone = self.skip_dirs.clone();
|
||||||
|
|
||||||
|
let scan_task = async move {
|
||||||
|
let mut local_files = Vec::new();
|
||||||
|
|
||||||
|
for path in &self.search_paths {
|
||||||
|
let walker = WalkDir::new(path).into_iter();
|
||||||
|
for entry in walker
|
||||||
|
.filter_entry(|e| !skip_hidden(e) && !skip_dir(e, &skip_dirs_clone))
|
||||||
|
.filter_map(|e| e.ok())
|
||||||
|
{
|
||||||
|
if entry.path().is_file() {
|
||||||
|
local_files.push(FileEntry::from(entry));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Yield control periodically to check for timeout
|
||||||
|
if local_files.len() % 1000 == 0 {
|
||||||
|
tokio::task::yield_now().await;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update the shared files collection
|
||||||
|
let mut files_guard = files_clone.lock().await;
|
||||||
|
*files_guard = local_files;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Run the scan with a timeout
|
||||||
|
if let Err(_) = tokio::time::timeout(timeout, scan_task).await {
|
||||||
|
eprintln!("File indexing timed out after {:?}", timeout);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn skip_hidden(entry: &DirEntry) -> bool {
|
fn skip_hidden(entry: &DirEntry) -> bool {
|
||||||
@ -106,32 +180,21 @@ fn skip_dir(entry: &DirEntry, dirs: &Vec<String>) -> bool {
|
|||||||
|
|
||||||
impl LauncherPlugin for FileSearchPlugin {
|
impl LauncherPlugin for FileSearchPlugin {
|
||||||
fn init(&self) {
|
fn init(&self) {
|
||||||
// let home = env::home_dir().unwrap();
|
// Start async file scanning with 500ms timeout
|
||||||
if let Some(ud) = UserDirs::new() {
|
let self_clone = FileSearchPlugin {
|
||||||
let scan = [
|
search_paths: self.search_paths.clone(),
|
||||||
ud.document_dir(),
|
skip_dirs: self.skip_dirs.clone(),
|
||||||
ud.picture_dir(),
|
files: Arc::clone(&self.files),
|
||||||
ud.audio_dir(),
|
};
|
||||||
ud.video_dir(),
|
|
||||||
];
|
|
||||||
|
|
||||||
for p in scan {
|
std::thread::spawn(move || {
|
||||||
match p {
|
let rt = tokio::runtime::Runtime::new().unwrap();
|
||||||
Some(path) => {
|
rt.block_on(async {
|
||||||
let walker = WalkDir::new(path).into_iter();
|
self_clone
|
||||||
for entry in walker
|
.init_with_timeout(Duration::from_millis(2000))
|
||||||
.filter_entry(|e| !skip_hidden(e) && !skip_dir(e, &self.skip_dirs))
|
.await;
|
||||||
.filter_map(|e| e.ok())
|
});
|
||||||
{
|
});
|
||||||
if entry.path().is_file() {
|
|
||||||
self.files.borrow_mut().push(FileEntry::from(entry));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
None => continue,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
fn name(&self) -> String {
|
fn name(&self) -> String {
|
||||||
return String::from("File search");
|
return String::from("File search");
|
||||||
@ -163,12 +226,15 @@ impl LauncherPlugin for FileSearchPlugin {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new();
|
let mut entries: Vec<Box<dyn LauncherListItem>> = Vec::new();
|
||||||
let files = self.files.borrow();
|
|
||||||
for f in files.iter() {
|
// Try to get files without blocking - if indexing is still in progress, return empty
|
||||||
if let Some(file_name) = f.path.file_name() {
|
if let Ok(files) = self.files.try_lock() {
|
||||||
let cmp = file_name.to_string_lossy().to_lowercase();
|
for f in files.iter() {
|
||||||
if cmp.contains(&query.to_lowercase()) {
|
if let Some(file_name) = f.path.file_name() {
|
||||||
entries.push(Box::new(f.clone()));
|
let cmp = file_name.to_string_lossy().to_lowercase();
|
||||||
|
if cmp.contains(&query.to_lowercase()) {
|
||||||
|
entries.push(Box::new(f.clone()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user