use fatfs::{self, FileSystem, FormatVolumeOptions, FsOptions};
use std::fs::{self, metadata, read_dir, DirEntry};
use std::io::{self, Cursor, Write};
use std::path::{Path, StripPrefixError};
use thiserror::Error;
#[derive(Debug, Error)]
pub enum FSError {
#[error("IO Error")]
IOError(#[from] io::Error),
#[error("Error strippping path prefix")]
StripPrefixError(#[from] StripPrefixError),
#[error("Missing file name")]
MissingFileName,
}
fn traverse<T>(
path: &impl AsRef<Path>,
mut acc: T,
cb: &impl Fn(T, &DirEntry) -> Result<T, FSError>,
) -> Result<T, FSError> {
for entry in read_dir(path)? {
let entry = entry?;
acc = cb(acc, &entry)?;
let path = entry.path();
if path.is_dir() {
acc = traverse(&path, acc, cb)?;
}
}
Ok(acc)
}
pub(crate) fn create_filesystem(fs_path: impl AsRef<Path>) -> Result<Vec<u8>, FSError> {
let fs_path = fs_path.as_ref().canonicalize()?;
const RESERVED_BYTES: usize = 128 * 1024;
let size = traverse(&fs_path, RESERVED_BYTES, &|mut size, entry| {
let stat = metadata(&entry.path())?;
if stat.is_file() {
size += (stat.len() as usize + 511) & !512;
}
Ok(size)
})?;
let mut stream = Cursor::new(vec![0; size]);
let opts = {
let opts = FormatVolumeOptions::new();
opts.volume_label(*b"TECHNEKDISK")
};
fatfs::format_volume(&mut stream, opts)?;
{
let disk = FileSystem::new(&mut stream, FsOptions::new())?;
let root_dir = disk.root_dir();
traverse(&fs_path, (), &|(), entry| {
let path = entry.path();
let name = &path.strip_prefix(&fs_path)?.to_string_lossy();
if entry.file_type()?.is_dir() {
root_dir.create_dir(name)?;
} else {
let buffer = fs::read(&path)?;
let mut dest = root_dir.create_file(name)?;
dest.write_all(&buffer)?;
}
Ok(())
})?;
}
Ok(stream.into_inner())
}