exocore-core 0.1.24

Core of Exocore (Distributed applications framework)
Documentation
use std::{
    collections::BTreeMap,
    io::{Read, Seek, SeekFrom, Write},
    sync::{Arc, RwLock},
};

use super::*;

#[derive(Default)]
pub struct RamDirectory {
    files: Arc<RwLock<BTreeMap<PathBuf, RamFileData>>>,
}

impl RamDirectory {
    pub fn new() -> Self {
        Self::default()
    }
}

impl Directory for RamDirectory {
    fn open_read(&self, path: &Path) -> Result<Box<dyn FileRead>, Error> {
        if path.parent().is_none() {
            return Err(Error::Path(anyhow!("expected a non-root path to a file")));
        }

        let files = self.files.read().unwrap();
        let file = files
            .get(path)
            .ok_or_else(|| Error::NotFound(path.to_path_buf()))?;

        Ok(Box::new(RamFile {
            data: file.clone(),
            cursor: 0,
        }))
    }

    fn open_write(&self, path: &Path) -> Result<Box<dyn FileWrite>, Error> {
        if path.parent().is_none() {
            return Err(Error::Path(anyhow!("expected a non-root path to a file")));
        }

        let mut files = self.files.write().unwrap();

        let data = files.get(path).cloned();
        let data = if let Some(data) = data {
            data
        } else {
            let data = RamFileData::default();
            files.insert(path.to_path_buf(), data.clone());
            data
        };

        Ok(Box::new(RamFile { data, cursor: 0 }))
    }

    fn open_create(&self, path: &Path) -> Result<Box<dyn FileWrite>, Error> {
        if path.parent().is_none() {
            return Err(Error::Path(anyhow!("expected a non-root path to a file")));
        }

        let mut files = self.files.write().unwrap();

        let data = RamFileData::default();
        files.insert(path.to_path_buf(), data.clone());

        Ok(Box::new(RamFile { data, cursor: 0 }))
    }

    fn list(&self, prefix: Option<&Path>) -> Result<Vec<Box<dyn FileStat>>, Error> {
        let prefix = prefix.unwrap_or_else(|| Path::new(""));
        let files = self.files.read().unwrap();

        let mut result: Vec<Box<dyn FileStat>> = Vec::new();
        for (path, file) in files.iter() {
            if path.starts_with(prefix) {
                result.push(Box::new(RamFileStat {
                    path: path.to_path_buf(),
                    data: file.clone(),
                }));
            }
        }

        Ok(result)
    }

    fn stat(&self, path: &Path) -> Result<Box<dyn FileStat>, Error> {
        let files = self.files.read().unwrap();
        let file = files
            .get(path)
            .ok_or_else(|| Error::NotFound(path.to_path_buf()))?;

        Ok(Box::new(RamFileStat {
            path: path.to_path_buf(),
            data: file.clone(),
        }))
    }

    fn exists(&self, path: &Path) -> bool {
        let files = self.files.read().unwrap();
        files.contains_key(path)
    }

    fn delete(&self, path: &Path) -> Result<(), Error> {
        let mut files = self.files.write().unwrap();
        files.remove(path);
        Ok(())
    }

    fn clone(&self) -> DynDirectory {
        RamDirectory {
            files: self.files.clone(),
        }
        .into()
    }

    fn as_os_path(&self) -> Result<PathBuf, Error> {
        Err(Error::NotOsDirectory)
    }
}

#[derive(Clone, Default)]
pub struct RamFileData {
    pub bytes: Arc<RwLock<Vec<u8>>>,
}

impl From<Vec<u8>> for RamFileData {
    fn from(bytes: Vec<u8>) -> Self {
        RamFileData {
            bytes: Arc::new(RwLock::new(bytes)),
        }
    }
}

#[derive(Default)]
pub struct RamFile {
    data: RamFileData,
    cursor: usize,
}

impl RamFile {
    pub fn new(data: RamFileData) -> Self {
        RamFile { data, cursor: 0 }
    }
}

impl FileRead for RamFile {}

impl FileWrite for RamFile {}

impl Read for RamFile {
    fn read(&mut self, buf: &mut [u8]) -> std::io::Result<usize> {
        let data = self.data.bytes.read().unwrap();
        if self.cursor >= data.len() {
            return Ok(0);
        }

        let to = std::cmp::min(self.cursor + buf.len(), data.len());
        let count = (&data[self.cursor..to]).read(buf)?;
        self.cursor += count;
        Ok(count)
    }
}

impl Seek for RamFile {
    fn seek(&mut self, pos: SeekFrom) -> std::io::Result<u64> {
        let data = self.data.bytes.read().unwrap();
        let len = data.len();
        let pos = match pos {
            SeekFrom::Start(pos) => pos as u64,
            SeekFrom::End(pos) => (len as i64 + pos) as u64,
            SeekFrom::Current(pos) => (self.cursor as i64 + pos) as u64,
        };
        self.cursor = pos as usize;
        Ok(pos)
    }
}

impl Write for RamFile {
    fn write(&mut self, buf: &[u8]) -> std::io::Result<usize> {
        let mut data = self.data.bytes.write().unwrap();

        for i in 0..buf.len() {
            if self.cursor + i >= data.len() {
                data.push(buf[i]);
            } else {
                data[self.cursor + i] = buf[i];
            }
        }

        self.cursor += buf.len();

        Ok(buf.len())
    }

    fn flush(&mut self) -> std::io::Result<()> {
        Ok(())
    }
}

pub struct RamFileStat {
    path: PathBuf,
    data: RamFileData,
}

impl FileStat for RamFileStat {
    fn path(&self) -> &Path {
        self.path.as_path()
    }

    fn size(&self) -> u64 {
        self.data.bytes.read().unwrap().len() as u64
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_write_read_file() {
        super::super::tests::test_write_read_file(RamDirectory::new());
    }

    #[test]
    fn test_list() {
        super::super::tests::test_list(RamDirectory::new());
    }

    #[test]
    fn test_delete() {
        super::super::tests::test_delete(RamDirectory::new());
    }
}