dittolive-ditto 5.0.0

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::{
    debug,
    error::{DittoError, ErrorKind},
};

/// Interface provided by the Filesystem directory into which Ditto will store
/// its local data
pub trait DittoRoot: Send + Sync {
    /// Return the root path of the Ditto directory
    fn root_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()?))
    }

    /// Return true if the Root path exists
    fn exists(&self) -> bool;

    /// Return true if the provided Root path is a valid path
    fn is_valid(&self) -> Result<(), DittoError>;

    /// Return the Root path as a `&str`
    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(),
            )
        })
    }
}

/// 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,
}

/// A Temporary Ditto persistence directory which will be deleted from storage when dropped.
pub struct TempRoot {
    root: PathBuf,
}

impl Drop for TempRoot {
    fn drop(&mut self) {
        // we need to step up one level
        let ditto_root = &mut self.root; // tempdir/jitter/ditto_root
        ditto_root.pop(); // tempdir/jitter <-- remove here
        #[allow(deprecated)] // Workaround for patched tracing
        {
            debug!(path = %ditto_root.display(), "removing TempRoot");
        }
        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 exists(&self) -> bool {
        self.root.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();
        #[allow(clippy::disallowed_methods)]
        std::fs::create_dir_all(&root).map_err(DittoError::from)?;
        let dir = PersistentRoot { root };

        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 exists(&self) -> bool {
        self.root.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::{distr::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::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);
            #[allow(clippy::disallowed_methods)]
            match fs::create_dir_all(&ditto_root) {
                Ok(_) => {
                    ditto_root.push("ditto");
                    #[allow(clippy::disallowed_methods)]
                    fs::create_dir_all(&ditto_root).unwrap();
                    return TempRoot { root: ditto_root };
                }
                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()
    }
}