rebecca-core 0.3.0

Core planning, safety, scanning, and history models for Rebecca.
Documentation
use std::path::Path;

use crate::error::Result;
use crate::scan::ScanBackendKind;

pub type InspectProgressResult = Result<()>;

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub enum InspectProgressDetail {
    #[default]
    Target,
    File,
}

impl InspectProgressDetail {
    pub const fn includes_file_events(self) -> bool {
        matches!(self, Self::File)
    }
}

#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct InspectProgressOptions {
    detail: InspectProgressDetail,
}

impl InspectProgressOptions {
    pub const fn target() -> Self {
        Self {
            detail: InspectProgressDetail::Target,
        }
    }

    pub const fn file() -> Self {
        Self {
            detail: InspectProgressDetail::File,
        }
    }

    pub const fn detail(self) -> InspectProgressDetail {
        self.detail
    }

    pub const fn includes_file_events(self) -> bool {
        self.detail.includes_file_events()
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InspectProgressRootStatus {
    Scanned,
    Skipped,
}

impl InspectProgressRootStatus {
    pub const fn label(self) -> &'static str {
        match self {
            Self::Scanned => "scanned",
            Self::Skipped => "skipped",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InspectProgressCacheEvent {
    Hit,
    Miss,
    WriteSkipped,
}

impl InspectProgressCacheEvent {
    pub const fn label(self) -> &'static str {
        match self {
            Self::Hit => "hit",
            Self::Miss => "miss",
            Self::WriteSkipped => "write-skipped",
        }
    }
}

#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum InspectProgressCounterKind {
    Files,
    Directories,
    Bytes,
    Records,
}

impl InspectProgressCounterKind {
    pub const fn label(self) -> &'static str {
        match self {
            Self::Files => "files",
            Self::Directories => "directories",
            Self::Bytes => "bytes",
            Self::Records => "records",
        }
    }
}

#[derive(Debug, Clone, Copy)]
pub enum InspectProgressEvent<'a> {
    RootStarted {
        root_index: usize,
        root_count: usize,
        root: &'a Path,
        backend: ScanBackendKind,
    },
    RootFinished {
        root_index: usize,
        root_count: usize,
        root: &'a Path,
        status: InspectProgressRootStatus,
        logical_bytes: u64,
        files: u64,
        directories: u64,
    },
    EntryStarted {
        root: &'a Path,
        path: &'a Path,
        entry_index: u64,
        backend: ScanBackendKind,
    },
    EntryMeasured {
        root: &'a Path,
        path: &'a Path,
        entry_index: u64,
        logical_bytes: u64,
        files: u64,
        directories: u64,
    },
    FileMeasured {
        root: &'a Path,
        target_path: &'a Path,
        path: &'a Path,
        file_size: u64,
        files_scanned: u64,
        bytes_scanned: u64,
    },
    TraversalProgress {
        root: &'a Path,
        counter: InspectProgressCounterKind,
        value: u64,
        logical_bytes: u64,
        files: u64,
        directories: u64,
    },
    BackendFallback {
        root: &'a Path,
        backend: ScanBackendKind,
        reason: &'a str,
    },
    BackendStageStarted {
        root: &'a Path,
        backend: ScanBackendKind,
        stage: &'static str,
    },
    BackendStageFinished {
        root: &'a Path,
        backend: ScanBackendKind,
        stage: &'static str,
    },
    BackendMetric {
        root: &'a Path,
        backend: ScanBackendKind,
        metric: &'static str,
        value: u64,
    },
    CacheEvent {
        path: &'a Path,
        event: InspectProgressCacheEvent,
        reason: Option<&'static str>,
        estimated_bytes: Option<u64>,
    },
    Finalizing {
        roots: usize,
        logical_bytes: u64,
        files: u64,
        directories: u64,
    },
}

#[derive(Debug, Clone)]
pub(crate) struct PowerOfTwoProgressSampler {
    next_files: u64,
    next_directories: u64,
    next_bytes: u64,
    next_records: u64,
}

impl Default for PowerOfTwoProgressSampler {
    fn default() -> Self {
        Self {
            next_files: 1,
            next_directories: 1,
            next_bytes: 1,
            next_records: 1,
        }
    }
}

impl PowerOfTwoProgressSampler {
    pub(crate) fn should_emit(&mut self, counter: InspectProgressCounterKind, value: u64) -> bool {
        let next = match counter {
            InspectProgressCounterKind::Files => &mut self.next_files,
            InspectProgressCounterKind::Directories => &mut self.next_directories,
            InspectProgressCounterKind::Bytes => &mut self.next_bytes,
            InspectProgressCounterKind::Records => &mut self.next_records,
        };
        if value < *next {
            return false;
        }

        while *next <= value {
            *next = next.saturating_mul(2).max(next.saturating_add(1));
        }
        true
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn power_of_two_sampler_emits_at_milestones() {
        let mut sampler = PowerOfTwoProgressSampler::default();

        assert!(sampler.should_emit(InspectProgressCounterKind::Files, 1));
        assert!(sampler.should_emit(InspectProgressCounterKind::Files, 2));
        assert!(!sampler.should_emit(InspectProgressCounterKind::Files, 3));
        assert!(sampler.should_emit(InspectProgressCounterKind::Files, 4));
        assert!(sampler.should_emit(InspectProgressCounterKind::Files, 9));
        assert!(!sampler.should_emit(InspectProgressCounterKind::Files, 10));
    }
}