blockset 0.3.1

Data Block Set
Documentation
use std::{
    cell::RefCell,
    collections::{HashMap, HashSet},
    io::{self, Read, Write},
    iter::once,
    rc::Rc,
    vec,
};

use crate::io::Io;

#[derive(Debug, Clone)]
pub struct Metadata {
    len: u64,
}

impl crate::io::Metadata for Metadata {
    fn len(&self) -> u64 {
        self.len
    }
}

#[derive(Debug, Default, Clone)]
pub struct VecRef(Rc<RefCell<Vec<u8>>>);

impl VecRef {
    pub fn to_string(&self) -> String {
        let mut result = String::default();
        for &c in self.0.borrow().iter() {
            if c == 8 {
                result.pop();
            } else {
                result.push(c as char);
            }
        }
        result
    }
}

impl Write for VecRef {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.0.borrow_mut().extend_from_slice(buf);
        Ok(buf.len())
    }
    fn flush(&mut self) -> io::Result<()> {
        Ok(())
    }
}

#[derive(Debug, Default)]
pub struct FileSystem {
    directory_set: HashSet<String>,
    file_map: HashMap<String, VecRef>,
}

impl FileSystem {
    pub fn check_dir(&self, path: &str) -> io::Result<()> {
        if let Some(d) = path.rfind('/').map(|i| &path[..i]) {
            if !self.directory_set.contains(d) {
                return Err(not_found());
            }
        }
        Ok(())
    }
}

pub struct DirEntry {
    path: String,
    metadata: Metadata,
}

impl crate::io::DirEntry for DirEntry {
    type Metadata = Metadata;
    fn path(&self) -> String {
        self.path.clone()
    }
    fn metadata(&self) -> io::Result<Self::Metadata> {
        Ok(self.metadata.clone())
    }
}

pub struct VirtualIo {
    pub args: Vec<String>,
    pub fs: RefCell<FileSystem>,
    pub stdout: VecRef,
}

impl VirtualIo {
    pub fn new(args: &[&str]) -> Self {
        Self {
            args: once("blockset".to_string())
                .chain(args.iter().map(|v| v.to_string()))
                .collect(),
            fs: Default::default(),
            stdout: VecRef::default(),
        }
    }
}

#[derive(Debug)]
pub struct MemFile {
    vec_ref: VecRef,
    pos: usize,
}

impl MemFile {
    fn new(vec_ref: VecRef) -> Self {
        Self { vec_ref, pos: 0 }
    }
}

impl Read for MemFile {
    fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
        let sorce = &self.vec_ref.0.borrow()[self.pos..];
        let len = sorce.len().min(buf.len());
        buf[..len].copy_from_slice(&sorce[..len]);
        self.pos += len;
        Ok(len)
    }
}

impl Write for MemFile {
    fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
        self.vec_ref.write(buf)
    }
    fn flush(&mut self) -> io::Result<()> {
        self.vec_ref.flush()
    }
}

fn not_found() -> io::Error {
    io::Error::new(io::ErrorKind::NotFound, "file not found")
}

fn check_path(a: &str) -> io::Result<()> {
    if a.chars()
        .all(|c| c.is_ascii_alphanumeric() || c == '/' || c == '_' || c == '.')
    {
        Ok(())
    } else {
        Err(io::Error::new(
            io::ErrorKind::InvalidInput,
            "invalid file name",
        ))
    }
}

impl Io for VirtualIo {
    type File = MemFile;
    type Stdout = VecRef;
    type Args = vec::IntoIter<String>;
    type Metadata = Metadata;
    type DirEntry = DirEntry;
    fn args(&self) -> Self::Args {
        self.args.clone().into_iter()
    }
    fn metadata(&self, path: &str) -> io::Result<Metadata> {
        let fs = self.fs.borrow();
        fs.file_map
            .get(path)
            .map(|v| Metadata {
                len: v.0.borrow().len() as u64,
            })
            .ok_or_else(not_found)
    }
    fn create(&self, path: &str) -> io::Result<Self::File> {
        let mut fs = self.fs.borrow_mut();
        fs.check_dir(path)?;
        let vec_ref = VecRef::default();
        check_path(path)?;
        fs.file_map.insert(path.to_string(), vec_ref.clone());
        Ok(MemFile::new(vec_ref))
    }
    fn create_dir(&self, path: &str) -> io::Result<()> {
        let mut fs = self.fs.borrow_mut();
        fs.directory_set.insert(path.to_string());
        Ok(())
    }
    fn open(&self, path: &str) -> io::Result<Self::File> {
        let fs = self.fs.borrow();
        fs.check_dir(path)?;
        check_path(path)?;
        fs.file_map
            .get(path)
            .map(|v| MemFile::new(v.clone()))
            .ok_or_else(not_found)
    }
    fn stdout(&self) -> VecRef {
        self.stdout.clone()
    }

    fn read_dir(&self, path: &str) -> io::Result<Vec<DirEntry>> {
        let fs = self.fs.borrow();
        let d = fs.directory_set.iter().map(|d| DirEntry {
            path: d.to_string(),
            metadata: Metadata { len: 0 },
        });
        let f = fs.file_map.iter().map(|(k, v)| DirEntry {
            path: k.to_owned(),
            metadata: Metadata {
                len: v.0.borrow().len() as u64,
            },
        });
        let i = d.chain(f);
        let x = i
            .filter(|p| {
                if let Some((a, _)) = p.path.rsplit_once('/') {
                    a == path
                } else {
                    false
                }
            })
            .collect();
        Ok(x)
    }
}