mod cache;
mod cli;
mod decode;
mod embed;
mod meta;
mod models;
mod ocr;
mod output;
mod pick;
mod search;
#[cfg(test)]
mod testing;
mod walk;
use std::{
any::Any,
cell::Cell,
panic::{self, AssertUnwindSafe},
process::ExitCode,
sync::atomic::{AtomicUsize, Ordering::Relaxed},
};
use anyhow::{Result, anyhow};
use cli::Mode;
use pick::Exit;
fn main() -> ExitCode {
let code = match run() {
Ok(code) => code,
Err(e) => {
eprintln!("scry: {e:#}");
ExitCode::from(2)
}
};
embed::gpu::stop();
code
}
fn run() -> Result<ExitCode> {
let (query, mode) = cli::parse();
hide_guarded_panics();
let search = search::run(query)?;
let (progress, skipped) = (search.progress.clone(), search.skipped);
let printed = match mode {
Mode::List(opts) => Some(output::list(search, &opts)? > 0),
Mode::Pick { view, binds, end } => match pick::run(search, view, binds)? {
Exit::Picked(paths) => {
output::paths(&paths, end)?;
Some(true)
}
Exit::Empty => Some(false),
Exit::Aborted => None,
},
};
output::notes(&progress, skipped);
let Some(printed) = printed else {
return Ok(ExitCode::from(130));
};
let failed = progress.errors.load(Relaxed) > 0;
Ok(match (printed, failed) {
(true, _) => ExitCode::SUCCESS,
(false, false) => ExitCode::FAILURE,
(false, true) => ExitCode::from(2),
})
}
thread_local! {
static GUARDS: Cell<usize> = const { Cell::new(0) };
}
static ACTIVE: AtomicUsize = AtomicUsize::new(0);
pub fn guard<T>(f: impl FnOnce() -> Result<T>) -> Result<T> {
GUARDS.set(GUARDS.get() + 1);
ACTIVE.fetch_add(1, Relaxed);
let result = catch(f);
ACTIVE.fetch_sub(1, Relaxed);
GUARDS.set(GUARDS.get() - 1);
result
}
pub fn catch<T>(f: impl FnOnce() -> Result<T>) -> Result<T> {
panic::catch_unwind(AssertUnwindSafe(f))
.unwrap_or_else(|payload| Err(anyhow!("crashed: {}", message(&*payload))))
}
fn message(payload: &(dyn Any + Send)) -> &str {
(payload.downcast_ref::<&str>().copied())
.or_else(|| payload.downcast_ref::<String>().map(String::as_str))
.unwrap_or("unknown panic")
}
fn hide_guarded_panics() {
let show = panic::take_hook();
panic::set_hook(Box::new(move |info| {
if !guarded() {
show(info);
}
}));
}
pub fn guarded() -> bool {
GUARDS.get() > 0 || rayon::current_thread_index().is_some() && ACTIVE.load(Relaxed) > 0
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn guard_turns_panics_into_errors() {
assert_eq!(guard(|| Ok(1)).unwrap(), 1);
let err = guard::<()>(|| panic!("bad {}", "pixels")).unwrap_err();
assert_eq!(err.to_string(), "crashed: bad pixels");
assert!(!guarded());
}
}