use std::path::PathBuf;
use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
use std::sync::{Arc, Mutex};
use std::time::Instant;
use crate::entry::{Entry, SortOrder};
use crate::scan::{HardlinkPolicy, ScanCounts};
pub(super) const STALE_DAYS: u32 = 90;
pub(super) struct ScanState {
pub(super) started_at: Instant,
pub(super) counts: Arc<ScanCounts>,
pub(super) scan_id: u64,
pub(super) inner: Inner,
}
pub(super) enum Inner {
Scanning,
Ready { tree: Entry, scanned_in_ms: u64 },
Error(String),
}
impl ScanState {
fn scanning(scan_id: u64) -> Self {
Self {
started_at: Instant::now(),
counts: Arc::new(ScanCounts::default()),
scan_id,
inner: Inner::Scanning,
}
}
}
pub(super) struct AppState {
pub(super) scan_root: PathBuf,
pub(super) sort: SortOrder,
pub(super) reverse: bool,
pub(super) hardlinks: HardlinkPolicy,
next_scan_id: AtomicU64,
scan_in_flight: AtomicBool,
pub(super) state: Mutex<ScanState>,
}
impl AppState {
pub(super) fn new(
scan_root: PathBuf,
sort: SortOrder,
reverse: bool,
hardlinks: HardlinkPolicy,
) -> Self {
Self {
scan_root,
sort,
reverse,
hardlinks,
next_scan_id: AtomicU64::new(1),
scan_in_flight: AtomicBool::new(false),
state: Mutex::new(ScanState::scanning(0)),
}
}
}
pub(super) fn start_scan(state: Arc<AppState>) {
if state
.scan_in_flight
.compare_exchange(false, true, Ordering::Acquire, Ordering::Relaxed)
.is_err()
{
return;
}
let guard = InFlightGuard(Arc::clone(&state));
let scan_id = state.next_scan_id.fetch_add(1, Ordering::Relaxed);
let fresh = ScanState::scanning(scan_id);
let counts = Arc::clone(&fresh.counts);
{
let mut s = state.state.lock().unwrap();
*s = fresh;
}
tokio::task::spawn_blocking(move || {
let _guard = guard;
let started = Instant::now();
let scan_result =
crate::scan::scan_with_progress(&state.scan_root, &counts, state.hardlinks);
let next_inner = match scan_result {
Ok(mut tree) => {
tree.sort(&state.sort, state.reverse);
let scanned_in_ms = started.elapsed().as_millis() as u64;
Inner::Ready {
tree,
scanned_in_ms,
}
}
Err(e) => Inner::Error(e.to_string()),
};
let mut s = state.state.lock().unwrap();
if s.scan_id == scan_id {
s.inner = next_inner;
}
});
}
struct InFlightGuard(Arc<AppState>);
impl Drop for InFlightGuard {
fn drop(&mut self) {
self.0.scan_in_flight.store(false, Ordering::Release);
}
}