1use crate::path::virtual_path::VirtualPath;
3use crate::validator::path_history::PathHistory;
4use crate::PathBoundary;
5use crate::Result;
6use std::marker::PhantomData;
7use std::path::Path;
8#[cfg(feature = "tempfile")]
9use std::sync::Arc;
10
11#[cfg(feature = "tempfile")]
13use tempfile::TempDir;
14
15#[derive(Clone)]
17pub struct VirtualRoot<Marker = ()> {
18 pub(crate) root: PathBoundary<Marker>,
19 #[cfg(feature = "tempfile")]
21 pub(crate) _temp_dir: Option<Arc<TempDir>>, pub(crate) _marker: PhantomData<Marker>,
23}
24
25impl<Marker> VirtualRoot<Marker> {
26 #[inline]
39 pub fn try_new<P: AsRef<Path>>(root_path: P) -> Result<Self> {
40 let root = PathBoundary::try_new(root_path)?;
41 Ok(Self {
42 root,
43 #[cfg(feature = "tempfile")]
44 _temp_dir: None,
45 _marker: PhantomData,
46 })
47 }
48
49 #[cfg(feature = "tempfile")]
66 #[inline]
67 pub fn try_new_temp() -> Result<Self> {
68 let root = PathBoundary::try_new_temp()?;
69 let temp_dir = root.temp_dir_arc();
70 Ok(Self {
71 root,
72 #[cfg(feature = "tempfile")]
73 _temp_dir: temp_dir,
74 _marker: PhantomData,
75 })
76 }
77
78 #[cfg(feature = "tempfile")]
95 #[inline]
96 pub fn try_new_temp_with_prefix(prefix: &str) -> Result<Self> {
97 let root = PathBoundary::try_new_temp_with_prefix(prefix)?;
98 let temp_dir = root.temp_dir_arc();
99 Ok(Self {
100 root,
101 #[cfg(feature = "tempfile")]
102 _temp_dir: temp_dir,
103 _marker: PhantomData,
104 })
105 }
106
107 #[inline]
109 pub fn metadata(&self) -> std::io::Result<std::fs::Metadata> {
110 self.root.metadata()
111 }
112
113 #[inline]
119 pub fn read_dir(&self) -> std::io::Result<std::fs::ReadDir> {
120 self.root.read_dir()
121 }
122
123 #[inline]
127 pub fn remove_dir(&self) -> std::io::Result<()> {
128 self.root.remove_dir()
129 }
130
131 #[inline]
135 pub fn remove_dir_all(&self) -> std::io::Result<()> {
136 self.root.remove_dir_all()
137 }
138
139 #[inline]
151 pub fn try_new_create<P: AsRef<Path>>(root_path: P) -> Result<Self> {
152 let root = PathBoundary::try_new_create(root_path)?;
153 Ok(Self {
154 root,
155 #[cfg(feature = "tempfile")]
156 _temp_dir: None,
157 _marker: PhantomData,
158 })
159 }
160
161 #[inline]
166 pub fn virtual_join<P: AsRef<Path>>(&self, candidate_path: P) -> Result<VirtualPath<Marker>> {
167 let user_candidate = candidate_path.as_ref().to_path_buf();
169 let anchored = PathHistory::new(user_candidate).canonicalize_anchored(&self.root)?;
170
171 let validated = anchored.boundary_check(self.root.stated_path())?;
173
174 let jp = crate::path::strict_path::StrictPath::new(
176 std::sync::Arc::new(self.root.clone()),
177 validated,
178 );
179 Ok(jp.virtualize())
180 }
181
182 #[inline]
184 pub(crate) fn path(&self) -> &Path {
185 self.root.path()
186 }
187
188 #[inline]
193 pub fn interop_path(&self) -> &std::ffi::OsStr {
194 self.root.interop_path()
195 }
196
197 #[inline]
199 pub fn exists(&self) -> bool {
200 self.root.exists()
201 }
202
203 #[inline]
208 pub fn as_unvirtual(&self) -> &PathBoundary<Marker> {
209 &self.root
210 }
211
212 #[inline]
217 pub fn unvirtual(self) -> PathBoundary<Marker> {
218 self.root
219 }
220
221 #[cfg(feature = "dirs")]
234 pub fn try_new_os_config(app_name: &str) -> Result<Self> {
235 let root = crate::PathBoundary::try_new_os_config(app_name)?;
236 Ok(Self {
237 root,
238 #[cfg(feature = "tempfile")]
239 _temp_dir: None,
240 _marker: PhantomData,
241 })
242 }
243
244 #[cfg(feature = "dirs")]
246 pub fn try_new_os_data(app_name: &str) -> Result<Self> {
247 let root = crate::PathBoundary::try_new_os_data(app_name)?;
248 Ok(Self {
249 root,
250 #[cfg(feature = "tempfile")]
251 _temp_dir: None,
252 _marker: PhantomData,
253 })
254 }
255
256 #[cfg(feature = "dirs")]
258 pub fn try_new_os_cache(app_name: &str) -> Result<Self> {
259 let root = crate::PathBoundary::try_new_os_cache(app_name)?;
260 Ok(Self {
261 root,
262 #[cfg(feature = "tempfile")]
263 _temp_dir: None,
264 _marker: PhantomData,
265 })
266 }
267
268 #[cfg(feature = "dirs")]
270 pub fn try_new_os_config_local(app_name: &str) -> Result<Self> {
271 let root = crate::PathBoundary::try_new_os_config_local(app_name)?;
272 Ok(Self {
273 root,
274 #[cfg(feature = "tempfile")]
275 _temp_dir: None,
276 _marker: PhantomData,
277 })
278 }
279
280 #[cfg(feature = "dirs")]
282 pub fn try_new_os_data_local(app_name: &str) -> Result<Self> {
283 let root = crate::PathBoundary::try_new_os_data_local(app_name)?;
284 Ok(Self {
285 root,
286 #[cfg(feature = "tempfile")]
287 _temp_dir: None,
288 _marker: PhantomData,
289 })
290 }
291
292 #[cfg(feature = "dirs")]
294 pub fn try_new_os_home() -> Result<Self> {
295 let root = crate::PathBoundary::try_new_os_home()?;
296 Ok(Self {
297 root,
298 #[cfg(feature = "tempfile")]
299 _temp_dir: None,
300 _marker: PhantomData,
301 })
302 }
303
304 #[cfg(feature = "dirs")]
306 pub fn try_new_os_desktop() -> Result<Self> {
307 let root = crate::PathBoundary::try_new_os_desktop()?;
308 Ok(Self {
309 root,
310 #[cfg(feature = "tempfile")]
311 _temp_dir: None,
312 _marker: PhantomData,
313 })
314 }
315
316 #[cfg(feature = "dirs")]
318 pub fn try_new_os_documents() -> Result<Self> {
319 let root = crate::PathBoundary::try_new_os_documents()?;
320 Ok(Self {
321 root,
322 #[cfg(feature = "tempfile")]
323 _temp_dir: None,
324 _marker: PhantomData,
325 })
326 }
327
328 #[cfg(feature = "dirs")]
330 pub fn try_new_os_downloads() -> Result<Self> {
331 let root = crate::PathBoundary::try_new_os_downloads()?;
332 Ok(Self {
333 root,
334 #[cfg(feature = "tempfile")]
335 _temp_dir: None,
336 _marker: PhantomData,
337 })
338 }
339
340 #[cfg(feature = "dirs")]
342 pub fn try_new_os_pictures() -> Result<Self> {
343 let root = crate::PathBoundary::try_new_os_pictures()?;
344 Ok(Self {
345 root,
346 #[cfg(feature = "tempfile")]
347 _temp_dir: None,
348 _marker: PhantomData,
349 })
350 }
351
352 #[cfg(feature = "dirs")]
354 pub fn try_new_os_audio() -> Result<Self> {
355 let root = crate::PathBoundary::try_new_os_audio()?;
356 Ok(Self {
357 root,
358 #[cfg(feature = "tempfile")]
359 _temp_dir: None,
360 _marker: PhantomData,
361 })
362 }
363
364 #[cfg(feature = "dirs")]
366 pub fn try_new_os_videos() -> Result<Self> {
367 let root = crate::PathBoundary::try_new_os_videos()?;
368 Ok(Self {
369 root,
370 #[cfg(feature = "tempfile")]
371 _temp_dir: None,
372 _marker: PhantomData,
373 })
374 }
375
376 #[cfg(feature = "dirs")]
378 pub fn try_new_os_executables() -> Result<Self> {
379 let root = crate::PathBoundary::try_new_os_executables()?;
380 Ok(Self {
381 root,
382 #[cfg(feature = "tempfile")]
383 _temp_dir: None,
384 _marker: PhantomData,
385 })
386 }
387
388 #[cfg(feature = "dirs")]
390 pub fn try_new_os_runtime() -> Result<Self> {
391 let root = crate::PathBoundary::try_new_os_runtime()?;
392 Ok(Self {
393 root,
394 #[cfg(feature = "tempfile")]
395 _temp_dir: None,
396 _marker: PhantomData,
397 })
398 }
399
400 #[cfg(feature = "dirs")]
402 pub fn try_new_os_state(app_name: &str) -> Result<Self> {
403 let root = crate::PathBoundary::try_new_os_state(app_name)?;
404 Ok(Self {
405 root,
406 #[cfg(feature = "tempfile")]
407 _temp_dir: None,
408 _marker: PhantomData,
409 })
410 }
411
412 #[cfg(feature = "app-path")]
417 pub fn try_new_app_path(subdir: &str, env_override: Option<&str>) -> Result<Self> {
418 let root = crate::PathBoundary::try_new_app_path(subdir, env_override)?;
419 Ok(Self {
420 root,
421 #[cfg(feature = "tempfile")]
422 _temp_dir: None,
423 _marker: PhantomData,
424 })
425 }
426}
427
428impl<Marker> std::fmt::Display for VirtualRoot<Marker> {
429 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
430 write!(f, "{}", self.path().display())
431 }
432}
433
434impl<Marker> AsRef<Path> for VirtualRoot<Marker> {
435 fn as_ref(&self) -> &Path {
436 self.path()
437 }
438}
439
440impl<Marker> std::fmt::Debug for VirtualRoot<Marker> {
441 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
442 f.debug_struct("VirtualRoot")
443 .field("root", &self.path())
444 .field("marker", &std::any::type_name::<Marker>())
445 .finish()
446 }
447}
448
449impl<Marker> PartialEq for VirtualRoot<Marker> {
450 #[inline]
451 fn eq(&self, other: &Self) -> bool {
452 self.path() == other.path()
453 }
454}
455
456impl<Marker> Eq for VirtualRoot<Marker> {}
457
458impl<Marker> std::hash::Hash for VirtualRoot<Marker> {
459 #[inline]
460 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
461 self.path().hash(state);
462 }
463}
464
465impl<Marker> PartialOrd for VirtualRoot<Marker> {
466 #[inline]
467 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
468 Some(self.cmp(other))
469 }
470}
471
472impl<Marker> Ord for VirtualRoot<Marker> {
473 #[inline]
474 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
475 self.path().cmp(other.path())
476 }
477}
478
479impl<Marker> PartialEq<crate::PathBoundary<Marker>> for VirtualRoot<Marker> {
480 #[inline]
481 fn eq(&self, other: &crate::PathBoundary<Marker>) -> bool {
482 self.path() == other.path()
483 }
484}
485
486impl<Marker> PartialEq<std::path::Path> for VirtualRoot<Marker> {
487 #[inline]
488 fn eq(&self, other: &std::path::Path) -> bool {
489 let other_str = other.to_string_lossy();
492
493 #[cfg(windows)]
494 let other_normalized = other_str.replace('\\', "/");
495 #[cfg(not(windows))]
496 let other_normalized = other_str.to_string();
497
498 let normalized_other = if other_normalized.starts_with('/') {
499 other_normalized
500 } else {
501 format!("/{}", other_normalized)
502 };
503
504 "/" == normalized_other
505 }
506}
507
508impl<Marker> PartialEq<std::path::PathBuf> for VirtualRoot<Marker> {
509 #[inline]
510 fn eq(&self, other: &std::path::PathBuf) -> bool {
511 self.eq(other.as_path())
512 }
513}
514
515impl<Marker> PartialEq<&std::path::Path> for VirtualRoot<Marker> {
516 #[inline]
517 fn eq(&self, other: &&std::path::Path) -> bool {
518 self.eq(*other)
519 }
520}
521
522impl<Marker> PartialOrd<std::path::Path> for VirtualRoot<Marker> {
523 #[inline]
524 fn partial_cmp(&self, other: &std::path::Path) -> Option<std::cmp::Ordering> {
525 let other_str = other.to_string_lossy();
527
528 if other_str.is_empty() {
530 return Some(std::cmp::Ordering::Greater);
531 }
532
533 #[cfg(windows)]
534 let other_normalized = other_str.replace('\\', "/");
535 #[cfg(not(windows))]
536 let other_normalized = other_str.to_string();
537
538 let normalized_other = if other_normalized.starts_with('/') {
539 other_normalized
540 } else {
541 format!("/{}", other_normalized)
542 };
543
544 Some("/".cmp(&normalized_other))
545 }
546}
547
548impl<Marker> PartialOrd<&std::path::Path> for VirtualRoot<Marker> {
549 #[inline]
550 fn partial_cmp(&self, other: &&std::path::Path) -> Option<std::cmp::Ordering> {
551 self.partial_cmp(*other)
552 }
553}
554
555impl<Marker> PartialOrd<std::path::PathBuf> for VirtualRoot<Marker> {
556 #[inline]
557 fn partial_cmp(&self, other: &std::path::PathBuf) -> Option<std::cmp::Ordering> {
558 self.partial_cmp(other.as_path())
559 }
560}
561
562impl<Marker: Default> std::str::FromStr for VirtualRoot<Marker> {
563 type Err = crate::StrictPathError;
564
565 #[inline]
580 fn from_str(path: &str) -> std::result::Result<Self, Self::Err> {
581 Self::try_new_create(path)
582 }
583}