1use_prelude!();
7use std::{
8 env, fs,
9 path::{Path, PathBuf},
10};
11
12use crate::error::{DittoError, ErrorKind};
13
14pub trait DittoRoot: Send + Sync {
17 fn root_path(&self) -> &Path;
19
20 #[deprecated(note = "Use root_path instead")]
22 fn data_path(&self) -> &Path {
23 self.root_path()
24 }
25
26 fn root_dir_to_c_str(&self) -> Result<char_p::Box, DittoError> {
28 Ok(char_p::new(self.root_path_as_str()?))
29 }
30
31 #[deprecated(note = "Use root_dir_to_c_str instead")]
33 fn data_dir_to_c_str(&self) -> Result<char_p::Box, DittoError> {
34 self.root_dir_to_c_str()
35 }
36
37 fn exists(&self) -> bool;
39
40 fn is_valid(&self) -> Result<(), DittoError>;
42
43 fn root_path_as_str(&self) -> Result<&str, DittoError> {
45 self.root_path().to_str().ok_or_else(|| {
46 DittoError::new(
47 ErrorKind::InvalidInput,
48 "Path is not valid UTF-8".to_string(),
49 )
50 })
51 }
52
53 #[deprecated(note = "Use root_path_as_str instead")]
55 fn data_path_as_str(&self) -> Result<&str, DittoError> {
56 self.root_path_as_str()
57 }
58}
59
60pub struct PersistentRoot {
63 root: PathBuf,
64}
65
66pub struct TempRoot {
68 root: PathBuf,
69}
70
71impl Drop for TempRoot {
72 fn drop(&mut self) {
73 let ditto_root = &mut self.root; ditto_root.pop(); debug!(path = %ditto_root.display(), "removing TempRoot");
77 let _ = ::std::fs::remove_dir_all(&ditto_root);
78 }
79}
80
81impl Default for PersistentRoot {
82 fn default() -> Self {
83 PersistentRoot::from_current_exe().unwrap() }
85}
86
87impl DittoRoot for PersistentRoot {
88 fn root_path(&self) -> &Path {
89 self.root.as_path()
90 }
91 fn exists(&self) -> bool {
92 self.root.exists()
93 }
94 fn is_valid(&self) -> Result<(), DittoError> {
95 Ok(()) }
97}
98
99impl PersistentRoot {
101 pub fn new(root_dir: impl Into<PathBuf>) -> Result<Self, DittoError> {
103 let root = root_dir.into();
104 #[allow(clippy::disallowed_methods)]
105 std::fs::create_dir_all(&root).map_err(DittoError::from)?;
106 let dir = PersistentRoot { root };
107
108 if let Err(e) = dir.is_valid() {
109 Err(e)
110 } else {
111 Ok(dir)
112 }
113 }
114
115 pub fn from_current_exe() -> Result<Self, DittoError> {
117 let root_dir = env::current_exe()
118 .ok()
119 .and_then(|abspath| abspath.parent().map(|x| x.to_path_buf()))
120 .ok_or_else(|| {
121 DittoError::new(
122 ErrorKind::InvalidInput,
123 "Unable to resolve a default data directory on this platform".to_string(),
124 )
125 })?;
126 Self::new(root_dir)
127 }
128
129 pub fn from_env() -> Result<Self, DittoError> {
131 let root_dir: PathBuf = env::var_os("DITTO_ROOT_PATH")
132 .map(PathBuf::from)
133 .ok_or_else(|| {
134 DittoError::new(
135 ErrorKind::InvalidInput,
136 "The DITTO_ROOT_PATH env var is not set".to_string(),
137 )
138 })?;
139 Self::new(root_dir)
140 }
141}
142
143impl DittoRoot for TempRoot {
144 fn root_path(&self) -> &Path {
145 self.root.as_path()
146 }
147 fn exists(&self) -> bool {
148 self.root.exists()
149 }
150 fn is_valid(&self) -> Result<(), DittoError> {
151 Ok(()) }
153}
154
155impl TempRoot {
156 const NUM_RETRIES: u32 = 1 << 31;
159 const NUM_RAND_CHARS: usize = 12;
160
161 pub fn new() -> Self {
163 use std::iter;
164
165 use rand::{distributions::Alphanumeric, Rng};
166 let mut ditto_root;
167 let tmpdir = std::env::temp_dir(); if !tmpdir.is_absolute() {
169 let cur_dir = env::current_exe()
170 .ok()
171 .and_then(|abspath| abspath.parent().map(|x| x.to_path_buf()))
172 .unwrap();
173 ditto_root = cur_dir;
174 ditto_root.push(tmpdir);
175 } else {
176 ditto_root = tmpdir;
177 }
178
179 let mut rng = rand::thread_rng();
180 for _ in 0..Self::NUM_RETRIES {
181 let jitter: String = iter::repeat(())
182 .map(|()| rng.sample(Alphanumeric))
183 .map(char::from)
184 .take(Self::NUM_RAND_CHARS)
185 .collect();
186 ditto_root.push(jitter);
187 #[allow(clippy::disallowed_methods)]
188 match fs::create_dir_all(&ditto_root) {
189 Ok(_) => {
190 ditto_root.push("ditto");
191 #[allow(clippy::disallowed_methods)]
192 fs::create_dir_all(&ditto_root).unwrap();
193 return TempRoot { root: ditto_root };
194 }
195 Err(e) if e.kind() == std::io::ErrorKind::AlreadyExists => continue,
196 Err(e) => {
197 panic!("Unable to create tempdir {:?}", e);
198 }
199 }
200 }
201 panic!("TempRoot {:?} already exists!", ditto_root.display());
202 }
203}
204
205impl Default for TempRoot {
206 fn default() -> Self {
207 TempRoot::new()
208 }
209}
210
211pub(crate) fn drain_ditto_data_dir(root: &Arc<dyn DittoRoot>) {
219 let root: PathBuf = root.root_path().into();
220 let old_data: PathBuf = root.join("ditto_data");
221 if old_data.exists() {
222 debug!(old_path = ?old_data, new_path = ?root, "migrating `ditto_data` to 'ditto' dir");
223 let mut copy_options = fs_extra::dir::CopyOptions::new();
224 copy_options.overwrite = true;
225 let data_children: Vec<_> = std::fs::read_dir(&old_data)
231 .unwrap_or_else(|err| panic!("Failed to access {old_data:?}: {err}"))
232 .filter_map(|child| child.ok())
233 .map(|child| child.path())
234 .collect();
235 fs_extra::copy_items(&data_children, &root, ©_options).unwrap();
237 std::fs::remove_dir_all(old_data).unwrap();
238 }
239}