use_prelude!();
use std::{
env, fs,
path::{Path, PathBuf},
};
use crate::error::{DittoError, ErrorKind};
pub trait DittoRoot: Send + Sync {
fn root_path(&self) -> &Path;
fn data_path(&self) -> &Path;
fn root_dir_to_c_str(&self) -> Result<char_p::Box, DittoError> {
Ok(char_p::new(self.root_path_as_str()?))
}
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(),
)
})
}
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(),
)
})
}
}
pub struct PersistentRoot {
root: PathBuf,
data: PathBuf,
}
pub struct TempRoot {
root: PathBuf,
data: PathBuf,
}
impl Drop for TempRoot {
fn drop(&mut self) {
let mut ditto_root = self.root.clone(); ditto_root.pop(); ::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() }
}
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(()) }
}
impl PersistentRoot {
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)
}
}
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)
}
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(()) }
}
impl TempRoot {
const NUM_RETRIES: u32 = 1 << 31;
const NUM_RAND_CHARS: usize = 12;
pub fn new() -> Self {
use std::iter;
use rand::{distributions::Alphanumeric, Rng};
let mut ditto_root;
let tmpdir = std::env::temp_dir(); 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()
}
}