luminol_filesystem/
lib.rs1pub mod archiver;
19pub mod egui_bytes_loader;
20pub mod erased;
21pub mod list;
22pub mod path_cache;
23pub mod project;
24
25mod trie;
26pub use trie::*;
27
28#[cfg(not(target_arch = "wasm32"))]
29pub mod native;
30#[cfg(target_arch = "wasm32")]
31pub mod web;
32
33#[cfg(not(target_arch = "wasm32"))]
36pub use native as host;
37#[cfg(target_arch = "wasm32")]
38pub use web as host;
39
40#[derive(thiserror::Error, Debug)]
41pub enum Error {
42 #[error("File or directory does not exist")]
43 NotExist,
44 #[error("Io error {0}")]
45 IoError(#[from] std::io::Error),
46 #[error("UTF-8 Error {0}")]
47 Utf8Error(#[from] std::string::FromUtf8Error),
48 #[error("Path is not valid UTF-8")]
49 PathUtf8Error,
50 #[error("Project not loaded")]
51 NotLoaded,
52 #[error("Operation not supported by this filesystem")]
53 NotSupported,
54 #[error("Archive header is incorrect")]
55 InvalidHeader,
56 #[error("Invalid archive version: {0} (supported versions are 1, 2 and 3)")]
57 InvalidArchiveVersion(u8),
58 #[error("No filesystems are loaded to perform this operation")]
59 NoFilesystems,
60 #[error("Unable to detect the project's RPG Maker version (perhaps you did not open an RPG Maker project?")]
61 UnableToDetectRMVer,
62 #[error("Cancelled loading project")]
63 CancelledLoading,
64 #[error("Your browser does not support File System Access API")]
65 Wasm32FilesystemNotSupported,
66 #[error("Invalid project folder")]
67 InvalidProjectFolder,
68}
69
70pub use color_eyre::Result;
71
72pub trait StdIoErrorExt {
73 fn wrap_io_err_with<C>(self, c: impl FnOnce() -> C) -> Self
75 where
76 Self: Sized,
77 C: std::fmt::Display + Send + Sync + 'static;
78
79 fn wrap_io_err<C>(self, c: C) -> Self
81 where
82 Self: Sized,
83 C: std::fmt::Display + Send + Sync + 'static,
84 {
85 self.wrap_io_err_with(|| c)
86 }
87
88 fn with_io_context<C>(self, c: impl FnOnce() -> C) -> Self
90 where
91 Self: Sized,
92 C: std::fmt::Display + Send + Sync + 'static,
93 {
94 self.wrap_io_err_with(c)
95 }
96
97 fn io_context<C>(self, c: C) -> Self
99 where
100 Self: Sized,
101 C: std::fmt::Display + Send + Sync + 'static,
102 {
103 self.wrap_io_err(c)
104 }
105}
106
107impl<T> StdIoErrorExt for std::io::Result<T> {
108 fn wrap_io_err_with<C>(self, c: impl FnOnce() -> C) -> Self
109 where
110 C: std::fmt::Display + Send + Sync + 'static,
111 {
112 self.map_err(|e| std::io::Error::new(e.kind(), color_eyre::eyre::eyre!(e).wrap_err(c())))
113 }
114}
115
116#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug)]
117pub struct Metadata {
118 pub is_file: bool,
119 pub size: u64,
120}
121
122#[derive(Clone, PartialEq, Eq, Hash, Debug)]
123pub struct DirEntry {
124 pub path: camino::Utf8PathBuf,
125 pub metadata: Metadata,
126}
127
128impl DirEntry {
129 pub fn new(path: camino::Utf8PathBuf, metadata: Metadata) -> Self {
130 Self { path, metadata }
131 }
132
133 pub fn path(&self) -> &camino::Utf8Path {
134 &self.path
135 }
136
137 pub fn metadata(&self) -> Metadata {
138 self.metadata
139 }
140
141 pub fn file_name(&self) -> &str {
142 self.path
143 .file_name()
144 .expect("path created through DirEntry must have a filename")
145 }
146
147 pub fn into_path(self) -> camino::Utf8PathBuf {
148 self.path
149 }
150}
151
152bitflags::bitflags! {
153 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
154 pub struct OpenFlags: u8 {
155 const Read = 0b00000001;
156 const Write = 0b00000010;
157 const Truncate = 0b00000100;
158 const Create = 0b00001000;
159 }
160}
161
162pub trait File: std::io::Read + std::io::Write + std::io::Seek + Send + Sync {
163 fn metadata(&self) -> std::io::Result<Metadata>;
164
165 fn set_len(&self, new_size: u64) -> std::io::Result<()>;
169
170 fn as_file(&mut self) -> &mut Self
172 where
173 Self: Sized,
174 {
175 self
176 }
177}
178
179impl<T> File for &mut T
180where
181 T: File + ?Sized,
182{
183 fn metadata(&self) -> std::io::Result<Metadata> {
184 (**self).metadata()
185 }
186
187 fn set_len(&self, new_size: u64) -> std::io::Result<()> {
188 (**self).set_len(new_size)
189 }
190}
191
192pub trait FileSystem: Send + Sync {
193 type File: File;
194
195 fn open_file(&self, path: impl AsRef<camino::Utf8Path>, flags: OpenFlags)
196 -> Result<Self::File>;
197
198 fn create_file(&self, path: impl AsRef<camino::Utf8Path>) -> Result<Self::File> {
199 self.open_file(path, OpenFlags::Create | OpenFlags::Write)
200 }
201
202 fn metadata(&self, path: impl AsRef<camino::Utf8Path>) -> Result<Metadata>;
203
204 fn rename(
205 &self,
206 from: impl AsRef<camino::Utf8Path>,
207 to: impl AsRef<camino::Utf8Path>,
208 ) -> Result<()>;
209
210 fn exists(&self, path: impl AsRef<camino::Utf8Path>) -> Result<bool>;
211
212 fn create_dir(&self, path: impl AsRef<camino::Utf8Path>) -> Result<()>;
213
214 fn remove_dir(&self, path: impl AsRef<camino::Utf8Path>) -> Result<()>;
215
216 fn remove_file(&self, path: impl AsRef<camino::Utf8Path>) -> Result<()>;
217
218 fn remove(&self, path: impl AsRef<camino::Utf8Path>) -> Result<()> {
219 let path = path.as_ref();
220 let metadata = self.metadata(path)?;
221 if metadata.is_file {
222 self.remove_file(path)
223 } else {
224 self.remove_dir(path)
225 }
226 }
227
228 fn read_dir(&self, path: impl AsRef<camino::Utf8Path>) -> Result<Vec<DirEntry>>;
229
230 fn read(&self, path: impl AsRef<camino::Utf8Path>) -> Result<Vec<u8>> {
233 use std::io::Read;
234
235 let path = path.as_ref();
236
237 let mut buf = Vec::with_capacity(self.metadata(path)?.size as usize);
238 let mut file = self.open_file(path, OpenFlags::Read)?;
239 file.read_to_end(&mut buf)?;
240
241 Ok(buf)
242 }
243
244 fn read_to_string(&self, path: impl AsRef<camino::Utf8Path>) -> Result<String> {
245 let buf = self.read(path)?;
246 String::from_utf8(buf).map_err(Into::into)
247 }
248
249 fn write(&self, path: impl AsRef<camino::Utf8Path>, data: impl AsRef<[u8]>) -> Result<()> {
253 use std::io::Write;
254
255 let mut file = self.open_file(
256 path,
257 OpenFlags::Write | OpenFlags::Truncate | OpenFlags::Create,
258 )?;
259 file.write_all(data.as_ref())?;
260 file.flush()?;
261
262 Ok(())
263 }
264}
265
266pub trait ReadDir {
267 fn read_dir(&self, path: impl AsRef<camino::Utf8Path>) -> Result<Vec<DirEntry>>;
268}
269
270impl<T> ReadDir for T
271where
272 T: FileSystem,
273{
274 fn read_dir(&self, path: impl AsRef<camino::Utf8Path>) -> Result<Vec<DirEntry>> {
275 self.read_dir(path)
276 }
277}