use crate::{
checksum::{compute_checksum_file, ChecksumComputeError},
BagIt, Checksum,
};
use digest::Digest;
use std::{
fmt::Display,
path::{Path, PathBuf},
};
#[derive(thiserror::Error, Debug, PartialEq)]
pub enum PayloadError {
#[error("Invalid line format")]
InvalidLine,
#[error("Failed to get absolute path")]
Absolute(std::io::ErrorKind),
#[error("Payload is not inside bag")]
NotInsideBag,
#[error("Failed to compute checksum: {0}")]
ComputeChecksum(#[from] ChecksumComputeError),
#[error("Provided checksum differs from file on disk")]
ChecksumDiffers,
#[error("Failed to get file size: {0}")]
FileSize(std::io::ErrorKind),
}
#[derive(Debug, PartialEq)]
pub struct Payload<'a> {
checksum: Checksum<'a>,
relative_path: std::path::PathBuf,
bytes: u64,
}
impl Display for Payload<'_> {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{} {}", self.checksum, self.relative_path.display())
}
}
impl<'a> Payload<'a> {
#[cfg(test)]
pub(crate) fn test_payload(
relative_path_file: impl AsRef<Path>,
checksum: &'a str,
bytes: u64,
) -> Self {
Self {
checksum: Checksum::from(checksum),
relative_path: PathBuf::from(relative_path_file.as_ref()),
bytes,
}
}
pub(crate) fn new(
absolute_base_path: impl AsRef<Path>,
relative_path_file: impl AsRef<Path>,
checksum: Checksum<'a>,
) -> Result<Self, PayloadError> {
let relative_path = relative_path_file.as_ref().to_path_buf();
let bytes = absolute_base_path
.as_ref()
.join(relative_path_file.as_ref())
.metadata()
.map(|metadata| metadata.len())
.map_err(|e| PayloadError::FileSize(e.kind()))?;
Ok(Self {
checksum,
relative_path,
bytes,
})
}
pub(crate) async fn from_manifest<'manifest, 'item, ChecksumAlgo: Digest>(
manifest_line: &'manifest str,
base_directory: impl AsRef<Path>,
) -> Result<Self, PayloadError> {
let base_directory = base_directory.as_ref();
let [checksum_from_manifest, relative_file_path] = manifest_line
.split_whitespace()
.next_chunk()
.map_err(|_| PayloadError::InvalidLine)?;
let file_path = base_directory
.join(relative_file_path)
.canonicalize()
.map_err(|e| PayloadError::Absolute(e.kind()))?;
let base_directory = base_directory
.canonicalize()
.map_err(|e| PayloadError::Absolute(e.kind()))?;
if !file_path.starts_with(base_directory) {
return Err(PayloadError::NotInsideBag);
}
let checksum = compute_checksum_file::<ChecksumAlgo>(&file_path).await?;
if checksum != checksum_from_manifest.into() {
return Err(PayloadError::ChecksumDiffers);
}
let bytes = file_path
.metadata()
.map(|metadata| metadata.len())
.map_err(|e| PayloadError::FileSize(e.kind()))?;
Ok(Self {
checksum,
relative_path: PathBuf::from(relative_file_path),
bytes,
})
}
pub fn checksum(&self) -> &Checksum {
&self.checksum
}
pub fn relative_path(&self) -> &Path {
&self.relative_path
}
pub fn absolute_path(&self, bag: &BagIt) -> PathBuf {
bag.path().join(&self.relative_path)
}
pub fn bytes(&self) -> u64 {
self.bytes
}
}