libmathcat/
shim_filesystem.rs1#![allow(clippy::needless_return)]
2use std::path::{Path, PathBuf};
9use crate::errors::*;
10
11
12#[cfg(feature = "include-zip")]
17pub static ZIPPED_RULE_FILES: &[u8] = include_bytes!(concat!(env!("OUT_DIR"),"/rules.zip"));
18
19
20cfg_if! {
21 if #[cfg(any(target_family = "wasm", feature = "include-zip"))] {
22 use std::cell::RefCell;
30 use std::rc::Rc;
31 use std::io::Cursor;
32 use std::io::Read;
33 use std::collections::{HashSet, HashMap};
34
35 #[derive(Debug)]
36 struct FilesEntry {
37 data: Rc<Option<Vec<u8>>>,
38 index: usize,
39 }
40 thread_local! {
41 static DIRECTORIES: RefCell<HashSet<String>> = RefCell::new(HashSet::with_capacity(63));
43 static FILES: RefCell<HashMap< String, FilesEntry >> = RefCell::new(HashMap::with_capacity(1023));
44 }
45
46 fn read_zip_file(containing_dir: &Path, zip_file: Option<Vec<u8>>) -> Result<()> {
47 let zip_file = Rc::new(zip_file);
49 FILES.with(|files| {
50 let mut files = files.borrow_mut();
51 DIRECTORIES.with(|dirs| {
52 let mut dirs = dirs.borrow_mut();
53 let mut archive = match zip_file.as_ref() {
54 None => {
55 let buf_reader = Cursor::new(ZIPPED_RULE_FILES);
56 zip::ZipArchive::new(buf_reader).unwrap()
57 },
58 Some(zip_file) => {
59 let buf_reader = Cursor::new((zip_file).as_ref());
60 match zip::ZipArchive::new(buf_reader) {
61 Err(e) => bail!("read_zip_file: failed to create ZipArchive in dir {}: {}", containing_dir.display(), e),
62 Ok(archive) => archive,
63 }
64 }
65 };
66 for i in 0..archive.len() {
67 let file = archive.by_index(i).unwrap();
68 let path = match file.enclosed_name() {
70 Some(path) => containing_dir.to_path_buf().join(path),
71 None => {
72 bail!("Entry {} has a suspicious path (outside of archive)", file.name());
73 }
74 };
75 for parent in path.ancestors().skip(1) {
79 if parent == containing_dir {
80 break;
81 }
82 dirs.insert(parent.to_str().unwrap_or_default().replace("/", std::path::MAIN_SEPARATOR_STR));
83 }
84 if file.is_file() {
85 files.insert(path.to_str().unwrap_or_default().replace("/", std::path::MAIN_SEPARATOR_STR), FilesEntry{ data: zip_file.clone(), index: i});
86 } else if file.is_dir() {
87 dirs.insert(path.to_str().unwrap_or_default().replace("/", std::path::MAIN_SEPARATOR_STR));
88 } else {
89 bail!("read_zip_file: {} is neither a file nor a directory", path.display());
90 }
91 };
92 return Ok( () );
95 })
96 })
97 }
98
99 pub fn is_file_shim(path: &Path) -> bool {
100 if FILES.with(|files| files.borrow().is_empty()) {
101 let empty_path = PathBuf::new();
102 read_zip_file(&empty_path, None).unwrap_or(());
103 }
104 return FILES.with(|files| files.borrow().contains_key(path.to_str().unwrap_or_default()) );
105 }
106
107 pub fn is_dir_shim(path: &Path) -> bool {
108 if FILES.with(|files| files.borrow().is_empty()) {
109 let empty_path = PathBuf::new();
110 read_zip_file(&empty_path, None).unwrap_or(());
111 }
112 return DIRECTORIES.with(|dirs| dirs.borrow().contains(path.to_str().unwrap_or_default()) );
113 }
114
115 pub fn find_files_in_dir_that_ends_with_shim(dir: &Path, ending: &str) -> Vec<String> {
116 return FILES.with(|files| {
119 let files = files.borrow();
120 let mut answer = Vec::new();
121
122 let dir_name = dir.to_str().unwrap_or_default();
123 for file_name in files.keys() {
124 if let Some(dir_relative_name) = file_name.strip_prefix(dir_name) {
125 if file_name.ends_with(ending) {
126 let file_name = dir_relative_name.split_once(std::path::MAIN_SEPARATOR).map(|(_, after)| after).unwrap_or(dir_relative_name);
128 answer.push( file_name.to_string() );
129 }
130 }
131 }
132 return answer;
133 });
134 }
135
136 pub fn find_all_dirs_shim(dir: &Path, found_dirs: &mut Vec<PathBuf> ) {
137 return DIRECTORIES.with(|dirs| {
138 let dirs = dirs.borrow();
139
140 let common_dir_name = dir.to_str().unwrap_or_default();
141 for dir_name in dirs.iter() {
142 if dir_name.starts_with(common_dir_name) && !dir_name.contains("SharedRules") {
143 found_dirs.push(PathBuf::from(dir_name));
144 };
145 }
146 });
147 }
148
149
150 pub fn canonicalize_shim(path: &Path) -> std::io::Result<PathBuf> {
151 use std::ffi::OsStr;
152 let dot_dot = OsStr::new("..");
153 let mut result = PathBuf::new();
154 for part in path.iter() {
155 if dot_dot == part {
156 result.pop();
157 } else {
158 result.push(part);
159 }
160 }
161 return Ok(result);
162 }
163
164 pub fn read_to_string_shim(path: &Path) -> Result<String> {
165 let path = canonicalize_shim(path).unwrap(); let file_name = path.to_str().unwrap_or_default();
167 if let Some(contents) = OVERRIDE_FILE_NAME.with(|override_name| {
169 if file_name == override_name.borrow().as_str() {
170 return OVERRIDE_FILE_CONTENTS.with(|contents| return Some(contents.borrow().clone()));
172 } else {
173 return None;
174 }
175 }) {
176 return Ok(contents);
177 };
178
179 return FILES.with(|files| {
182 let files = files.borrow();
183 let zip_file = match files.get(file_name) {
184 None => bail!("Didn't find file '{}'", file_name),
185 Some(data) => data,
186 };
187 let mut archive = match zip_file.data.as_ref() {
188 None => {
189 let buf_reader = Cursor::new(ZIPPED_RULE_FILES);
190 zip::ZipArchive::new(buf_reader).unwrap()
191 },
192 Some(zip_file) => {
193 let buf_reader = Cursor::new((zip_file).as_ref());
194 zip::ZipArchive::new(buf_reader).unwrap()
195 }
196 };
197 let mut file = match archive.by_index(zip_file.index) {
201 Ok(file) => file,
202 Err(..) => {
203 panic!("Didn't find {} in zip archive", file_name);
204 }
205 };
206
207 let mut contents = String::new();
208 if let Err(e) = file.read_to_string(&mut contents) {
209 bail!("read_to_string: {}", e);
210 }
211 return Ok(contents);
212 });
213 }
214
215 pub fn zip_extract_shim(dir: &Path, zip_file_name: &str) -> Result<bool> {
216 let zip_file_path = dir.join(zip_file_name);
217 let full_zip_file_name = zip_file_path.to_str().unwrap_or_default().replace(std::path::MAIN_SEPARATOR_STR, "/");
218
219 let buf_reader = Cursor::new(ZIPPED_RULE_FILES);
221 let mut archive = zip::ZipArchive::new(buf_reader).unwrap();
222 let mut file = match archive.by_name(&full_zip_file_name) {
223 Ok(file) => file,
224 Err(..) => {
225 bail!("Didn't find {} in dir {} in zip archive", zip_file_name, dir.display());
226 }
227 };
228
229 let mut zip_file_bytes: Vec<u8> = Vec::with_capacity(file.size() as usize);
231 if let Err(e) = file.read_to_end(&mut zip_file_bytes) {
232 bail!("Failed to extract file {} (size={}): {}", zip_file_path.display(), file.size(), e);
233 }
234 read_zip_file(dir, Some(zip_file_bytes))?;
235 return Ok(true);
236 }
237
238 thread_local! {
239 static OVERRIDE_FILE_NAME: RefCell<String> = RefCell::new("".to_string());
241 static OVERRIDE_FILE_CONTENTS: RefCell<String> = RefCell::new("".to_string());
242 }
243 pub fn override_file_for_debugging_rules(file_name: &str, file_contents: &str) {
244 OVERRIDE_FILE_NAME.with(|name| *name.borrow_mut() = file_name.to_string().replace("/", "\\"));
246 OVERRIDE_FILE_CONTENTS.with(|contents| *contents.borrow_mut() = file_contents.to_string());
247 crate::interface::set_rules_dir("Rules".to_string()).unwrap(); }
249 } else {
250 pub fn is_file_shim(path: &Path) -> bool {
251 return path.is_file();
252 }
253
254 pub fn is_dir_shim(path: &Path) -> bool {
255 return path.is_dir();
256 }
257
258 pub fn find_files_in_dir_that_ends_with_shim(dir: &Path, ending: &str) -> Vec<String> {
259 match dir.read_dir() {
260 Err(_) => return vec![], Ok(read_dir) => {
262 let mut answer = Vec::new();
263 for dir_entry in read_dir.flatten() {
264 let file_name = dir_entry.file_name();
265 let file_name = file_name.to_string_lossy().to_string();
266 if file_name.ends_with(ending) {
267 let file_name = file_name.split_once(std::path::MAIN_SEPARATOR).map(|(_, after)| after).unwrap_or(&file_name);
269 answer.push( file_name.to_string() );
270 }
271 }
272 return answer;
273 }
274 }
275 }
276
277 pub fn find_all_dirs_shim(dir: &Path, found_dirs: &mut Vec<PathBuf> ) {
278 assert!(dir.is_dir(), "find_all_dirs_shim called with non-directory path: {}", dir.display());
280 let mut found_rules_file = false;
281 if let Ok(entries) = std::fs::read_dir(dir) {
282 for entry in entries.flatten() {
283 let path = entry.path();
284 if path.is_dir() {
285 if let Some(dir_name) = path.file_name() {
287 if dir_name.to_str().unwrap_or_default() != "SharedRules" {
288 find_all_dirs_shim(&path, found_dirs);
289 }
290 }
291 } else {
292 let file_name = path.file_name().unwrap_or_default().to_str().unwrap_or_default();
293 if !found_rules_file && (file_name.starts_with("unicode") || file_name.ends_with("_Rules.yaml") || file_name.ends_with(".zip")) {
294 found_dirs.push(path.parent().unwrap().to_path_buf());
295 found_rules_file = true;
296 }
297 }
298 }
299 }
300 }
301
302 pub fn canonicalize_shim(path: &Path) -> std::io::Result<PathBuf> {
303 return path.canonicalize();
304 }
305
306 pub fn read_to_string_shim(path: &Path) -> Result<String> {
307 let path = match path.canonicalize() {
308 Ok(path) => path,
309 Err(e) => bail!("Read error while trying to canonicalize in read_to_string_shim {}: {}", path.display(), e),
310 };
311 info!("Reading file '{}'", &path.display());
312 match std::fs::read_to_string(&path) {
313 Ok(str) => return Ok(str),
314 Err(e) => bail!("Read error while trying to read {}: {}", &path.display(), e),
315 }
316 }
317
318 pub fn zip_extract_shim(dir: &Path, zip_file_name: &str) -> Result<bool> {
319 let zip_file = dir.join(zip_file_name);
320 return match std::fs::read(zip_file) {
321 Err(e) => {
322 let yaml_files = find_files_in_dir_that_ends_with_shim(dir, ".yaml");
324 if yaml_files.is_empty() {
325 bail!("{}", e)
326 } else {
327 Ok(false)
328 }
329 },
330 Ok(contents) => {
331 let archive = std::io::Cursor::new(contents);
332 let mut zip_archive = zip::ZipArchive::new(archive).unwrap();
333 zip_archive.extract(dir).expect("Zip extraction failed");
334 Ok(true)
335 },
336 };
337 }
338 }
339}