diskit 0.1.1

Utilities for intercepting disk requests.
Documentation
//! Virtual file system emulation diskit
//!
//! For more information see the [struct level documentation](VirtualDiskit).

use std::{
    io::{Error, ErrorKind, SeekFrom},
    path::{Path, PathBuf},
    sync::{Arc, Mutex, MutexGuard},
};

use crate::{
    dir_entry::DirEntry,
    file::{File, FileInner},
    metadata::Metadata,
    open_options::OpenOptions,
    walkdir::{WalkDir, WalkdirIterator, WalkdirIteratorInner},
    Diskit,
};

use self::implementation::VirtualDiskitInner;

mod helpers;
mod implementation;
#[cfg(test)]
mod tests;

/// Virtual file system emulation diskit
///
/// This diskit emulates a virtual file system, no requests are passed
/// through to the disk.
///
/// This is by far the most complex of all the four predefined diskits
/// and as such has the highest potential for bugs and lacking
/// features.  Notably a **permission** system and **symlinks** are
/// missing, although symlinks and – in part – permissions are
/// supported by the rest of this library.  If you want to fix this,
/// feel free to submit a [pull
/// request](https://codeberg.org/zvavybir/diskit/pulls) (in an
/// earlier version I wanted to add a permission system and already
/// had implemented a bit before I abandoned it.  If you want to try
/// to add it I can send you my code – but beware it's quality is
/// horrible and the library had a couple redesigns since then).
#[derive(Debug, Clone)]
pub struct VirtualDiskit
{
    inner: Arc<Mutex<VirtualDiskitInner>>,
}

impl Default for VirtualDiskit
{
    fn default() -> Self
    {
        Self {
            inner: Arc::new(Mutex::new(VirtualDiskitInner::new())),
        }
    }
}

impl Diskit for VirtualDiskit
{
    fn set_pwd_inner(&self, new_pwd: &Path) -> Result<(), Error>
    {
        let pwd = &mut self.get_inner().pwd;
        *pwd = pwd.join(new_pwd);

        Ok(())
    }

    fn get_pwd(&self) -> Result<PathBuf, Error>
    {
        Ok(self.get_inner().pwd.clone())
    }

    fn open_inner(&self, path: &Path) -> Result<File<Self>, Error>
    {
        self.open_with_options_inner(path, OpenOptions::new().read(true))
    }

    fn create_inner(&self, path: &Path) -> Result<File<Self>, Error>
    {
        self.open_with_options_inner(
            path,
            OpenOptions::new().write(true).create(true).truncate(true),
        )
    }

    fn open_with_options_inner(
        &self,
        path: &Path,
        options: OpenOptions,
    ) -> Result<File<Self>, Error>
    {
        self.get_inner().open_with_options(path, options, self)
    }

    fn read_inner(&self, file: &FileInner, buf: &mut [u8]) -> Result<usize, Error>
    {
        self.get_inner().read(file, buf)
    }

    fn read_to_end_inner(&self, file: &mut FileInner, buf: &mut Vec<u8>) -> Result<usize, Error>
    {
        self.get_inner().read_to_end(file, buf)
    }

    fn read_to_string_inner(&self, file: &mut FileInner, buf: &mut String) -> Result<usize, Error>
    {
        self.get_inner().read_to_string(file, buf)
    }

    fn write_inner(&self, file: &mut FileInner, buf: &[u8]) -> Result<usize, Error>
    {
        self.get_inner().write(file, buf)
    }

    fn write_all_inner(&self, file: &mut FileInner, buf: &[u8]) -> Result<(), Error>
    {
        self.get_inner().write_all(file, buf)
    }

    fn flush_inner(&self, _file: &mut FileInner) -> Result<(), Error>
    {
        // There is no buffering here, because everything is just
        // virtual and in memory and there isn't actually a disk.
        Ok(())
    }

    fn metadata_inner(&self, file: &FileInner) -> Result<Metadata, Error>
    {
        self.get_inner().metadata_inner(file)
    }

    fn seek_inner(&self, file: &mut FileInner, pos: SeekFrom) -> Result<u64, Error>
    {
        self.get_inner().seek(file, pos)
    }

    fn create_dir_inner(&self, path: &Path) -> Result<(), Error>
    {
        self.get_inner().create_dir(path)
    }

    fn create_dir_all_inner(&self, path: &Path) -> Result<(), Error>
    {
        self.get_inner().create_dir_all(path)
    }

    fn close_inner(&self, mut file: FileInner) -> Result<(), Error>
    {
        self.get_inner().close(&mut file);

        Ok(())
    }

    fn walkdir_inner(&self, path: &Path) -> WalkDir<Self>
    {
        WalkDir::new_default(self.clone(), path.to_owned())
    }

    fn into_walkdir_iterator(&self, walkdir: WalkDir<Self>) -> WalkdirIterator<Self>
    {
        self.get_inner().into_walkdir_iterator(walkdir)
    }

    fn walkdir_next_inner(
        &self,
        inner: &mut WalkdirIteratorInner,
    ) -> Option<Result<DirEntry, Error>>
    {
        // The documentation for this lint says that this lint guards
        // against stuff like accidentally locking a mutex for the
        // whole match and therefore maybe deadlocking it (if one
        // wants to use the mutex again, but doesn't remember that the
        // mutex is already locked).  This isn't a mutex and I'm also
        // not seeing any other pitfalls, so I think it's a false
        // positive.
        #[allow(clippy::significant_drop_in_scrutinee)]
        match self.get_inner().walkdir_next_inner(inner)
        {
            Some(Err(err)) if err.kind() == ErrorKind::NotFound => None,
            x => x,
        }
    }

    #[cfg(feature = "trash")]
    fn trash_delete_inner(&self, path: &Path) -> Result<(), trash::Error>
    {
        self.get_inner()
            .trash_delete(path)
            .map_err(trash::into_unknown)
    }
}

impl VirtualDiskit
{
    fn get_inner(&self) -> MutexGuard<'_, VirtualDiskitInner>
    {
        self.inner.lock().expect("A virtual diskit panicked")
    }
}