bitbottle 0.10.0

a modern archive file format
Documentation
use std::cell::RefCell;
use std::fs::Metadata;
use std::os::unix::fs::MetadataExt;
use std::path::{Component, PathBuf};
use std::rc::Rc;
use users::{get_user_by_uid, get_group_by_gid};
use crate::bottle_cap::BottleCap;
use crate::file_list::FileBlocks;


/// Metadata about a file (path, size, permissions, timestamps, ownership),
/// its overall hash, and a list of the blocks that make up the file's
/// content.
#[derive(Clone, Debug, Default, Eq, PartialEq)]
pub struct FileAtlas {
    pub path: PathBuf,  // the actual filename on disk (when creating)
    pub normalized_path: PathBuf,  // the filename it should have in the archive (when creating/expanding)
    pub is_folder: bool,
    pub size: u64,
    pub perms: u32,
    pub mtime_ns: i64,
    pub ctime_ns: i64,
    pub user: String,
    pub group: String,
    pub symlink_target: Option<PathBuf>,
    pub contents: FileBlocks,

    // in case there is other stuff of interest
    pub bottle_cap: BottleCap,
}

impl FileAtlas {
    /// Turn me into a bobbled self (non-thread-safe, runtime reference counted)
    pub fn bobble(self) -> FileAtlasRef {
        Rc::new(RefCell::new(self))
    }

    /// if this file's normalized path contains any monkey business (root
    /// paths, relative backtrack references, and so on), remove all of them,
    /// replace the path with a clean version, and return the bad one.
    /// returns None if the path was fine.
    pub fn fix_bad_path(&mut self) -> Option<PathBuf> {
        let mut components = self.normalized_path.components();
        if !components.all(|c| matches!(c, Component::Normal(_))) {
            let bad_path = self.normalized_path.clone();
            self.normalized_path = components.filter(|c| matches!(c, Component::Normal(_))).collect();
            Some(bad_path)
        } else {
            None
        }
    }
}


impl TryFrom<&Metadata> for FileAtlas {
    type Error = std::io::Error;

    fn try_from(metadata: &Metadata) -> std::io::Result<FileAtlas> {
        let user = get_user_by_uid(metadata.uid());
        let group = get_group_by_gid(metadata.gid());

        Ok(FileAtlas {
            path: PathBuf::default(),
            normalized_path: PathBuf::default(),
            is_folder: metadata.is_dir(),
            size: if metadata.is_dir() { 0 } else { metadata.len() },
            perms: metadata.mode(),
            mtime_ns: metadata.mtime() * 1_000_000_000 + metadata.mtime_nsec(),
            ctime_ns: metadata.ctime() * 1_000_000_000 + metadata.ctime_nsec(),
            user: user.and_then(|u| u.name().to_str().map(|s| s.to_string())).unwrap_or_else(|| "?".to_string()),
            group: group.and_then(|g| g.name().to_str().map(|s| s.to_string())).unwrap_or_else(|| "?".to_string()),
            symlink_target: None,
            contents: FileBlocks::default(),
            bottle_cap: BottleCap::default(),
        })
    }
}


/// Shared reference to a FileAtlas.
pub type FileAtlasRef = Rc<RefCell<FileAtlas>>;