dittolive-ditto 3.0.0-alpha2

Ditto is a peer to peer cross-platform database that allows mobile, web, IoT and server apps to sync with or without an internet connection.
Documentation
//! Ditto Root Directory Wrapper
//!
//! A utility module for creating, managing, and removing the Ditto directory
//! structures on the local file system

use_prelude!();
use std::{
    env, fs,
    path::{Path, PathBuf},
};

use crate::error::{DittoError, ErrorKind};

/// Interface provided by the Filesystem directory into which Ditto will store
/// its local data
pub trait DittoRoot: Send + Sync {
    fn root_path(&self) -> &Path;

    fn data_path(&self) -> &Path;

    /// Emits the Ditto Root Directory Path as a null-terminated UTF-8 C-string
    fn root_dir_to_c_str(&self) -> Result<char_p::Box, DittoError> {
        Ok(char_p::new(self.root_path_as_str()?))
    }

    /// Emits the Ditto Data Directory Path as a null-terminated UTF-8 C-string
    fn data_dir_to_c_str(&self) -> Result<char_p::Box, DittoError> {
        Ok(char_p::new(self.data_path_as_str()?))
    }

    fn exists(&self) -> bool;

    fn is_valid(&self) -> Result<(), DittoError>;

    fn root_path_as_str(&self) -> Result<&str, DittoError> {
        self.root_path().to_str().ok_or_else(|| {
            DittoError::new(
                ErrorKind::InvalidInput,
                "Path is not valid UTF-8".to_string(),
            )
        })
    }

    /// Returns the path of the Ditto Data Directory as a &str
    fn data_path_as_str(&self) -> Result<&str, DittoError> {
        self.data_path().to_str().ok_or_else(|| {
            DittoError::new(
                ErrorKind::InvalidInput,
                "Path is not valid UTF-8".to_string(),
            )
        })
    }
}

// A persistent working directory where Ditto will store its data
// across restarts of the host process on the local file system
pub struct PersistentRoot {
    root: PathBuf,
    data: PathBuf,
}

// A Temporary Ditto Root which will clean itself up on exit
pub struct TempRoot {
    root: PathBuf,
    data: PathBuf,
}

impl Drop for TempRoot {
    fn drop(&mut self) {
        // we need to step up one level
        let mut ditto_root = self.root.clone(); // tempdir/jitter/ditto_root
        ditto_root.pop(); // tempdir/jitter <-- remove here
        ::log::debug!("Removing TempRoot {}", ditto_root.display());
        let _ = ::std::fs::remove_dir_all(&ditto_root);
    }
}

impl Default for PersistentRoot {
    fn default() -> Self {
        PersistentRoot::from_current_exe().unwrap() // This should never fail
    }
}

impl DittoRoot for PersistentRoot {
    fn root_path(&self) -> &Path {
        self.root.as_path()
    }
    fn data_path(&self) -> &Path {
        self.data.as_path()
    }
    fn exists(&self) -> bool {
        self.root.exists() && self.data.exists()
    }
    fn is_valid(&self) -> Result<(), DittoError> {
        Ok(()) // TODO
    }
}

// Constructors
impl PersistentRoot {
    /// Manually construct a root directory
    pub fn new(root_dir: impl Into<PathBuf>) -> Result<Self, DittoError> {
        let root = root_dir.into();
        let mut data_dir = root.clone();
        data_dir.push("ditto_data");
        std::fs::create_dir_all(data_dir.as_path()).map_err(DittoError::from)?;
        let dir = PersistentRoot {
            root,
            data: data_dir,
        };

        if let Err(e) = dir.is_valid() {
            Err(e)
        } else {
            Ok(dir)
        }
    }
    /// Get a Ditto Directory from the path of the current executable
    pub fn from_current_exe() -> Result<Self, DittoError> {
        let root_dir = env::current_exe()
            .ok()
            .and_then(|abspath| abspath.parent().map(|x| x.to_path_buf()))
            .ok_or_else(|| {
                DittoError::new(
                    ErrorKind::InvalidInput,
                    "Unable to resolve a default data directory on this platform".to_string(),
                )
            })?;
        Self::new(root_dir)
    }

    /// Create a Ditto Directory from Env Vars
    pub fn from_env() -> Result<Self, DittoError> {
        let root_dir: PathBuf = env::var_os("DITTO_ROOT_PATH")
            .map(PathBuf::from)
            .ok_or_else(|| {
                DittoError::new(
                    ErrorKind::InvalidInput,
                    "The DITTO_ROOT_PATH env var is not set".to_string(),
                )
            })?;
        Self::new(root_dir)
    }
}

impl DittoRoot for TempRoot {
    fn root_path(&self) -> &Path {
        self.root.as_path()
    }
    fn data_path(&self) -> &Path {
        self.data.as_path()
    }
    fn exists(&self) -> bool {
        self.root.exists() && self.data.exists()
    }
    fn is_valid(&self) -> Result<(), DittoError> {
        Ok(()) // TODO
    }
}
impl TempRoot {
    // Temp randomization impl adapted from temp_dir crate
    // but with a contemporary version of the rand crate
    const NUM_RETRIES: u32 = 1 << 31;
    const NUM_RAND_CHARS: usize = 12;
    /// Create a new Ditto Root which will be deleted when TempRoot is dropped
    pub fn new() -> Self {
        use std::iter;

        use rand::{distributions::Alphanumeric, Rng};
        let mut ditto_root;
        let tmpdir = std::env::temp_dir(); // the OS provided temp root from TMPDIR
        if !tmpdir.is_absolute() {
            let cur_dir = env::current_exe()
                .ok()
                .and_then(|abspath| abspath.parent().map(|x| x.to_path_buf()))
                .unwrap();
            ditto_root = cur_dir;
            ditto_root.push(tmpdir);
        } else {
            ditto_root = tmpdir;
        }

        let mut rng = rand::thread_rng();
        for _ in 0..Self::NUM_RETRIES {
            let jitter: String = iter::repeat(())
                .map(|()| rng.sample(Alphanumeric))
                .map(char::from)
                .take(Self::NUM_RAND_CHARS)
                .collect();
            ditto_root.push(jitter);
            match fs::create_dir_all(&ditto_root) {
                Ok(_) => {
                    ditto_root.push("ditto");
                    let mut data_dir = ditto_root.clone();
                    data_dir.push("ditto_data");
                    fs::create_dir_all(&ditto_root).unwrap();
                    fs::create_dir_all(&data_dir).unwrap();
                    return TempRoot {
                        root: ditto_root,
                        data: data_dir,
                    };
                }
                Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
                Err(e) => {
                    panic!("Unable to create tempdir {:?}", e);
                }
            }
        }
        panic!("TempRoot {:?} already exists!", ditto_root.display());
    }
}

impl Default for TempRoot {
    fn default() -> Self {
        TempRoot::new()
    }
}