conserve 23.1.1

A robust backup tool.
Documentation
// Conserve backup system.
// Copyright 2017, 2018, 2019, 2020, 2022 Martin Pool.

// This program is free software; you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation; either version 2 of the License, or
// (at your option) any later version.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.

//! Abstract Tree trait.

use std::ops::Range;

use crate::stats::Sizes;
use crate::*;

/// Abstract Tree that may be either on the real filesystem or stored in an archive.
pub trait ReadTree {
    type Entry: Entry + 'static;
    type R: std::io::Read;
    type IT: Iterator<Item = Self::Entry>;

    /// Iterate, in apath order, all the entries in this tree.
    ///
    /// Errors reading individual paths or directories are sent to the UI and
    /// counted, but are not treated as fatal, and don't appear as Results in the
    /// iterator.
    fn iter_entries(&self, subtree: Apath, exclude: Exclude) -> Result<Self::IT>;

    /// Read file contents as a `std::io::Read`.
    // TODO: Remove this and use ReadBlocks or similar.
    fn file_contents(&self, entry: &Self::Entry) -> Result<Self::R>;

    /// Estimate the number of entries in the tree.
    /// This might do somewhat expensive IO, so isn't the Iter's `size_hint`.
    fn estimate_count(&self) -> Result<u64>;

    /// Measure the tree size.
    ///
    /// This typically requires walking all entries, which may take a while.
    fn size(&self, exclude: Exclude) -> Result<TreeSize> {
        struct Model {
            files: usize,
            total_bytes: u64,
        }
        impl nutmeg::Model for Model {
            fn render(&mut self, _width: usize) -> String {
                format!(
                    "Measuring... {} files, {} MB",
                    self.files,
                    self.total_bytes / 1_000_000
                )
            }
        }
        let progress = nutmeg::View::new(
            Model {
                files: 0,
                total_bytes: 0,
            },
            ui::nutmeg_options(),
        );
        let mut tot = 0u64;
        for e in self.iter_entries(Apath::root(), exclude)? {
            // While just measuring size, ignore directories/files we can't stat.
            if let Some(bytes) = e.size() {
                tot += bytes;
                progress.update(|model| {
                    model.files += 1;
                    model.total_bytes += bytes;
                });
            }
        }
        Ok(TreeSize { file_bytes: tot })
    }
}

/// Read a file as a series of blocks of bytes.
///
/// When reading from the archive, the blocks are whatever size is stored.
/// When reading from the filesystem they're MAX_BLOCK_SIZE. But the caller
/// shouldn't assume the size.
pub trait ReadBlocks {
    /// Return a range of integers indexing the blocks (starting from 0.)
    fn num_blocks(&self) -> Result<usize>;

    fn block_range(&self) -> Result<Range<usize>> {
        Ok(0..self.num_blocks()?)
    }

    /// Read one block and return it as a byte vec. Also returns the compressed and uncompressed
    /// sizes.
    fn read_block(&self, i: usize) -> Result<(Vec<u8>, Sizes)>;
}

/// The measured size of a tree.
pub struct TreeSize {
    pub file_bytes: u64,
}