matryoshka 0.1.0

Provides a unified API for both file system access and access to ZIP archives.
Documentation
use std::{fs, io};
use std::fs::{File, Metadata, OpenOptions};
use std::path::{Path, PathBuf};

use thiserror::Error;

use crate::{DirEntry, FileInfo, normalize_path};

#[derive(Debug, Clone, Eq, PartialEq, Ord, PartialOrd, Hash)]
pub struct DataSource {
    dir: PathBuf,
}

impl DataSource {
    pub fn new<P: Into<PathBuf>>(dir: P) -> Result<Self, io::Error> {
        let dir = dir.into();
        match fs::read_dir(&dir) {
            Err(e) => Err(e),
            Ok(_) => Ok(DataSource { dir })
        }
    }

    pub fn open<P: AsRef<Path>>(&self, path: P, opts: OpenOptions) -> Result<File, Error> {
        Ok(opts.open(self.get_full_path(path)?)?)
    }

    pub fn list_dir<P: AsRef<Path>>(&self, path: P) -> Result<Vec<DirEntry>, Error> {
        let result = fs::read_dir(self.get_full_path(path)?)?;
        Ok(result
            .filter_map(|e| e.ok())
            .map(|e| {
                let meta = e.metadata().expect("failed to load file metadata");
                DirEntry {
                    path: Path::new("/").join(e.path().strip_prefix(&self.dir).unwrap()),
                    info: meta.into(),
                }
            })
            .collect())
    }

    pub fn read_info<P: AsRef<Path>>(&self, path: P) -> Result<FileInfo, Error> {
        Ok(fs::symlink_metadata(self.get_full_path(path)?)?.into())
    }

    pub fn create_dir<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
        Ok(fs::create_dir(self.get_full_path(path)?)?)
    }

    pub fn create_dir_all<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
        Ok(fs::create_dir_all(self.get_full_path(path)?)?)
    }

    pub fn delete_file<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
        Ok(fs::remove_file(self.get_full_path(path)?)?)
    }

    pub fn delete_dir<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
        Ok(fs::remove_dir(self.get_full_path(path)?)?)
    }

    pub fn delete_dir_all<P: AsRef<Path>>(&self, path: P) -> Result<(), Error> {
        Ok(fs::remove_dir_all(self.get_full_path(path)?)?)
    }

    pub fn root(&self) -> &Path { &self.dir }

    fn get_full_path<P: AsRef<Path>>(&self, path: P) -> Result<PathBuf, Error> {
        Ok(self.dir.join(normalize_path(&path).ok_or_else(|| Error::InvalidPath(path.as_ref().to_path_buf()))?.strip_prefix("/").unwrap()))
    }
}

impl From<fs::Metadata> for FileInfo {
    fn from(meta: Metadata) -> Self {
        let ft = meta.file_type();
        FileInfo {
            is_file: ft.is_file(),
            is_dir: ft.is_dir(),
            is_symlink: ft.is_symlink(),
            read_only: meta.permissions().readonly(),
        }
    }
}

#[derive(Debug, Error)]
pub enum Error {
    #[error("root directory not found")]
    RootDirNotFound(io::Error),
    #[error("invalid path")]
    InvalidPath(PathBuf),
    #[error("I/O error")]
    Io(io::Error),
}

impl From<io::Error> for Error {
    fn from(err: io::Error) -> Self {
        Error::Io(err)
    }
}