mirror of
https://codeberg.org/unspeaker/perch.git
synced 2025-12-06 09:36:42 +01:00
108 lines
3.7 KiB
Rust
108 lines
3.7 KiB
Rust
#![allow(stable_features)]
|
|
#![feature(os_str_display)]
|
|
#![feature(str_as_str)]
|
|
|
|
use std::sync::{Arc, RwLock};
|
|
use std::path::{Path, PathBuf};
|
|
use std::env::{current_dir, set_current_dir};
|
|
use std::thread::{sleep, spawn, JoinHandle};
|
|
use std::time::Duration;
|
|
|
|
use tengri::{input::*, output::*, tui::*};
|
|
use crate::crossterm::event::{Event, KeyEvent, KeyCode, KeyModifiers, KeyEventState, KeyEventKind};
|
|
|
|
use clap::{arg, command, value_parser};
|
|
use walkdir::WalkDir;
|
|
use xxhash_rust::xxh3::xxh3_64;
|
|
use file_type::FileType;
|
|
|
|
mod keys;
|
|
mod view; use self::view::*;
|
|
mod model; pub(crate) use self::model::*;
|
|
|
|
pub(crate) type Usually<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
|
pub(crate) type Perhaps<T> = Usually<Option<T>>;
|
|
|
|
pub(crate) const PAGE_SIZE: usize = 10;
|
|
|
|
fn cli () -> clap::Command {
|
|
command!()
|
|
.arg(arg!([path] "Path to root directory")
|
|
.value_parser(value_parser!(PathBuf)))
|
|
.arg(arg!(-j --threads <N> "Number of indexing threads")
|
|
.required(false)
|
|
.value_parser(value_parser!(usize)))
|
|
.arg(arg!(-c --check "Only index content, don't open editor")
|
|
.required(false)
|
|
.value_parser(value_parser!(bool)))
|
|
}
|
|
|
|
fn main () -> Usually<()> {
|
|
let args = cli().get_matches();
|
|
|
|
let path = if let Some(path) = args.get_one::<PathBuf>("path") {
|
|
path.into()
|
|
} else {
|
|
current_dir()?
|
|
};
|
|
set_current_dir(&path)?;
|
|
|
|
let threads = args.get_one::<usize>("threads").map(|x|*x).unwrap_or(4);
|
|
let results = collect(&path, threads)?;
|
|
|
|
if let Ok(results) = Arc::try_unwrap(results) {
|
|
let mut results = results.into_inner()?;
|
|
results.sort();
|
|
let state = Arc::new(RwLock::new(Taggart::new(&path, results)?));
|
|
if let Some(true) = args.get_one::<bool>("check") {
|
|
return Ok(())
|
|
}
|
|
Tui::new()?.run(&state)
|
|
} else {
|
|
panic!("read did not finish")
|
|
}
|
|
}
|
|
|
|
fn collect (root: &impl AsRef<Path>, thread_count: usize) -> Usually<Arc<RwLock<Vec<Entry>>>> {
|
|
let results = Arc::new(RwLock::new(vec![]));
|
|
let entries = Arc::new(RwLock::new(WalkDir::new(&root).into_iter()
|
|
.filter_entry(|e|!e.file_name().to_str().map(|s|s.starts_with(".")).unwrap_or(false))));
|
|
let mut threads: Vec<JoinHandle<()>> = vec![];
|
|
for thread_id in (0..thread_count).map(|x|x+1) {
|
|
threads.push({
|
|
let root = root.as_ref().to_path_buf().clone();
|
|
let results = results.clone();
|
|
let entries = entries.clone();
|
|
spawn(move || loop {
|
|
let (path, depth) = {
|
|
let entry = entries.write().unwrap().next();
|
|
if let Some(entry) = entry {
|
|
let entry = entry.expect("failed to walk entry");
|
|
let path = entry.path().to_path_buf();
|
|
let depth = entry.depth();
|
|
(path, depth)
|
|
} else {
|
|
break
|
|
}
|
|
};
|
|
if depth > 0 {
|
|
let short_path = path.strip_prefix(root.as_path())
|
|
.expect("failed to strip prefix");
|
|
println!("(thread {thread_id}) {}", short_path.display());
|
|
if let Ok(Some(entry)) = Entry::new(&root, &path, depth) {
|
|
results.write().unwrap().push(entry);
|
|
}
|
|
}
|
|
})
|
|
});
|
|
}
|
|
let timer = Duration::from_millis(100);
|
|
let _ = spawn(move || loop {
|
|
if threads.iter().all(|x|x.is_finished()) {
|
|
break
|
|
} else {
|
|
sleep(timer)
|
|
}
|
|
}).join();
|
|
Ok(results)
|
|
}
|