exocore_core/dir/
scoped.rs1use std::path::{Path, PathBuf};
2
3use super::{Directory, DynDirectory, Error, FileStat};
4
5pub struct ScopedDirectory {
6 inner: DynDirectory,
7 base_path: PathBuf,
8}
9
10impl ScopedDirectory {
11 pub fn new(dir: impl Into<DynDirectory>, base_path: PathBuf) -> Self {
12 ScopedDirectory {
13 inner: dir.into(),
14 base_path,
15 }
16 }
17
18 fn join_path(&self, path: &Path, expect_file: bool) -> Result<PathBuf, Error> {
19 let joined = self.base_path.join(path);
20 if expect_file && path.parent().is_none() {
21 return Err(Error::Path(anyhow!("expected a non-root path to a file")));
22 }
23
24 Ok(joined)
25 }
26}
27
28impl Directory for ScopedDirectory {
29 fn open_read(&self, path: &std::path::Path) -> Result<Box<dyn super::FileRead>, super::Error> {
30 let path = self.join_path(path, true)?;
31 self.inner.open_read(&path)
32 }
33
34 fn open_write(
35 &self,
36 path: &std::path::Path,
37 ) -> Result<Box<dyn super::FileWrite>, super::Error> {
38 let path = self.join_path(path, true)?;
39 self.inner.open_write(&path)
40 }
41
42 fn open_create(
43 &self,
44 path: &std::path::Path,
45 ) -> Result<Box<dyn super::FileWrite>, super::Error> {
46 let path = self.join_path(path, true)?;
47 self.inner.open_create(&path)
48 }
49
50 fn list(
51 &self,
52 prefix: Option<&std::path::Path>,
53 ) -> Result<Vec<Box<dyn super::FileStat>>, super::Error> {
54 let path = prefix.map(|p| self.join_path(p, false)).transpose()?;
55 self.inner.list(path.as_deref())
56 }
57
58 fn stat(&self, path: &std::path::Path) -> Result<Box<dyn super::FileStat>, super::Error> {
59 let resolved_path = self.join_path(path, true)?;
60 let stat = self.inner.stat(&resolved_path)?;
61
62 Ok(Box::new(ScopedFileStat {
63 path: path.to_path_buf(),
64 inner: stat,
65 }))
66 }
67
68 fn exists(&self, path: &std::path::Path) -> bool {
69 let Ok(path) = self.join_path(path, true) else {
70 return false;
71 };
72
73 self.inner.exists(&path)
74 }
75
76 fn delete(&self, path: &std::path::Path) -> Result<(), super::Error> {
77 let path = self.join_path(path, true)?;
78 self.inner.delete(&path)
79 }
80
81 fn clone(&self) -> DynDirectory {
82 ScopedDirectory {
83 inner: self.inner.clone(),
84 base_path: self.base_path.clone(),
85 }
86 .into()
87 }
88
89 fn as_os_path(&self) -> Result<std::path::PathBuf, super::Error> {
90 let path = self.inner.as_os_path()?;
91 Ok(path.join(&self.base_path))
92 }
93}
94
95pub struct ScopedFileStat {
96 path: PathBuf,
97 inner: Box<dyn super::FileStat>,
98}
99
100impl FileStat for ScopedFileStat {
101 fn path(&self) -> &Path {
102 self.path.as_path()
103 }
104
105 fn size(&self) -> u64 {
106 self.inner.size()
107 }
108}
109
110#[cfg(test)]
111mod tests {
112 use tempfile::tempdir;
113
114 use super::*;
115 use crate::dir::{os::OsDirectory, ram::RamDirectory};
116
117 #[test]
118 fn test_write_read_file() {
119 let ram = RamDirectory::new();
120 let scoped = ScopedDirectory::new(ram, PathBuf::from("sub"));
121 super::super::tests::test_write_read_file(scoped);
122
123 let ram = RamDirectory::new();
124 let scoped = ScopedDirectory::new(ram, PathBuf::from("sub/sub"));
125 super::super::tests::test_write_read_file(scoped);
126
127 let ram = RamDirectory::new();
128 let scoped = ScopedDirectory::new(ram, PathBuf::from(""));
129 super::super::tests::test_write_read_file(scoped);
130 }
131
132 #[test]
133 fn test_list() {
134 let ram = RamDirectory::new();
135 let scoped = ScopedDirectory::new(ram, PathBuf::from("sub"));
136 super::super::tests::test_list(scoped);
137
138 let ram = RamDirectory::new();
139 let scoped = ScopedDirectory::new(ram, PathBuf::from("sub/sub"));
140 super::super::tests::test_list(scoped);
141
142 let ram = RamDirectory::new();
143 let scoped = ScopedDirectory::new(ram, PathBuf::from(""));
144 super::super::tests::test_list(scoped);
145 }
146
147 #[test]
148 fn test_delete() {
149 let ram = RamDirectory::new();
150 let scoped = ScopedDirectory::new(ram, PathBuf::from("sub"));
151 super::super::tests::test_delete(scoped);
152
153 let ram = RamDirectory::new();
154 let scoped = ScopedDirectory::new(ram, PathBuf::from("sub/sub"));
155 super::super::tests::test_delete(scoped);
156
157 let ram = RamDirectory::new();
158 let scoped = ScopedDirectory::new(ram, PathBuf::from(""));
159 super::super::tests::test_delete(scoped);
160 }
161
162 #[test]
163 fn test_as_os_path() {
164 let dir = tempdir().unwrap();
165 let scoped = ScopedDirectory::new(
166 OsDirectory::new(dir.path().to_path_buf()),
167 PathBuf::from("sub"),
168 );
169
170 let os_path = scoped.as_os_path().unwrap();
171 assert_eq!(dir.path().join("sub"), os_path.as_path());
172 }
173}