use std::fs::{read_dir, File};
use std::io::BufReader;
use std::iter::once;
use std::path::{Path, PathBuf};
use std::str::FromStr;
use std::{collections::VecDeque, time::SystemTime};
use eyre::{eyre, Context, Result};
use uuid::Uuid;
use super::manifest::Manifest;
pub struct MarEntry {
pub path: PathBuf,
pub uuid: Uuid,
pub manifest: Manifest,
}
pub struct MarEntryIterator {
directories: VecDeque<PathBuf>,
}
impl Iterator for MarEntryIterator {
type Item = Result<MarEntry>;
fn next(&mut self) -> Option<Self::Item> {
self.directories.pop_front().map(MarEntry::from_path)
}
fn size_hint(&self) -> (usize, Option<usize>) {
(self.directories.len(), Some(self.directories.len()))
}
}
impl MarEntry {
pub fn iterate_from_container(
mar_staging: &Path,
) -> Result<impl Iterator<Item = Result<MarEntry>>> {
let mut entries: Vec<(PathBuf, Option<SystemTime>)> = read_dir(mar_staging)
.wrap_err(eyre!(
"Unable to open MAR staging area: {}",
mar_staging.display()
))?
.filter_map(std::io::Result::ok)
.filter(|d| d.path().is_dir())
.map(|d| (d.path(), d.metadata().and_then(|m| m.created()).ok()))
.collect();
entries.sort_by(|a, b| a.1.cmp(&b.1));
Ok(MarEntryIterator {
directories: entries.into_iter().map(|e| e.0).collect(),
})
}
pub fn from_path(path: PathBuf) -> Result<Self> {
let uuid = Uuid::from_str(
path.file_name()
.ok_or_else(|| eyre!("{} is not a directory", path.display()))?
.to_str()
.ok_or_else(|| eyre!("{} is not a valid directory name", path.display()))?,
)?;
let manifest = path.join("manifest.json");
if !manifest.exists() {
return Err(eyre!(
"{} does not contain a manifest file.",
path.display()
));
}
let buf_reader = BufReader::new(
File::open(&manifest)
.wrap_err_with(|| format!("Error reading manifest file {:?}", manifest))?,
);
let manifest: Manifest = serde_json::from_reader(buf_reader)
.wrap_err_with(|| format!("Error parsing manifest file {:?}", manifest))?;
Ok(Self {
path,
uuid,
manifest,
})
}
pub fn filenames(&self) -> impl Iterator<Item = String> {
once("manifest.json".to_owned()).chain(self.manifest.attachments())
}
}
#[cfg(test)]
mod tests {
use rstest::{fixture, rstest};
use crate::mar::test_utils::MarCollectorFixture;
use crate::test_utils::setup_logger;
use super::*;
#[rstest]
fn collecting_from_empty_folder(_setup_logger: (), mar_fixture: MarCollectorFixture) {
assert_eq!(
MarEntry::iterate_from_container(&mar_fixture.tmp_mar_staging)
.unwrap()
.count(),
0
)
}
#[rstest]
fn collecting_from_folder_with_partial_entries(
_setup_logger: (),
mut mar_fixture: MarCollectorFixture,
) {
mar_fixture.create_empty_entry(false);
mar_fixture.create_logentry(false);
assert_eq!(
MarEntry::iterate_from_container(&mar_fixture.tmp_mar_staging)
.unwrap()
.filter(|e| e.is_ok())
.count(),
1
)
}
#[fixture]
fn mar_fixture() -> MarCollectorFixture {
MarCollectorFixture::new()
}
}