exocore_core/dir/
scoped.rs

1use 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}