use std::cell::RefCell; use std::fs::File; use std::io; use std::io::{ErrorKind, Read}; use std::path::{Path, PathBuf}; use std::sync::{Arc, Mutex}; use hashbrown::HashMap; use super::common::CodeNotFound; use super::{FSResult, Loaded, VirtFS}; use crate::intern::{intern, Token}; use crate::name::PathSlice; use crate::proj_error::{ErrorSansOrigin, ErrorSansOriginObj}; #[derive(Clone)] struct OpenError { file: Arc>, dir: Arc>, } impl OpenError { pub fn wrap(file: io::Error, dir: io::Error) -> ErrorSansOriginObj { Self { dir: Arc::new(Mutex::new(dir)), file: Arc::new(Mutex::new(file)) }.pack() } } impl ErrorSansOrigin for OpenError { const DESCRIPTION: &'static str = "A file system error occurred"; fn message(&self) -> String { let Self { dir, file } = self; format!( "File system errors other than not found occurred\n\ as a file: {}\nas a directory: {}", file.lock().unwrap(), dir.lock().unwrap() ) } } #[derive(Clone)] struct IOError(Arc>); impl IOError { pub fn wrap(inner: io::Error) -> ErrorSansOriginObj { Self(Arc::new(Mutex::new(inner))).pack() } } impl ErrorSansOrigin for IOError { const DESCRIPTION: &'static str = "an I/O error occured"; fn message(&self) -> String { format!("File read error: {}", self.0.lock().unwrap()) } } #[derive(Clone)] struct NotUtf8(PathBuf); impl NotUtf8 { pub fn wrap(path: &Path) -> ErrorSansOriginObj { Self(path.to_owned()).pack() } } impl ErrorSansOrigin for NotUtf8 { const DESCRIPTION: &'static str = "Source files must be UTF-8"; fn message(&self) -> String { format!("{} is a source file but contains invalid UTF-8", self.0.display()) } } /// A real file system directory linked into the virtual FS pub struct DirNode { cached: RefCell>, root: PathBuf, suffix: &'static str, } impl DirNode { /// Reference a real file system directory in the virtual FS pub fn new(root: PathBuf, suffix: &'static str) -> Self { assert!(suffix.starts_with('.'), "Extension must begin with ."); Self { cached: RefCell::default(), root, suffix } } fn ext(&self) -> &str { self.suffix.strip_prefix('.').expect("Checked in constructor") } fn load_file(&self, fpath: &Path, orig_path: &PathSlice) -> FSResult { match fpath.read_dir() { Err(dir_e) => { let fpath = fpath.with_extension(self.ext()); let mut file = File::open(&fpath).map_err(|file_e| match (dir_e.kind(), file_e.kind()) { (ErrorKind::NotFound, ErrorKind::NotFound) => CodeNotFound::new(orig_path.to_vpath()).pack(), _ => OpenError::wrap(file_e, dir_e), })?; let mut buf = Vec::new(); file.read_to_end(&mut buf).map_err(IOError::wrap)?; let text = String::from_utf8(buf).map_err(|_| NotUtf8::wrap(&fpath))?; Ok(Loaded::Code(Arc::new(text))) }, Ok(dir) => Ok(Loaded::collection(dir.filter_map(|ent_r| { let ent = ent_r.ok()?; let name = ent.file_name().into_string().ok()?; match ent.metadata().ok()?.is_dir() { false => Some(intern(name.strip_suffix(self.suffix)?)), true => Some(intern(&name)), } }))), } } fn mk_pathbuf(&self, path: &[IStr]) -> PathBuf { let mut fpath = self.root.clone(); path.iter().for_each(|seg| fpath.push(seg.as_str())); fpath } } impl VirtFS for DirNode { fn get(&self, path: &[IStr], full_path: &PathSlice) -> FSResult { let fpath = self.mk_pathbuf(path); let mut binding = self.cached.borrow_mut(); let (_, res) = (binding.raw_entry_mut().from_key(&fpath)) .or_insert_with(|| (fpath.clone(), self.load_file(&fpath, full_path))); res.clone() } fn display(&self, path: &[IStr]) -> Option { let pathbuf = self.mk_pathbuf(path).with_extension(self.ext()); Some(pathbuf.to_string_lossy().to_string()) } }