1use_prelude!();
7use std::{
8 env, fs,
9 path::{Path, PathBuf},
10};
11
12use crate::{
13 debug,
14 error::{DittoError, ErrorKind},
15};
16
17pub trait DittoRoot: Send + Sync {
20 fn root_path(&self) -> &Path;
22
23 fn root_dir_to_c_str(&self) -> Result<char_p::Box, DittoError> {
25 Ok(char_p::new(self.root_path_as_str()?))
26 }
27
28 fn exists(&self) -> bool;
30
31 fn is_valid(&self) -> Result<(), DittoError>;
33
34 fn root_path_as_str(&self) -> Result<&str, DittoError> {
36 self.root_path().to_str().ok_or_else(|| {
37 DittoError::new(
38 ErrorKind::InvalidInput,
39 "Path is not valid UTF-8".to_string(),
40 )
41 })
42 }
43}
44
45pub struct PersistentRoot {
48 root: PathBuf,
49}
50
51pub struct TempRoot {
53 root: PathBuf,
54}
55
56impl Drop for TempRoot {
57 fn drop(&mut self) {
58 let ditto_root = &mut self.root; ditto_root.pop(); #[allow(deprecated)] {
63 debug!(path = %ditto_root.display(), "removing TempRoot");
64 }
65 let _ = ::std::fs::remove_dir_all(&ditto_root);
66 }
67}
68
69impl Default for PersistentRoot {
70 fn default() -> Self {
71 PersistentRoot::from_current_exe().unwrap() }
73}
74
75impl DittoRoot for PersistentRoot {
76 fn root_path(&self) -> &Path {
77 self.root.as_path()
78 }
79 fn exists(&self) -> bool {
80 self.root.exists()
81 }
82 fn is_valid(&self) -> Result<(), DittoError> {
83 Ok(()) }
85}
86
87impl PersistentRoot {
89 pub fn new(root_dir: impl Into<PathBuf>) -> Result<Self, DittoError> {
91 let root = root_dir.into();
92 #[allow(clippy::disallowed_methods)]
93 std::fs::create_dir_all(&root).map_err(DittoError::from)?;
94 let dir = PersistentRoot { root };
95
96 if let Err(e) = dir.is_valid() {
97 Err(e)
98 } else {
99 Ok(dir)
100 }
101 }
102
103 pub fn from_current_exe() -> Result<Self, DittoError> {
105 let root_dir = env::current_exe()
106 .ok()
107 .and_then(|abspath| abspath.parent().map(|x| x.to_path_buf()))
108 .ok_or_else(|| {
109 DittoError::new(
110 ErrorKind::InvalidInput,
111 "Unable to resolve a default data directory on this platform".to_string(),
112 )
113 })?;
114 Self::new(root_dir)
115 }
116
117 pub fn from_env() -> Result<Self, DittoError> {
119 let root_dir: PathBuf = env::var_os("DITTO_ROOT_PATH")
120 .map(PathBuf::from)
121 .ok_or_else(|| {
122 DittoError::new(
123 ErrorKind::InvalidInput,
124 "The DITTO_ROOT_PATH env var is not set".to_string(),
125 )
126 })?;
127 Self::new(root_dir)
128 }
129}
130
131impl DittoRoot for TempRoot {
132 fn root_path(&self) -> &Path {
133 self.root.as_path()
134 }
135 fn exists(&self) -> bool {
136 self.root.exists()
137 }
138 fn is_valid(&self) -> Result<(), DittoError> {
139 Ok(()) }
141}
142
143impl TempRoot {
144 const NUM_RETRIES: u32 = 1 << 31;
147 const NUM_RAND_CHARS: usize = 12;
148
149 pub fn new() -> Self {
151 use std::iter;
152
153 use rand::{distr::Alphanumeric, Rng};
154 let mut ditto_root;
155 let tmpdir = std::env::temp_dir(); if !tmpdir.is_absolute() {
157 let cur_dir = env::current_exe()
158 .ok()
159 .and_then(|abspath| abspath.parent().map(|x| x.to_path_buf()))
160 .unwrap();
161 ditto_root = cur_dir;
162 ditto_root.push(tmpdir);
163 } else {
164 ditto_root = tmpdir;
165 }
166
167 let mut rng = rand::rng();
168 for _ in 0..Self::NUM_RETRIES {
169 let jitter: String = iter::repeat(())
170 .map(|()| rng.sample(Alphanumeric))
171 .map(char::from)
172 .take(Self::NUM_RAND_CHARS)
173 .collect();
174 ditto_root.push(jitter);
175 #[allow(clippy::disallowed_methods)]
176 match fs::create_dir_all(&ditto_root) {
177 Ok(_) => {
178 ditto_root.push("ditto");
179 #[allow(clippy::disallowed_methods)]
180 fs::create_dir_all(&ditto_root).unwrap();
181 return TempRoot { root: ditto_root };
182 }
183 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
184 Err(e) => {
185 panic!("Unable to create tempdir {:?}", e);
186 }
187 }
188 }
189 panic!("TempRoot {:?} already exists!", ditto_root.display());
190 }
191}
192
193impl Default for TempRoot {
194 fn default() -> Self {
195 TempRoot::new()
196 }
197}