Skip to content

Commit

Permalink
Add ManifestBuilder object
Browse files Browse the repository at this point in the history
This is designed to generate manifests from files on the file-system
  • Loading branch information
bobbobbio committed Jan 23, 2024
1 parent 1c70103 commit 8b6ca55
Show file tree
Hide file tree
Showing 2 changed files with 163 additions and 81 deletions.
55 changes: 40 additions & 15 deletions crates/maelstrom-util/src/fs.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
use anyhow::{Context as _, Result};
use fs2::FileExt as _;
use std::{
fmt,
io::{self},
path::{Path, PathBuf},
};
Expand All @@ -15,14 +16,14 @@ impl Fs {
}

macro_rules! fs_trampoline {
($f:ident, $p:ident) => {{
($f:path, $p:ident) => {{
let path = $p.as_ref();
std::fs::$f(path).with_context(|| format!("{}(\"{}\")", stringify!($f), path.display()))
$f(path).with_context(|| format!("{}(\"{}\")", stringify!($f), path.display()))
}};
($f:ident, $p1:ident, $p2:ident) => {{
($f:path, $p1:ident, $p2:ident) => {{
let path1 = $p1.as_ref();
let path2 = $p2.as_ref();
std::fs::$f(path1, path2).with_context(|| {
$f(path1, path2).with_context(|| {
format!(
"{}(\"{}\", \"{}\")",
stringify!($f),
Expand Down Expand Up @@ -82,6 +83,12 @@ impl DirEntry {
}
}

impl fmt::Debug for DirEntry {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.inner.fmt(f)
}
}

pub struct Metadata {
inner: std::fs::Metadata,
path: PathBuf,
Expand Down Expand Up @@ -189,34 +196,41 @@ fn is_not_found_err(err: &anyhow::Error) -> bool {

impl Fs {
pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> Result<()> {
fs_trampoline!(create_dir, path)
fs_trampoline!(std::fs::create_dir, path)
}

pub fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> Result<()> {
fs_trampoline!(create_dir_all, path)
fs_trampoline!(std::fs::create_dir_all, path)
}

pub fn metadata<P: AsRef<Path>>(&self, path: P) -> Result<Metadata> {
fs_trampoline!(metadata, path).map(|inner| Metadata {
fs_trampoline!(std::fs::metadata, path).map(|inner| Metadata {
inner,
path: path.as_ref().into(),
})
}

pub fn symlink_metadata<P: AsRef<Path>>(&self, path: P) -> Result<Metadata> {
fs_trampoline!(std::fs::symlink_metadata, path).map(|inner| Metadata {
inner,
path: path.as_ref().into(),
})
}

pub fn rename<P: AsRef<Path>, Q: AsRef<Path>>(&self, from: P, to: Q) -> Result<()> {
fs_trampoline!(rename, from, to)
fs_trampoline!(std::fs::rename, from, to)
}

pub fn remove_dir<P: AsRef<Path>>(&self, path: P) -> Result<()> {
fs_trampoline!(remove_dir, path)
fs_trampoline!(std::fs::remove_dir, path)
}

pub fn remove_dir_all<P: AsRef<Path>>(&self, path: P) -> Result<()> {
fs_trampoline!(remove_dir_all, path)
fs_trampoline!(std::fs::remove_dir_all, path)
}

pub fn remove_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
fs_trampoline!(remove_file, path)
fs_trampoline!(std::fs::remove_file, path)
}

pub fn write<P: AsRef<Path>, C: AsRef<[u8]>>(&self, path: P, contents: C) -> Result<()> {
Expand All @@ -225,24 +239,28 @@ impl Fs {
}

pub fn read_to_string<P: AsRef<Path>>(&self, path: P) -> Result<String> {
fs_trampoline!(read_to_string, path)
fs_trampoline!(std::fs::read_to_string, path)
}

pub fn read_to_string_if_exists<P: AsRef<Path>>(&self, path: P) -> Result<Option<String>> {
match fs_trampoline!(read_to_string, path) {
match fs_trampoline!(std::fs::read_to_string, path) {
Ok(contents) => Ok(Some(contents)),
Err(err) if is_not_found_err(&err) => Ok(None),
Err(err) => Err(err),
}
}

pub fn read_dir<P: AsRef<Path>>(&self, path: P) -> Result<ReadDir> {
fs_trampoline!(read_dir, path).map(|inner| ReadDir {
fs_trampoline!(std::fs::read_dir, path).map(|inner| ReadDir {
inner,
path: path.as_ref().into(),
})
}

pub fn read_link<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf> {
fs_trampoline!(std::fs::read_link, path)
}

pub fn open_file<P: AsRef<Path>>(&self, path: P) -> Result<File<'_>> {
let path = path.as_ref();
Ok(File {
Expand Down Expand Up @@ -278,14 +296,21 @@ impl Fs {
}

pub fn canonicalize<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf> {
fs_trampoline!(canonicalize, path)
fs_trampoline!(std::fs::canonicalize, path)
}

pub fn exists<P: AsRef<Path>>(&self, path: P) -> bool {
path.as_ref().exists()
}
}

#[cfg(unix)]
impl Fs {
pub fn symlink<P: AsRef<Path>, Q: AsRef<Path>>(&self, original: P, link: Q) -> Result<()> {
fs_trampoline!(std::os::unix::fs::symlink, original, link)
}
}

pub struct File<'fs> {
inner: std::fs::File,
path: PathBuf,
Expand Down
189 changes: 123 additions & 66 deletions crates/maelstrom-util/src/manifest.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
use crate::fs;
use crate::fs::{self, Fs};
use anyhow::{anyhow, Result};
use maelstrom_base::{
manifest::{
Identity, ManifestEntry, ManifestEntryData, ManifestEntryMetadata, ManifestWriter, Mode,
Expand All @@ -16,22 +17,31 @@ fn to_utf8_path(path: impl AsRef<Path>) -> Utf8PathBuf {

fn convert_metadata(meta: &fs::Metadata) -> ManifestEntryMetadata {
ManifestEntryMetadata {
size: meta.size(),
size: meta.is_file().then(|| meta.size()).unwrap_or(0),
mode: Mode(meta.mode()),
user: Identity::Id(meta.uid() as u64),
group: Identity::Id(meta.gid() as u64),
mtime: UnixTimestamp(meta.mtime()),
}
}

pub struct ManifestBuilder<WriteT> {
type DataUploadCb<'cb> = Box<dyn FnMut(&Path) -> Result<Sha256Digest> + 'cb>;

pub struct ManifestBuilder<'cb, WriteT> {
fs: Fs,
writer: ManifestWriter<WriteT>,
data_upload: DataUploadCb<'cb>,
}

impl<WriteT: io::Write> ManifestBuilder<WriteT> {
pub fn new(writer: WriteT) -> io::Result<Self> {
impl<'cb, WriteT: io::Write> ManifestBuilder<'cb, WriteT> {
pub fn new(
writer: WriteT,
data_upload: impl FnMut(&Path) -> Result<Sha256Digest> + 'cb,
) -> io::Result<Self> {
Ok(Self {
fs: Fs::new(),
writer: ManifestWriter::new(writer)?,
data_upload: Box::new(data_upload),
})
}

Expand All @@ -40,7 +50,7 @@ impl<WriteT: io::Write> ManifestBuilder<WriteT> {
meta: &fs::Metadata,
path: impl AsRef<Path>,
data: ManifestEntryData,
) -> io::Result<()> {
) -> Result<()> {
let entry = ManifestEntry {
path: to_utf8_path(path),
metadata: convert_metadata(meta),
Expand All @@ -49,75 +59,122 @@ impl<WriteT: io::Write> ManifestBuilder<WriteT> {
self.writer.write_entry(&entry)?;
Ok(())
}
pub fn add_file(
&mut self,
meta: &fs::Metadata,
path: impl AsRef<Path>,
data: Option<Sha256Digest>,
) -> io::Result<()> {
self.add_entry(meta, path, ManifestEntryData::File(data))
}

pub fn add_directory(&mut self, meta: &fs::Metadata, path: impl AsRef<Path>) -> io::Result<()> {
self.add_entry(meta, path, ManifestEntryData::Directory)
pub fn add_file(&mut self, source: impl AsRef<Path>, dest: impl AsRef<Path>) -> Result<()> {
let meta = self.fs.symlink_metadata(source.as_ref())?;
if meta.is_file() {
let data = (meta.size() > 0)
.then(|| (self.data_upload)(source.as_ref()))
.transpose()?;
self.add_entry(&meta, dest, ManifestEntryData::File(data))
} else if meta.is_dir() {
self.add_entry(&meta, dest, ManifestEntryData::Directory)
} else if meta.is_symlink() {
let data = self.fs.read_link(source.as_ref())?;
self.add_entry(
&meta,
dest,
ManifestEntryData::Symlink(data.into_os_string().into_encoded_bytes()),
)
} else {
Err(anyhow!("unknown file type {}", source.as_ref().display()))
}
}
}

pub fn add_symlink(
&mut self,
meta: &fs::Metadata,
path: impl AsRef<Path>,
data: impl Into<Vec<u8>>,
) -> io::Result<()> {
self.add_entry(meta, path, ManifestEntryData::Symlink(data.into()))
#[cfg(test)]
mod tests {
use super::*;
use crate::fs::Fs;
use maelstrom_base::manifest::ManifestReader;
use maelstrom_test::*;
use std::path::PathBuf;
use tempfile::{tempdir, TempDir};

struct Fixture {
fs: Fs,
_temp_dir: TempDir,
input_path: PathBuf,
}

pub fn add_hardlink(
&mut self,
meta: &fs::Metadata,
target: impl AsRef<Path>,
source: impl AsRef<Path>,
) -> io::Result<()> {
self.add_entry(
meta,
target,
ManifestEntryData::Hardlink(to_utf8_path(source)),
)
impl Fixture {
fn new() -> Self {
let temp_dir = tempdir().unwrap();
Fixture {
fs: Fs::new(),
input_path: temp_dir.path().join("input_entry"),
_temp_dir: temp_dir,
}
}
}
}

#[test]
fn builder_file() {
use maelstrom_base::manifest::ManifestReader;
use maelstrom_test::*;
fn assert_entry(
build: impl FnOnce(&mut Fixture, &mut ManifestBuilder<&mut Vec<u8>>),
expected_path: &str,
expected_size: u64,
data: ManifestEntryData,
) {
let mut buffer = vec![];
let mut builder = ManifestBuilder::new(&mut buffer, |_| Ok(digest![42])).unwrap();

let mut buffer = vec![];
let mut builder = ManifestBuilder::new(&mut buffer).unwrap();
let mut fixture = Fixture::new();
build(&mut fixture, &mut builder);

let tmp_dir = tempfile::tempdir().unwrap();
let fs = fs::Fs::new();
let foo_path = tmp_dir.path().join("foo.txt");
fs.write(&foo_path, b"foobar").unwrap();
builder
.add_file(
&fs.metadata(&foo_path).unwrap(),
let actual_entries: Vec<_> = ManifestReader::new(io::Cursor::new(buffer))
.unwrap()
.map(|e| e.unwrap())
.collect();
assert_eq!(
actual_entries,
vec![ManifestEntry {
path: to_utf8_path(expected_path),
metadata: ManifestEntryMetadata {
size: expected_size,
..convert_metadata(&fixture.fs.symlink_metadata(&fixture.input_path).unwrap())
},
data,
}]
);
}

#[test]
fn builder_file() {
assert_entry(
|fixture, builder| {
fixture.fs.write(&fixture.input_path, b"foobar").unwrap();
builder
.add_file(&fixture.input_path, "foo/bar.txt")
.unwrap();
},
"foo/bar.txt",
Some(digest![42]),
)
.unwrap();

let entries: Vec<_> = ManifestReader::new(io::Cursor::new(buffer))
.unwrap()
.map(|e| e.unwrap())
.collect();
assert_eq!(
entries,
vec![ManifestEntry {
path: to_utf8_path("foo/bar.txt"),
metadata: ManifestEntryMetadata {
size: 6,
..convert_metadata(&fs.metadata(&foo_path).unwrap())
6,
ManifestEntryData::File(Some(digest![42])),
);
}

#[test]
fn builder_directory() {
assert_entry(
|fixture, builder| {
fixture.fs.create_dir(&fixture.input_path).unwrap();
builder.add_file(&fixture.input_path, "foo/bar").unwrap();
},
"foo/bar",
0,
ManifestEntryData::Directory,
);
}

#[test]
fn builder_symlink() {
assert_entry(
|fixture, builder| {
fixture.fs.symlink("../baz", &fixture.input_path).unwrap();
builder.add_file(&fixture.input_path, "foo/bar").unwrap();
},
data: ManifestEntryData::File(Some(digest![42]))
}]
);
"foo/bar",
0,
ManifestEntryData::Symlink(b"../baz".to_vec()),
);
}
}

0 comments on commit 8b6ca55

Please sign in to comment.