liblitho 0.2.0

cli tool to flash/clone the images to storage devices
Documentation
use crate::progress::{check_cancel, OperationPhase, OperationProgress};
use anyhow::Result;
use std::sync::atomic::AtomicBool;
use std::thread;
use std::time::Duration;

const SIMULATED_TOTAL_BYTES: u64 = 1_024 * 1_024 * 512; // 512 MiB
const SIMULATED_STEPS: u64 = 20;

/// Simulated flash for CLI output-mode testing (no real block I/O).
pub fn simulate_flash<F>(
    image: &str,
    device: &str,
    block_size: usize,
    silent: bool,
    verify: bool,
    mut progress: Option<F>,
    cancel: Option<&AtomicBool>,
) -> Result<()>
where
    F: FnMut(OperationProgress),
{
    emit(
        silent,
        &mut progress,
        OperationProgress::new(OperationPhase::Preparing)
            .with_message(format!("Opening image {image} (simulated)")),
    );

    check_cancel(cancel)?;
    thread::sleep(Duration::from_millis(80));
    check_cancel(cancel)?;

    for step in 1..=SIMULATED_STEPS {
        check_cancel(cancel)?;
        let bytes = SIMULATED_TOTAL_BYTES * step / SIMULATED_STEPS;
        let pct = if verify {
            (bytes as f64 / SIMULATED_TOTAL_BYTES as f64) * 85.0
        } else {
            (bytes as f64 / SIMULATED_TOTAL_BYTES as f64) * 90.0
        };
        emit(
            silent,
            &mut progress,
            OperationProgress::new(OperationPhase::Writing)
                .with_bytes(bytes, Some(SIMULATED_TOTAL_BYTES))
                .with_percentage(pct),
        );
        thread::sleep(Duration::from_millis(40));
    }

    let (sync_start, sync_end) = if verify { (85.0, 95.0) } else { (90.0, 100.0) };
    for step in 1..=5 {
        check_cancel(cancel)?;
        let synced = SIMULATED_TOTAL_BYTES * step / 5;
        let fraction = synced as f64 / SIMULATED_TOTAL_BYTES as f64;
        let pct = sync_start + fraction * (sync_end - sync_start);
        emit(
            silent,
            &mut progress,
            OperationProgress::new(OperationPhase::Syncing)
                .with_bytes(synced, Some(SIMULATED_TOTAL_BYTES))
                .with_percentage(pct)
                .with_message("Syncing data to device (simulated)"),
        );
        thread::sleep(Duration::from_millis(40));
    }

    if verify {
        check_cancel(cancel)?;
        emit(
            silent,
            &mut progress,
            OperationProgress::new(OperationPhase::Verifying)
                .with_percentage(95.0)
                .with_message("Verifying checksum (simulated)"),
        );

        for step in 1..=5 {
            check_cancel(cancel)?;
            let verified = SIMULATED_TOTAL_BYTES * step / 5;
            let pct = 95.0 + (verified as f64 / SIMULATED_TOTAL_BYTES as f64) * 5.0;
            emit(
                silent,
                &mut progress,
                OperationProgress::new(OperationPhase::Verifying)
                    .with_bytes(verified, Some(SIMULATED_TOTAL_BYTES))
                    .with_percentage(pct.min(99.9)),
            );
            thread::sleep(Duration::from_millis(40));
        }
    }

    check_cancel(cancel)?;
    emit(
        silent,
        &mut progress,
        OperationProgress::new(OperationPhase::Complete)
            .with_bytes(SIMULATED_TOTAL_BYTES, Some(SIMULATED_TOTAL_BYTES))
            .with_percentage(100.0)
            .with_message(format!(
                "Simulated flash of {image} to {device} (block_size={block_size}, verify={verify})"
            )),
    );

    Ok(())
}

/// Simulated clone for CLI output-mode testing (no real block I/O).
pub fn simulate_clone<F>(
    device: &str,
    file: &str,
    block_size: usize,
    silent: bool,
    mut progress: Option<F>,
    cancel: Option<&AtomicBool>,
) -> Result<()>
where
    F: FnMut(OperationProgress),
{
    emit(
        silent,
        &mut progress,
        OperationProgress::new(OperationPhase::Preparing)
            .with_message(format!("Opening {device} (simulated)")),
    );

    check_cancel(cancel)?;
    thread::sleep(Duration::from_millis(80));
    check_cancel(cancel)?;

    for step in 1..=SIMULATED_STEPS {
        check_cancel(cancel)?;
        let bytes = SIMULATED_TOTAL_BYTES * step / SIMULATED_STEPS;
        let pct = (bytes as f64 / SIMULATED_TOTAL_BYTES as f64) * 100.0;
        emit(
            silent,
            &mut progress,
            OperationProgress::new(OperationPhase::Writing)
                .with_bytes(bytes, Some(SIMULATED_TOTAL_BYTES))
                .with_percentage(pct),
        );
        thread::sleep(Duration::from_millis(40));
    }

    check_cancel(cancel)?;
    emit(
        silent,
        &mut progress,
        OperationProgress::new(OperationPhase::Complete)
            .with_bytes(SIMULATED_TOTAL_BYTES, Some(SIMULATED_TOTAL_BYTES))
            .with_percentage(100.0)
            .with_message(format!(
                "Simulated clone of {device} to {file} (block_size={block_size})"
            )),
    );

    Ok(())
}

fn emit<F>(silent: bool, progress: &mut Option<F>, event: OperationProgress)
where
    F: FnMut(OperationProgress),
{
    if silent {
        return;
    }
    if let Some(callback) = progress {
        callback(event);
    }
}