1use anyhow::anyhow;
2use futures::future::BoxFuture;
3use tokio::io::{AsyncRead, AsyncSeek, AsyncWrite};
4
5use std::convert::TryInto;
6use std::io::{self, Error as IoError, ErrorKind as IoErrorKind, SeekFrom};
7use std::path::Path;
8use std::path::PathBuf;
9use std::pin::Pin;
10use std::sync::Arc;
11use std::task::{Context, Poll};
12
13use crate::mem_fs::FileSystem as MemFileSystem;
14use crate::{
15 FileOpener, FileSystem, FsError, Metadata, OpenOptions, OpenOptionsConfig, ReadDir, VirtualFile,
16};
17use indexmap::IndexMap;
18use webc::v1::{FsEntry, FsEntryType, OwnedFsEntryFile};
19
20#[derive(Debug)]
22pub struct StaticFileSystem {
23 pub package: String,
24 pub volumes: Arc<IndexMap<String, webc::v1::Volume<'static>>>,
25 pub memory: Arc<MemFileSystem>,
26}
27
28impl StaticFileSystem {
29 pub fn init(bytes: &'static [u8], package: &str) -> Option<Self> {
30 let volumes = Arc::new(webc::v1::WebC::parse_volumes_from_fileblock(bytes).ok()?);
31 let fs = Self {
32 package: package.to_string(),
33 volumes: volumes.clone(),
34 memory: Arc::new(MemFileSystem::default()),
35 };
36 let volume_names = fs.volumes.keys().cloned().collect::<Vec<_>>();
37 for volume_name in volume_names {
38 let directories = volumes.get(&volume_name).unwrap().list_directories();
39 for directory in directories {
40 let _ = fs.create_dir(Path::new(&directory));
41 }
42 }
43 Some(fs)
44 }
45}
46
47impl FileOpener for StaticFileSystem {
49 fn open(
50 &self,
51 path: &Path,
52 _conf: &OpenOptionsConfig,
53 ) -> Result<Box<dyn VirtualFile + Send + Sync>, FsError> {
54 match get_volume_name_opt(path) {
55 Some(volume) => {
56 let file = (*self.volumes)
57 .get(&volume)
58 .ok_or(FsError::EntryNotFound)?
59 .get_file_entry(path.to_string_lossy().as_ref())
60 .map_err(|_e| FsError::EntryNotFound)?;
61
62 Ok(Box::new(WebCFile {
63 package: self.package.clone(),
64 volume,
65 volumes: self.volumes.clone(),
66 path: path.to_path_buf(),
67 entry: file,
68 cursor: 0,
69 }))
70 }
71 None => {
72 for (volume, v) in self.volumes.iter() {
73 let entry = match v.get_file_entry(path.to_string_lossy().as_ref()) {
74 Ok(s) => s,
75 Err(_) => continue, };
77
78 return Ok(Box::new(WebCFile {
79 package: self.package.clone(),
80 volume: volume.clone(),
81 volumes: self.volumes.clone(),
82 path: path.to_path_buf(),
83 entry,
84 cursor: 0,
85 }));
86 }
87 self.memory.new_open_options().open(path)
88 }
89 }
90 }
91}
92
93#[derive(Debug)]
94pub struct WebCFile {
95 pub volumes: Arc<IndexMap<String, webc::v1::Volume<'static>>>,
96 pub package: String,
97 pub volume: String,
98 pub path: PathBuf,
99 pub entry: OwnedFsEntryFile,
100 pub cursor: u64,
101}
102
103#[async_trait::async_trait]
104impl VirtualFile for WebCFile {
105 fn last_accessed(&self) -> u64 {
106 0
107 }
108 fn last_modified(&self) -> u64 {
109 0
110 }
111 fn created_time(&self) -> u64 {
112 0
113 }
114 fn size(&self) -> u64 {
115 self.entry.get_len()
116 }
117 fn set_len(&mut self, _new_size: u64) -> crate::Result<()> {
118 Ok(())
119 }
120 fn unlink(&mut self) -> Result<(), FsError> {
121 Ok(())
122 }
123 fn poll_read_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
124 let remaining = self.entry.get_len() - self.cursor;
125 Poll::Ready(Ok(remaining as usize))
126 }
127 fn poll_write_ready(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<usize>> {
128 Poll::Ready(Ok(0))
129 }
130}
131
132impl AsyncRead for WebCFile {
133 fn poll_read(
134 self: Pin<&mut Self>,
135 _cx: &mut Context<'_>,
136 buf: &mut tokio::io::ReadBuf<'_>,
137 ) -> Poll<io::Result<()>> {
138 let bytes = self
139 .volumes
140 .get(&self.volume)
141 .ok_or_else(|| {
142 IoError::new(
143 IoErrorKind::NotFound,
144 anyhow!("Unknown volume {:?}", self.volume),
145 )
146 })?
147 .get_file_bytes(&self.entry)
148 .map_err(|e| IoError::new(IoErrorKind::NotFound, e))?;
149
150 let cursor: usize = self.cursor.try_into().unwrap_or(u32::MAX as usize);
151 let _start = cursor.min(bytes.len());
152 let bytes = &bytes[cursor..];
153
154 if bytes.len() > buf.remaining() {
155 let remaining = buf.remaining();
156 buf.put_slice(&bytes[..remaining]);
157 } else {
158 buf.put_slice(bytes);
159 }
160 Poll::Ready(Ok(()))
161 }
162}
163
164impl AsyncWrite for WebCFile {
167 fn poll_write(
168 self: Pin<&mut Self>,
169 _cx: &mut Context<'_>,
170 buf: &[u8],
171 ) -> Poll<io::Result<usize>> {
172 Poll::Ready(Ok(buf.len()))
173 }
174 fn poll_flush(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<()>> {
175 Poll::Ready(Ok(()))
176 }
177 fn poll_shutdown(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<Result<(), io::Error>> {
178 Poll::Ready(Ok(()))
179 }
180}
181
182impl AsyncSeek for WebCFile {
183 fn start_seek(mut self: Pin<&mut Self>, pos: io::SeekFrom) -> io::Result<()> {
184 let self_size = self.size();
185 match pos {
186 SeekFrom::Start(s) => {
187 self.cursor = s.min(self_size);
188 }
189 SeekFrom::End(e) => {
190 let self_size_i64 = self_size.try_into().unwrap_or(i64::MAX);
191 self.cursor = ((self_size_i64).saturating_add(e))
192 .min(self_size_i64)
193 .try_into()
194 .unwrap_or(i64::MAX as u64);
195 }
196 SeekFrom::Current(c) => {
197 self.cursor = (self
198 .cursor
199 .saturating_add(c.try_into().unwrap_or(i64::MAX as u64)))
200 .min(self_size);
201 }
202 }
203 Ok(())
204 }
205 fn poll_complete(self: Pin<&mut Self>, _cx: &mut Context<'_>) -> Poll<io::Result<u64>> {
206 Poll::Ready(Ok(self.cursor))
207 }
208}
209
210fn get_volume_name_opt<P: AsRef<Path>>(path: P) -> Option<String> {
211 use std::path::Component::Normal;
212 if let Some(Normal(n)) = path.as_ref().components().next() {
213 if let Some(s) = n.to_str() {
214 if s.ends_with(':') {
215 return Some(s.replace(':', ""));
216 }
217 }
218 }
219 None
220}
221
222fn transform_into_read_dir(path: &Path, fs_entries: &[FsEntry<'_>]) -> crate::ReadDir {
223 let entries = fs_entries
224 .iter()
225 .map(|e| crate::DirEntry {
226 path: path.join(&*e.text),
227 metadata: Ok(crate::Metadata {
228 ft: translate_file_type(e.fs_type),
229 accessed: 0,
230 created: 0,
231 modified: 0,
232 len: e.get_len(),
233 }),
234 })
235 .collect();
236
237 crate::ReadDir::new(entries)
238}
239
240impl FileSystem for StaticFileSystem {
241 fn readlink(&self, path: &Path) -> crate::Result<PathBuf> {
242 let path = normalizes_path(path);
243 if self
244 .volumes
245 .values()
246 .find_map(|v| v.get_file_entry(&path).ok())
247 .is_some()
248 {
249 Err(FsError::InvalidInput)
250 } else {
251 self.memory.readlink(Path::new(&path))
252 }
253 }
254
255 fn read_dir(&self, path: &Path) -> Result<ReadDir, FsError> {
256 let path = normalizes_path(path);
257 for volume in self.volumes.values() {
258 let read_dir_result = volume
259 .read_dir(&path)
260 .map(|o| transform_into_read_dir(Path::new(&path), o.as_ref()))
261 .map_err(|_| FsError::EntryNotFound);
262
263 match read_dir_result {
264 Ok(o) => {
265 return Ok(o);
266 }
267 Err(_) => {
268 continue;
269 }
270 }
271 }
272
273 self.memory.read_dir(Path::new(&path))
274 }
275 fn create_dir(&self, path: &Path) -> Result<(), FsError> {
276 let path = normalizes_path(path);
277 self.memory.create_dir(Path::new(&path))
278 }
279 fn remove_dir(&self, path: &Path) -> Result<(), FsError> {
280 let path = normalizes_path(path);
281 let result = self.memory.remove_dir(Path::new(&path));
282 if self
283 .volumes
284 .values()
285 .find_map(|v| v.get_file_entry(&path).ok())
286 .is_some()
287 {
288 Ok(())
289 } else {
290 result
291 }
292 }
293 fn rename<'a>(&'a self, from: &'a Path, to: &'a Path) -> BoxFuture<'a, Result<(), FsError>> {
294 Box::pin(async {
295 let from = normalizes_path(from);
296 let to = normalizes_path(to);
297 let result = self.memory.rename(Path::new(&from), Path::new(&to)).await;
298 if self
299 .volumes
300 .values()
301 .find_map(|v| v.get_file_entry(&from).ok())
302 .is_some()
303 {
304 Ok(())
305 } else {
306 result
307 }
308 })
309 }
310 fn metadata(&self, path: &Path) -> Result<Metadata, FsError> {
311 let path = normalizes_path(path);
312 if let Some(fs_entry) = self
313 .volumes
314 .values()
315 .find_map(|v| v.get_file_entry(&path).ok())
316 {
317 Ok(Metadata {
318 ft: translate_file_type(FsEntryType::File),
319 accessed: 0,
320 created: 0,
321 modified: 0,
322 len: fs_entry.get_len(),
323 })
324 } else if let Some(_fs) = self.volumes.values().find_map(|v| v.read_dir(&path).ok()) {
325 Ok(Metadata {
326 ft: translate_file_type(FsEntryType::Dir),
327 accessed: 0,
328 created: 0,
329 modified: 0,
330 len: 0,
331 })
332 } else {
333 self.memory.metadata(Path::new(&path))
334 }
335 }
336 fn remove_file(&self, path: &Path) -> Result<(), FsError> {
337 let path = normalizes_path(path);
338 let result = self.memory.remove_file(Path::new(&path));
339 if self
340 .volumes
341 .values()
342 .find_map(|v| v.get_file_entry(&path).ok())
343 .is_some()
344 {
345 Ok(())
346 } else {
347 result
348 }
349 }
350 fn new_open_options(&self) -> OpenOptions<'_> {
351 OpenOptions::new(self)
352 }
353 fn symlink_metadata(&self, path: &Path) -> Result<Metadata, FsError> {
354 let path = normalizes_path(path);
355 if let Some(fs_entry) = self
356 .volumes
357 .values()
358 .find_map(|v| v.get_file_entry(&path).ok())
359 {
360 Ok(Metadata {
361 ft: translate_file_type(FsEntryType::File),
362 accessed: 0,
363 created: 0,
364 modified: 0,
365 len: fs_entry.get_len(),
366 })
367 } else if self
368 .volumes
369 .values()
370 .find_map(|v| v.read_dir(&path).ok())
371 .is_some()
372 {
373 Ok(Metadata {
374 ft: translate_file_type(FsEntryType::Dir),
375 accessed: 0,
376 created: 0,
377 modified: 0,
378 len: 0,
379 })
380 } else {
381 self.memory.symlink_metadata(Path::new(&path))
382 }
383 }
384
385 fn mount(
386 &self,
387 _name: String,
388 _path: &Path,
389 _fs: Box<dyn FileSystem + Send + Sync>,
390 ) -> Result<(), FsError> {
391 Err(FsError::Unsupported)
392 }
393}
394
395fn normalizes_path(path: &Path) -> String {
396 let path = format!("{}", path.display());
397 if !path.starts_with('/') {
398 format!("/{path}")
399 } else {
400 path
401 }
402}
403
404fn translate_file_type(f: FsEntryType) -> crate::FileType {
405 crate::FileType {
406 dir: f == FsEntryType::Dir,
407 file: f == FsEntryType::File,
408 symlink: false,
409 char_device: false,
410 block_device: false,
411 socket: false,
412 fifo: false,
413 }
414}