1use std::{
2 io,
3 ops::Deref,
4 path::Path,
5 sync::{Arc, Mutex},
6};
7
8use thiserror::Error;
9use zip::{ZipArchive, result::ZipError};
10
11mod buffer;
12pub use buffer::*;
13
14mod list_directories;
15pub use list_directories::*;
16
17mod list_files;
18pub use list_files::*;
19
20mod list_files_recursive;
21pub use list_files_recursive::*;
22
23mod kind;
24use kind::*;
25
26#[derive(Error, Debug)]
27pub enum VfsError {
28 #[error("Zip error: {0}")]
29 ZipError(#[from] ZipError),
30 #[error("IO error: {0}")]
31 IoError(#[from] io::Error),
32 #[error("File not found: {0}")]
33 NotFound(Arc<str>),
34 #[error("Unknown error")]
35 Other,
36}
37
38#[derive(Debug, Clone)]
43pub struct Vfs {
44 kind: VfsKind,
45 subdirectory: Option<Box<str>>,
46}
47
48impl Vfs {
49 fn parse_subdirectory<S: Deref<Target = str>>(subdirectory: S) -> Option<Box<str>> {
50 let mut data = subdirectory.split('/').collect::<Vec<_>>();
51
52 data.retain(|it| !it.is_empty() && *it != ".");
53
54 let mut dirrectory = vec![];
55
56 for value in data {
57 if value == ".." {
58 dirrectory.pop();
59 } else {
60 dirrectory.push(value);
61 }
62 }
63
64 if dirrectory.is_empty() {
65 return None;
66 }
67
68 return Some(dirrectory.join("/").into_boxed_str());
69 }
70
71 fn from_kind<S: Deref<Target = str>>(
72 subdirectory: Option<S>,
73 kind: VfsKind,
74 ) -> Result<Self, VfsError> {
75 let subdirectory = subdirectory.map(Self::parse_subdirectory).unwrap_or(None);
76
77 if let Some(dir) = &subdirectory
78 && !kind.subdirectory_exists(dir)
79 {
80 return Err(VfsError::NotFound(dir.deref().into()));
81 }
82
83 return Ok(Self { kind, subdirectory });
84 }
85
86 pub fn working() -> Result<Self, VfsError> {
87 return Self::from_kind::<&str>(None, VfsKind::Working);
88 }
89
90 pub fn zip<B: Into<VfsBuffer>>(buf: B) -> Result<Self, VfsError> {
91 return Self::from_kind::<&str>(
92 None,
93 VfsKind::ZipArchive(Arc::new(Mutex::new(ZipArchive::new(buf.into())?))),
94 );
95 }
96
97 pub fn subdirectory(&self, subdirectory: &str) -> Result<Self, VfsError> {
98 let combined = if let Some(dir) = &self.subdirectory {
99 format!("{}/{}", dir, subdirectory)
100 } else {
101 subdirectory.into()
102 };
103
104 return Self::from_kind(Some(combined), self.kind.clone());
105 }
106
107 pub fn open(&self, path: &str) -> Result<VfsBuffer, VfsError> {
108 let path = self
109 .subdirectory
110 .as_ref()
111 .map(|it| format!("{}/{}", it, path))
112 .unwrap_or_else(|| path.into());
113
114 return self.kind.open(&path);
115 }
116
117 pub fn list_subdirectories(&self) -> Result<VfsListDirectories, VfsError> {
118 match &self.kind {
119 VfsKind::Working => {
120 let path = self
121 .subdirectory
122 .as_ref()
123 .map_or(Path::new("."), |it| Path::new(it.deref()));
124
125 let entries = path.read_dir()?;
126
127 return Ok(VfsListDirectories::Working(entries));
128 }
129
130 VfsKind::ZipArchive(zip) => {
131 return Ok(VfsListDirectories::ZipArchive {
132 index: 0,
133 prefix: self
134 .subdirectory
135 .clone()
136 .map_or("".into(), |it| format!("{}/", it).into()),
137 zip: zip.clone(),
138 seen: std::collections::HashSet::new(),
139 });
140 }
141 };
142 }
143
144 pub fn list_files(&self) -> Result<VfsListFiles, VfsError> {
145 match &self.kind {
146 VfsKind::Working => {
147 let path = self
148 .subdirectory
149 .as_ref()
150 .map_or(Path::new("."), |it| Path::new(it.deref()));
151
152 let entries = path.read_dir()?;
153
154 return Ok(VfsListFiles::Working(entries));
155 }
156
157 VfsKind::ZipArchive(zip) => {
158 return Ok(VfsListFiles::ZipArchive {
159 index: 0,
160 prefix: self
161 .subdirectory
162 .clone()
163 .map_or("".into(), |it| format!("{}/", it).into()),
164 zip: zip.clone(),
165 });
166 }
167 };
168 }
169
170 pub fn list_files_recursive(&self) -> Result<VfsListFilesRecursive, VfsError> {
171 return Ok(VfsListFilesRecursive {
172 stack: vec![(self.clone(), self.list_subdirectories()?)],
173 files: Some(self.list_files()?),
174 prefix: self.subdirectory.clone().unwrap_or_default(),
175 });
176 }
177}
178
179pub trait VfsListUtils: Iterator<Item = Result<String, VfsError>> + Sized {
180 fn with_extension<S: Deref<Target = str>>(
181 self,
182 ext: S,
183 ) -> impl Iterator<Item = Result<String, VfsError>> {
184 let ext = ext.to_string();
185 return self.filter(move |it| it.as_ref().map(|it| it.ends_with(&ext)).unwrap_or(true));
186 }
187}
188
189impl<T: Iterator<Item = Result<String, VfsError>> + Sized> VfsListUtils for T {}