sett 0.4.0

Rust port of sett (data compression, encryption and transfer tool).
Documentation
//! Abstraction of progress displays in CLI and GUI
//!
//! Display providers (GUI, CLI) should implement [ProgressDisplay].
//! Code using this abstraction can conveniently use [ProgressDisplay::start]
//! to let the display be automatically controlled by [ProgressTask].

/// Progress tracking API for a provider (i.e. GUI, CLI) of a progress user feedback
pub trait ProgressDisplay {
    /// Increments the current progress completion.
    fn increment(&mut self, delta: u64);

    /// Set the completion value of the display.
    fn set_completion_value(&mut self, position: u64);

    /// Finalizes the progress bar.
    fn finish(&mut self);

    /// Start the progress display
    fn start(self, completion_value: u64) -> ProgressTask<Self>
    where
        Self: Sized,
    {
        ProgressTask::new(self, completion_value)
    }
}

/// Controls a [ProgressDisplay] object from the beginning of
/// process tracking and automatically completes the progress display when dropped
pub struct ProgressTask<P: ProgressDisplay> {
    progress: P,
}

impl<P: ProgressDisplay> Drop for ProgressTask<P> {
    fn drop(&mut self) {
        self.progress.finish();
    }
}

impl<P: ProgressDisplay> ProgressTask<P> {
    /// Start a new progress task, automatically finishing the task when this object is dropped
    pub fn new(mut progress: P, completion_value: u64) -> Self {
        progress.set_completion_value(completion_value);
        Self { progress }
    }

    /// Increments the underlying progress completion.
    pub fn increment(&mut self, delta: u64) {
        self.progress.increment(delta);
    }

    pub(super) fn wrap_reader<R>(&mut self, reader: R) -> ProgressReader<R, &mut Self> {
        ProgressReader {
            reader,
            progress_task: self,
        }
    }
}

/// Runs a callback function while reading through a reader to
/// report the current progress.
pub(super) struct ProgressReader<R, P> {
    reader: R,
    progress_task: P,
}

impl<R, P> ProgressReader<R, P> {
    pub(super) fn new(reader: R, progress_task: P) -> Self {
        Self {
            reader,
            progress_task,
        }
    }
}

impl<R: std::io::Read, P: IncrementProgress> std::io::Read for ProgressReader<R, P> {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        let read = self.reader.read(buf)?;
        self.progress_task
            .increment(read.try_into().map_err(std::io::Error::other)?);
        Ok(read)
    }
}

/// Helper trait to make both [ProgressTask] and `&mut [ProgressTask]` usable by [ProgressReader]
trait IncrementProgress {
    /// Increments the current progress completion
    fn increment(&mut self, delta: u64);
}

impl<P: ProgressDisplay> IncrementProgress for ProgressTask<P> {
    fn increment(&mut self, delta: u64) {
        self.progress.increment(delta);
    }
}

impl<P: ProgressDisplay> IncrementProgress for &mut ProgressTask<P> {
    fn increment(&mut self, delta: u64) {
        self.progress.increment(delta);
    }
}

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

    #[derive(Debug, Default)]
    struct MockProgress {
        pos: u64,
        completion_value: Option<u64>,
        has_completed: bool,
        increment_after_completion: bool,
    }

    impl MockProgress {
        fn new() -> Self {
            Default::default()
        }
    }

    impl ProgressDisplay for &mut MockProgress {
        fn set_completion_value(&mut self, value: u64) {
            self.completion_value = Some(value);
        }
        fn increment(&mut self, delta: u64) {
            self.pos += delta;
            if self.has_completed {
                self.increment_after_completion = true;
            }
        }
        fn finish(&mut self) {
            self.has_completed = true;
        }
    }

    #[derive(Debug, Default)]
    struct MockReader {
        buf: Vec<u8>,
        chunk_size: usize,
        buf_pos: usize,
    }

    impl MockReader {
        fn new(buf: Vec<u8>) -> Self {
            Self {
                buf,
                ..Default::default()
            }
        }
    }

    impl std::io::Read for MockReader {
        fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
            let end = usize::max(self.buf_pos + self.chunk_size, self.buf.len());
            let chunk = &self.buf[self.buf_pos..end];
            buf[..end - self.buf_pos].clone_from_slice(chunk);
            self.buf_pos += chunk.len();
            Ok(chunk.len())
        }
    }

    #[test]
    fn test_progress_tasks_completes_display() {
        let mut progress = MockProgress::new();
        {
            let mut task = (&mut progress).start(10);
            task.increment(1);
        }
        assert!(!progress.increment_after_completion);
        (&mut progress).increment(1);
        assert!(progress.increment_after_completion);
    }

    #[test]
    fn test_no_increment_after_completion_check_works() {
        let mut progress = MockProgress::new();
        {
            let task = (&mut progress).start(10);
            let reader = MockReader::new(vec![0; 10]);
            let mut progress_reader = ProgressReader::new(reader, task);
            let mut buffer = Vec::new();
            use std::io::Read;
            progress_reader.read_to_end(&mut buffer).unwrap();
        }
        assert!(progress.has_completed);
        assert_eq!(progress.completion_value, Some(10));
    }

    #[test]
    fn test_progress_task_does_not_complete_during_move() {
        let mut progress = MockProgress::new();
        {
            let task = (&mut progress).start(10);
            let reader = MockReader::new(vec![0; 9]);
            let mut progress_reader = ProgressReader::new(reader, task);
            let mut buffer = Vec::new();
            use std::io::Read;
            progress_reader.read_to_end(&mut buffer).unwrap();
        }
        assert!(!progress.increment_after_completion);
        assert!(progress.has_completed);
        assert_eq!(progress.completion_value, Some(10));
        assert_eq!(progress.pos, 9);
    }
}