use_prelude!();
use std::{
env, fs,
path::{Path, PathBuf},
};
use tracing::debug;
use crate::error::{DittoError, ErrorKind};
pub trait DittoRoot: Send + Sync {
fn root_path(&self) -> &Path;
#[deprecated(note = "Use root_path instead")]
fn data_path(&self) -> &Path {
self.root_path()
}
fn root_dir_to_c_str(&self) -> Result<char_p::Box, DittoError> {
Ok(char_p::new(self.root_path_as_str()?))
}
#[deprecated(note = "Use root_dir_to_c_str instead")]
fn data_dir_to_c_str(&self) -> Result<char_p::Box, DittoError> {
self.root_dir_to_c_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(),
)
})
}
#[deprecated(note = "Use root_path_as_str instead")]
fn data_path_as_str(&self) -> Result<&str, DittoError> {
self.root_path_as_str()
}
}
pub struct PersistentRoot {
root: PathBuf,
}
pub struct TempRoot {
root: PathBuf,
}
impl Drop for TempRoot {
fn drop(&mut self) {
let ditto_root = &mut self.root; ditto_root.pop(); 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() }
}
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(()) }
}
impl PersistentRoot {
pub fn new(root_dir: impl Into<PathBuf>) -> Result<Self, DittoError> {
let root = root_dir.into();
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)
}
}
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 exists(&self) -> bool {
self.root.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");
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()
}
}
pub(crate) fn drain_ditto_data_dir(root: &Arc<dyn DittoRoot>) {
let root: PathBuf = root.root_path().into();
let old_data: PathBuf = root.join("ditto_data");
if old_data.exists() {
debug!(old_path = ?old_data, new_path = ?root, "migrating `ditto_data` to 'ditto' dir");
let mut copy_options = fs_extra::dir::CopyOptions::new();
copy_options.overwrite = true;
let data_children: Vec<_> = std::fs::read_dir(&old_data)
.unwrap_or_else(|err| panic!("Failed to access {old_data:?}: {err}"))
.filter_map(|child| child.ok())
.map(|child| child.path())
.collect();
fs_extra::copy_items(&data_children, &root, ©_options).unwrap();
std::fs::remove_dir_all(old_data).unwrap();
}
}