covet 2.1.0

CLI and web frontend for scanning with HP Envy printer / scanners
use bytes::Bytes;
use futures_util::stream::{Stream, StreamExt};
use jiff::Timestamp;
use tokio::fs::File;
use tokio::io::AsyncWriteExt;
use tracing::info;

use std::time::Duration;

use crate::cli::Source;
use crate::message::scan_job::{ColorSpace, Format, InputSource, ScanJob};
use crate::message::scan_status::AdfState;
use crate::scanner::{self, Scanner, ScannerError};

pub(crate) async fn scan_to_file(
    scanner: Scanner,
    format: Format,
    color: ColorSpace,
    source: Source,
    resolution: u32,
    quality: u32,
) -> Result<(), ScannerError> {
    let mut stream = scan_to_stream(&scanner, format, color, source, resolution, quality).await?;
    let file_name = scanner::output_file_name(format, &Timestamp::now());
    let mut file = File::create(file_name).await?;
    while let Some(item) = stream.next().await {
        file.write_all(item?.as_ref()).await?;
    }
    Ok(())
}

pub(crate) async fn scan_to_stream(
    scanner: &Scanner,
    format: Format,
    color: ColorSpace,
    source: Source,
    resolution: u32,
    quality: u32,
) -> Result<impl Stream<Item = Result<Bytes, reqwest::Error>> + use<>, ScannerError> {
    let status = scanner.get_scan_status().await?;
    if !status.is_idle() {
        return Err(ScannerError::Busy);
    }
    let input_source = choose_source(source, status.adf_state())?;
    let mut job = scanner
        .start_job(ScanJob::new(
            input_source,
            resolution,
            quality,
            format,
            color,
        ))
        .await?;
    info!("Job: {job:?}");
    loop {
        let ready = job.retrieve_status().await?;
        if ready {
            info!("Job: {job:?}");
            return job.download_stream().await;
        }
        tokio::time::sleep(Duration::from_millis(500)).await;
    }
}

pub(crate) fn choose_source(
    source: Source,
    adf_state: Option<AdfState>,
) -> Result<InputSource, ScannerError> {
    match adf_state {
        Some(AdfState::Loaded) => match source {
            Source::Adf | Source::Auto => Ok(InputSource::Adf),
            Source::Glass => Ok(InputSource::Platen),
        },
        _ => match source {
            Source::Adf => Err(ScannerError::AdfEmpty),
            _ => Ok(InputSource::Platen),
        },
    }
}

#[cfg(test)]
mod test {

    use super::*;

    #[test]
    fn test_choose_source() {
        assert_eq!(
            choose_source(Source::Glass, None).unwrap(),
            InputSource::Platen
        );
        assert_eq!(
            choose_source(Source::Glass, Some(AdfState::Empty)).unwrap(),
            InputSource::Platen
        );
        assert_eq!(
            choose_source(Source::Glass, Some(AdfState::Loaded)).unwrap(),
            InputSource::Platen
        );
        assert_eq!(
            choose_source(Source::Auto, None).unwrap(),
            InputSource::Platen
        );
        assert_eq!(
            choose_source(Source::Auto, Some(AdfState::Empty)).unwrap(),
            InputSource::Platen
        );
        assert_eq!(
            choose_source(Source::Auto, Some(AdfState::Loaded)).unwrap(),
            InputSource::Adf
        );
        assert!(choose_source(Source::Adf, None).is_err());
        assert!(choose_source(Source::Adf, Some(AdfState::Empty)).is_err());
        assert_eq!(
            choose_source(Source::Adf, Some(AdfState::Loaded)).unwrap(),
            InputSource::Adf
        );
    }
}