copyrite 0.4.0

A CLI tool for efficient checksum and copy operations across object stores
Documentation
//! File-based sums file logic.
//!

use crate::checksum::file::SumsFile;
use crate::error::Error::ParseError;
use crate::error::{ApiError, Result};
use crate::io::sums::ObjectSums;
use std::collections::HashSet;
use std::path::PathBuf;
use tokio::fs;
use tokio::io::{AsyncRead, AsyncReadExt};

/// Build a file based sums object.
#[derive(Debug, Default)]
pub struct FileBuilder {
    file: Option<String>,
}

impl FileBuilder {
    /// Set the file location.
    pub fn with_file(mut self, file: String) -> Self {
        self.file = Some(file);
        self
    }

    fn get_components(self) -> Result<String> {
        self.file
            .ok_or_else(|| ParseError("file is required for `FileBuilder`".to_string()))
    }

    /// Build using the file name.
    pub fn build(self) -> Result<File> {
        Ok(self.get_components()?.into())
    }
}

impl From<String> for File {
    fn from(file: String) -> Self {
        Self::new(file)
    }
}

/// A file object.
#[derive(Debug, Clone)]
pub struct File {
    file: String,
}

impl File {
    /// Create a new file.
    pub fn new(file: String) -> Self {
        Self { file }
    }

    /// Get an existing sums file.
    pub async fn get_existing_sums(&self) -> Result<Option<SumsFile>> {
        let path = SumsFile::format_sums_file(&self.file);

        if !PathBuf::from(&path).exists() {
            return Ok(None);
        }

        let mut file = fs::File::open(&path).await?;
        let mut buf = vec![];
        file.read_to_end(&mut buf).await?;

        let sums = SumsFile::read_from_slice(&buf).await?;
        Ok(Some(sums))
    }

    /// Get the reader to the sums file.
    pub async fn sums_reader(&self) -> Result<impl AsyncRead + 'static> {
        let path = SumsFile::format_target_file(&self.file);
        Ok(fs::File::open(&path).await?)
    }

    /// Get the size of the target file.
    pub async fn size(&self) -> Result<Option<u64>> {
        Ok(fs::metadata(SumsFile::format_target_file(&self.file))
            .await
            .ok()
            .map(|metadata| metadata.len()))
    }

    /// Write the sums file to the configured location.
    pub async fn write_sums(&self, sums_file: &SumsFile) -> Result<()> {
        let path = SumsFile::format_sums_file(&self.file);
        fs::write(&path, sums_file.to_json_string()?).await?;
        Ok(())
    }
}

#[async_trait::async_trait]
impl ObjectSums for File {
    async fn sums_file(&mut self) -> Result<Option<SumsFile>> {
        Ok(self.get_existing_sums().await?)
    }

    async fn reader(&mut self) -> Result<Box<dyn AsyncRead + Unpin + Send + 'static>> {
        Ok(Box::new(self.sums_reader().await?))
    }

    async fn file_size(&mut self) -> Result<Option<u64>> {
        self.size().await
    }

    async fn write_sums_file(&self, sums_file: &SumsFile) -> Result<()> {
        self.write_sums(sums_file).await
    }

    fn location(&self) -> String {
        self.file.to_string()
    }

    fn api_errors(&self) -> HashSet<ApiError> {
        HashSet::new()
    }
}