luminol_filesystem/
list.rs

1// Copyright (C) 2024 Melody Madeline Lyons
2//
3// This file is part of Luminol.
4//
5// Luminol is free software: you can redistribute it and/or modify
6// it under the terms of the GNU General Public License as published by
7// the Free Software Foundation, either version 3 of the License, or
8// (at your option) any later version.
9//
10// Luminol is distributed in the hope that it will be useful,
11// but WITHOUT ANY WARRANTY; without even the implied warranty of
12// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13// GNU General Public License for more details.
14//
15// You should have received a copy of the GNU General Public License
16// along with Luminol.  If not, see <http://www.gnu.org/licenses/>.
17
18use crate::{erased::ErasedFilesystem, DirEntry, Error, File, Metadata, OpenFlags, Result};
19use color_eyre::eyre::WrapErr;
20use itertools::Itertools;
21
22#[derive(Default)]
23pub struct FileSystem {
24    filesystems: Vec<Box<dyn ErasedFilesystem>>,
25}
26
27impl FileSystem {
28    pub fn new() -> Self {
29        Self::default()
30    }
31
32    pub fn push(&mut self, fs: impl crate::FileSystem + 'static) {
33        self.filesystems.push(Box::new(fs))
34    }
35}
36
37impl crate::FileSystem for FileSystem {
38    type File = Box<dyn File>;
39
40    fn open_file(
41        &self,
42        path: impl AsRef<camino::Utf8Path>,
43        flags: OpenFlags,
44    ) -> Result<Self::File> {
45        let path = path.as_ref();
46        let c = format!("While opening file {path:?} in a list filesystem");
47        let parent = path.parent().unwrap_or(path);
48        for fs in self.filesystems.iter() {
49            if fs.exists(path).wrap_err_with(|| c.clone())?
50                || (flags.contains(OpenFlags::Create)
51                    && fs.exists(parent).wrap_err_with(|| c.clone())?)
52            {
53                return fs.open_file(path, flags).wrap_err_with(|| c.clone());
54            }
55        }
56        Err(Error::NotExist).wrap_err_with(|| c.clone())
57    }
58
59    fn metadata(&self, path: impl AsRef<camino::Utf8Path>) -> Result<Metadata> {
60        let path = path.as_ref();
61        let c = format!("While getting metadata for {path:?} in a list filesystem");
62        for fs in self.filesystems.iter() {
63            if fs.exists(path).wrap_err_with(|| c.clone())? {
64                return fs.metadata(path).wrap_err_with(|| c.clone());
65            }
66        }
67        Err(Error::NotExist).wrap_err_with(|| c.clone())
68    }
69
70    fn rename(
71        &self,
72        from: impl AsRef<camino::Utf8Path>,
73        to: impl AsRef<camino::Utf8Path>,
74    ) -> Result<()> {
75        let from = from.as_ref();
76        let to = to.as_ref();
77        let c = format!("While renaming {from:?} to {to:?} in a list filesystem");
78        for fs in self.filesystems.iter() {
79            if fs.exists(from).wrap_err_with(|| c.clone())? {
80                return fs.rename(from, to.as_ref()).wrap_err_with(|| c.clone());
81            }
82        }
83        Err(Error::NotExist).wrap_err_with(|| c.clone())
84    }
85
86    fn exists(&self, path: impl AsRef<camino::Utf8Path>) -> Result<bool> {
87        let path = path.as_ref();
88        let c = format!("While checking if {path:?} exists in a list filesystem");
89        for fs in self.filesystems.iter() {
90            if fs.exists(path).wrap_err_with(|| c.clone())? {
91                return Ok(true);
92            }
93        }
94        Ok(false)
95    }
96
97    fn create_dir(&self, path: impl AsRef<camino::Utf8Path>) -> Result<()> {
98        let path = path.as_ref();
99        let c = format!("While creating a directory at {path:?} in a list filesystem");
100        let fs = self
101            .filesystems
102            .first()
103            .ok_or(Error::NoFilesystems)
104            .wrap_err_with(|| c.clone())?;
105        fs.create_dir(path).wrap_err_with(|| c.clone())
106    }
107
108    fn remove_dir(&self, path: impl AsRef<camino::Utf8Path>) -> Result<()> {
109        let path = path.as_ref();
110        let c = format!("While removing a directory at {path:?} in a list filesystem");
111        let fs = self
112            .filesystems
113            .first()
114            .ok_or(Error::NoFilesystems)
115            .wrap_err_with(|| c.clone())?;
116        fs.remove_dir(path).wrap_err_with(|| c.clone())
117    }
118
119    fn remove_file(&self, path: impl AsRef<camino::Utf8Path>) -> Result<()> {
120        let path = path.as_ref();
121        let c = format!("While removing a file at {path:?} in a list filesystem");
122        let fs = self
123            .filesystems
124            .first()
125            .ok_or(Error::NoFilesystems)
126            .wrap_err_with(|| c.clone())?;
127        fs.remove_file(path).wrap_err_with(|| c.clone())
128    }
129
130    fn remove(&self, path: impl AsRef<camino::Utf8Path>) -> Result<()> {
131        let path = path.as_ref();
132        let c = format!("While removing {path:?} in a list filesystem");
133        let fs = self
134            .filesystems
135            .first()
136            .ok_or(Error::NoFilesystems)
137            .wrap_err_with(|| c.clone())?;
138        fs.remove(path).wrap_err_with(|| c.clone())
139    }
140
141    fn read_dir(&self, path: impl AsRef<camino::Utf8Path>) -> Result<Vec<DirEntry>> {
142        let path = path.as_ref();
143        let c =
144            format!("While reading the contents of the directory {path:?} in a list filesystem");
145
146        let mut entries = Vec::new();
147        for fs in self.filesystems.iter() {
148            if fs.exists(path).wrap_err_with(|| c.clone())? {
149                entries.extend(fs.read_dir(path).wrap_err_with(|| c.clone())?)
150            }
151        }
152        // FIXME: remove duplicates in a more efficient manner
153        let entries = entries.into_iter().unique().collect_vec();
154
155        Ok(entries)
156    }
157
158    fn read(&self, path: impl AsRef<camino::Utf8Path>) -> Result<Vec<u8>> {
159        let path = path.as_ref();
160        let c = format!("While reading from the file {path:?} in a list filesystem");
161        for fs in self.filesystems.iter() {
162            if fs.exists(path).wrap_err_with(|| c.clone())? {
163                return fs.read(path).wrap_err_with(|| c.clone());
164            }
165        }
166        Err(Error::NotExist).wrap_err_with(|| c.clone())
167    }
168
169    fn read_to_string(&self, path: impl AsRef<camino::Utf8Path>) -> Result<String> {
170        let path = path.as_ref();
171        let c = format!("While reading from the file {path:?} in a list filesystem");
172        for fs in self.filesystems.iter() {
173            if fs.exists(path).wrap_err_with(|| c.clone())? {
174                return fs.read_to_string(path).wrap_err_with(|| c.clone());
175            }
176        }
177        Err(Error::NotExist).wrap_err_with(|| c.clone())
178    }
179
180    fn write(&self, path: impl AsRef<camino::Utf8Path>, data: impl AsRef<[u8]>) -> Result<()> {
181        let path = path.as_ref();
182        let c = format!("While writing to the file {path:?} in a list filesystem");
183        for fs in self.filesystems.iter() {
184            if fs.exists(path).wrap_err_with(|| c.clone())? {
185                return fs.write(path, data.as_ref()).wrap_err_with(|| c.clone());
186            }
187        }
188        Err(Error::NotExist).wrap_err_with(|| c.clone())
189    }
190}