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 pub fn virtual_symlink(
115 &self,
116 link_path: &crate::path::virtual_path::VirtualPath<Marker>,
117 ) -> std::io::Result<()> {
118 let root = self
119 .virtual_join("")
120 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
121
122 root.as_unvirtual().strict_symlink(link_path.as_unvirtual())
123 }
124
125 pub fn virtual_hard_link(
127 &self,
128 link_path: &crate::path::virtual_path::VirtualPath<Marker>,
129 ) -> std::io::Result<()> {
130 let root = self
131 .virtual_join("")
132 .map_err(|err| std::io::Error::new(std::io::ErrorKind::Other, err))?;
133
134 root.as_unvirtual()
135 .strict_hard_link(link_path.as_unvirtual())
136 }
137
138 #[inline]
144 pub fn read_dir(&self) -> std::io::Result<std::fs::ReadDir> {
145 self.root.read_dir()
146 }
147
148 #[inline]
152 pub fn remove_dir(&self) -> std::io::Result<()> {
153 self.root.remove_dir()
154 }
155
156 #[inline]
160 pub fn remove_dir_all(&self) -> std::io::Result<()> {
161 self.root.remove_dir_all()
162 }
163
164 #[inline]
176 pub fn try_new_create<P: AsRef<Path>>(root_path: P) -> Result<Self> {
177 let root = PathBoundary::try_new_create(root_path)?;
178 Ok(Self {
179 root,
180 #[cfg(feature = "tempfile")]
181 _temp_dir: None,
182 _marker: PhantomData,
183 })
184 }
185
186 #[inline]
191 pub fn virtual_join<P: AsRef<Path>>(&self, candidate_path: P) -> Result<VirtualPath<Marker>> {
192 let user_candidate = candidate_path.as_ref().to_path_buf();
194 let anchored = PathHistory::new(user_candidate).canonicalize_anchored(&self.root)?;
195
196 let validated = anchored.boundary_check(self.root.stated_path())?;
198
199 let jp = crate::path::strict_path::StrictPath::new(
201 std::sync::Arc::new(self.root.clone()),
202 validated,
203 );
204 Ok(jp.virtualize())
205 }
206
207 #[inline]
209 pub(crate) fn path(&self) -> &Path {
210 self.root.path()
211 }
212
213 #[inline]
218 pub fn interop_path(&self) -> &std::ffi::OsStr {
219 self.root.interop_path()
220 }
221
222 #[inline]
224 pub fn exists(&self) -> bool {
225 self.root.exists()
226 }
227
228 #[inline]
233 pub fn as_unvirtual(&self) -> &PathBoundary<Marker> {
234 &self.root
235 }
236
237 #[inline]
242 pub fn unvirtual(self) -> PathBoundary<Marker> {
243 self.root
244 }
245
246 #[cfg(feature = "dirs")]
259 pub fn try_new_os_config(app_name: &str) -> Result<Self> {
260 let root = crate::PathBoundary::try_new_os_config(app_name)?;
261 Ok(Self {
262 root,
263 #[cfg(feature = "tempfile")]
264 _temp_dir: None,
265 _marker: PhantomData,
266 })
267 }
268
269 #[cfg(feature = "dirs")]
271 pub fn try_new_os_data(app_name: &str) -> Result<Self> {
272 let root = crate::PathBoundary::try_new_os_data(app_name)?;
273 Ok(Self {
274 root,
275 #[cfg(feature = "tempfile")]
276 _temp_dir: None,
277 _marker: PhantomData,
278 })
279 }
280
281 #[cfg(feature = "dirs")]
283 pub fn try_new_os_cache(app_name: &str) -> Result<Self> {
284 let root = crate::PathBoundary::try_new_os_cache(app_name)?;
285 Ok(Self {
286 root,
287 #[cfg(feature = "tempfile")]
288 _temp_dir: None,
289 _marker: PhantomData,
290 })
291 }
292
293 #[cfg(feature = "dirs")]
295 pub fn try_new_os_config_local(app_name: &str) -> Result<Self> {
296 let root = crate::PathBoundary::try_new_os_config_local(app_name)?;
297 Ok(Self {
298 root,
299 #[cfg(feature = "tempfile")]
300 _temp_dir: None,
301 _marker: PhantomData,
302 })
303 }
304
305 #[cfg(feature = "dirs")]
307 pub fn try_new_os_data_local(app_name: &str) -> Result<Self> {
308 let root = crate::PathBoundary::try_new_os_data_local(app_name)?;
309 Ok(Self {
310 root,
311 #[cfg(feature = "tempfile")]
312 _temp_dir: None,
313 _marker: PhantomData,
314 })
315 }
316
317 #[cfg(feature = "dirs")]
319 pub fn try_new_os_home() -> Result<Self> {
320 let root = crate::PathBoundary::try_new_os_home()?;
321 Ok(Self {
322 root,
323 #[cfg(feature = "tempfile")]
324 _temp_dir: None,
325 _marker: PhantomData,
326 })
327 }
328
329 #[cfg(feature = "dirs")]
331 pub fn try_new_os_desktop() -> Result<Self> {
332 let root = crate::PathBoundary::try_new_os_desktop()?;
333 Ok(Self {
334 root,
335 #[cfg(feature = "tempfile")]
336 _temp_dir: None,
337 _marker: PhantomData,
338 })
339 }
340
341 #[cfg(feature = "dirs")]
343 pub fn try_new_os_documents() -> Result<Self> {
344 let root = crate::PathBoundary::try_new_os_documents()?;
345 Ok(Self {
346 root,
347 #[cfg(feature = "tempfile")]
348 _temp_dir: None,
349 _marker: PhantomData,
350 })
351 }
352
353 #[cfg(feature = "dirs")]
355 pub fn try_new_os_downloads() -> Result<Self> {
356 let root = crate::PathBoundary::try_new_os_downloads()?;
357 Ok(Self {
358 root,
359 #[cfg(feature = "tempfile")]
360 _temp_dir: None,
361 _marker: PhantomData,
362 })
363 }
364
365 #[cfg(feature = "dirs")]
367 pub fn try_new_os_pictures() -> Result<Self> {
368 let root = crate::PathBoundary::try_new_os_pictures()?;
369 Ok(Self {
370 root,
371 #[cfg(feature = "tempfile")]
372 _temp_dir: None,
373 _marker: PhantomData,
374 })
375 }
376
377 #[cfg(feature = "dirs")]
379 pub fn try_new_os_audio() -> Result<Self> {
380 let root = crate::PathBoundary::try_new_os_audio()?;
381 Ok(Self {
382 root,
383 #[cfg(feature = "tempfile")]
384 _temp_dir: None,
385 _marker: PhantomData,
386 })
387 }
388
389 #[cfg(feature = "dirs")]
391 pub fn try_new_os_videos() -> Result<Self> {
392 let root = crate::PathBoundary::try_new_os_videos()?;
393 Ok(Self {
394 root,
395 #[cfg(feature = "tempfile")]
396 _temp_dir: None,
397 _marker: PhantomData,
398 })
399 }
400
401 #[cfg(feature = "dirs")]
403 pub fn try_new_os_executables() -> Result<Self> {
404 let root = crate::PathBoundary::try_new_os_executables()?;
405 Ok(Self {
406 root,
407 #[cfg(feature = "tempfile")]
408 _temp_dir: None,
409 _marker: PhantomData,
410 })
411 }
412
413 #[cfg(feature = "dirs")]
415 pub fn try_new_os_runtime() -> Result<Self> {
416 let root = crate::PathBoundary::try_new_os_runtime()?;
417 Ok(Self {
418 root,
419 #[cfg(feature = "tempfile")]
420 _temp_dir: None,
421 _marker: PhantomData,
422 })
423 }
424
425 #[cfg(feature = "dirs")]
427 pub fn try_new_os_state(app_name: &str) -> Result<Self> {
428 let root = crate::PathBoundary::try_new_os_state(app_name)?;
429 Ok(Self {
430 root,
431 #[cfg(feature = "tempfile")]
432 _temp_dir: None,
433 _marker: PhantomData,
434 })
435 }
436
437 #[cfg(feature = "app-path")]
442 pub fn try_new_app_path(subdir: &str, env_override: Option<&str>) -> Result<Self> {
443 let root = crate::PathBoundary::try_new_app_path(subdir, env_override)?;
444 Ok(Self {
445 root,
446 #[cfg(feature = "tempfile")]
447 _temp_dir: None,
448 _marker: PhantomData,
449 })
450 }
451}
452
453impl<Marker> std::fmt::Display for VirtualRoot<Marker> {
454 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
455 write!(f, "{}", self.path().display())
456 }
457}
458
459impl<Marker> AsRef<Path> for VirtualRoot<Marker> {
460 fn as_ref(&self) -> &Path {
461 self.path()
462 }
463}
464
465impl<Marker> std::fmt::Debug for VirtualRoot<Marker> {
466 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
467 f.debug_struct("VirtualRoot")
468 .field("root", &self.path())
469 .field("marker", &std::any::type_name::<Marker>())
470 .finish()
471 }
472}
473
474impl<Marker> PartialEq for VirtualRoot<Marker> {
475 #[inline]
476 fn eq(&self, other: &Self) -> bool {
477 self.path() == other.path()
478 }
479}
480
481impl<Marker> Eq for VirtualRoot<Marker> {}
482
483impl<Marker> std::hash::Hash for VirtualRoot<Marker> {
484 #[inline]
485 fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
486 self.path().hash(state);
487 }
488}
489
490impl<Marker> PartialOrd for VirtualRoot<Marker> {
491 #[inline]
492 fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
493 Some(self.cmp(other))
494 }
495}
496
497impl<Marker> Ord for VirtualRoot<Marker> {
498 #[inline]
499 fn cmp(&self, other: &Self) -> std::cmp::Ordering {
500 self.path().cmp(other.path())
501 }
502}
503
504impl<Marker> PartialEq<crate::PathBoundary<Marker>> for VirtualRoot<Marker> {
505 #[inline]
506 fn eq(&self, other: &crate::PathBoundary<Marker>) -> bool {
507 self.path() == other.path()
508 }
509}
510
511impl<Marker> PartialEq<std::path::Path> for VirtualRoot<Marker> {
512 #[inline]
513 fn eq(&self, other: &std::path::Path) -> bool {
514 let other_str = other.to_string_lossy();
517
518 #[cfg(windows)]
519 let other_normalized = other_str.replace('\\', "/");
520 #[cfg(not(windows))]
521 let other_normalized = other_str.to_string();
522
523 let normalized_other = if other_normalized.starts_with('/') {
524 other_normalized
525 } else {
526 format!("/{}", other_normalized)
527 };
528
529 "/" == normalized_other
530 }
531}
532
533impl<Marker> PartialEq<std::path::PathBuf> for VirtualRoot<Marker> {
534 #[inline]
535 fn eq(&self, other: &std::path::PathBuf) -> bool {
536 self.eq(other.as_path())
537 }
538}
539
540impl<Marker> PartialEq<&std::path::Path> for VirtualRoot<Marker> {
541 #[inline]
542 fn eq(&self, other: &&std::path::Path) -> bool {
543 self.eq(*other)
544 }
545}
546
547impl<Marker> PartialOrd<std::path::Path> for VirtualRoot<Marker> {
548 #[inline]
549 fn partial_cmp(&self, other: &std::path::Path) -> Option<std::cmp::Ordering> {
550 let other_str = other.to_string_lossy();
552
553 if other_str.is_empty() {
555 return Some(std::cmp::Ordering::Greater);
556 }
557
558 #[cfg(windows)]
559 let other_normalized = other_str.replace('\\', "/");
560 #[cfg(not(windows))]
561 let other_normalized = other_str.to_string();
562
563 let normalized_other = if other_normalized.starts_with('/') {
564 other_normalized
565 } else {
566 format!("/{}", other_normalized)
567 };
568
569 Some("/".cmp(&normalized_other))
570 }
571}
572
573impl<Marker> PartialOrd<&std::path::Path> for VirtualRoot<Marker> {
574 #[inline]
575 fn partial_cmp(&self, other: &&std::path::Path) -> Option<std::cmp::Ordering> {
576 self.partial_cmp(*other)
577 }
578}
579
580impl<Marker> PartialOrd<std::path::PathBuf> for VirtualRoot<Marker> {
581 #[inline]
582 fn partial_cmp(&self, other: &std::path::PathBuf) -> Option<std::cmp::Ordering> {
583 self.partial_cmp(other.as_path())
584 }
585}
586
587impl<Marker: Default> std::str::FromStr for VirtualRoot<Marker> {
588 type Err = crate::StrictPathError;
589
590 #[inline]
605 fn from_str(path: &str) -> std::result::Result<Self, Self::Err> {
606 Self::try_new_create(path)
607 }
608}