Skip to main content

pawkit_fs/
lib.rs

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/// A virtual filesystem.
39/// It stores the source, and am optional subdirectory.
40/// The subdirectory is always delimitered by '/'. It also shouldn't begin or end with '/'.
41/// It'll convert to OS-speicifc delimiters before interacting with the real filesystem.
42#[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 {}