use std::{fmt, io}; use std::fmt::Debug; use std::io::Cursor; use std::io::ErrorKind; use std::io::SeekFrom; use std::path::{Component, Path, PathBuf}; use ggez::GameResult; use ggez::GameError::FilesystemError; use ggez::vfs::{OpenOptions, VFile, VFS, VMetadata}; #[derive(Debug)] pub struct BuiltinFile(Cursor<&'static [u8]>); impl BuiltinFile { pub fn from(buf: &'static [u8]) -> Box { Box::new(BuiltinFile(Cursor::new(buf))) } } impl io::Read for BuiltinFile { fn read(&mut self, buf: &mut [u8]) -> io::Result { self.0.read(buf) } } impl io::Seek for BuiltinFile { fn seek(&mut self, pos: SeekFrom) -> io::Result { self.0.seek(pos) } } impl io::Write for BuiltinFile { fn write(&mut self, buf: &[u8]) -> io::Result { Err(io::Error::new(ErrorKind::PermissionDenied, "Built-in file system is read-only.")) } fn flush(&mut self) -> io::Result<()> { Err(io::Error::new(ErrorKind::PermissionDenied, "Built-in file system is read-only.")) } } struct BuiltinMetadata { is_dir: bool, size: u64, } impl VMetadata for BuiltinMetadata { fn is_dir(&self) -> bool { self.is_dir } fn is_file(&self) -> bool { !self.is_dir } fn len(&self) -> u64 { self.size } } #[derive(Clone)] enum FSNode { File(&'static str, &'static [u8]), Directory(&'static str, Vec), } impl FSNode { fn get_name(&self) -> &'static str { match self { FSNode::File(name, _) => { name } FSNode::Directory(name, _) => { name } } } fn to_file(&self) -> GameResult> { match self { FSNode::File(_, buf) => { Ok(BuiltinFile::from(buf)) } FSNode::Directory(name, _) => { Err(FilesystemError(format!("{} is a directory.", name))) } } } fn to_metadata(&self) -> Box { match self { FSNode::File(_, buf) => { Box::new(BuiltinMetadata { is_dir: false, size: buf.len() as u64, }) } FSNode::Directory(_, _) => { Box::new(BuiltinMetadata { is_dir: true, size: 0, }) } } } } pub struct BuiltinFS { root: Vec, } impl BuiltinFS { pub fn new() -> Self { Self { root: vec![ FSNode::Directory("builtin", vec![ FSNode::File("builtin_font.fnt", include_bytes!("builtin/builtin_font.fnt")), FSNode::File("builtin_font_0.png", include_bytes!("builtin/builtin_font_0.png")), FSNode::File("builtin_font_1.png", include_bytes!("builtin/builtin_font_1.png")), FSNode::File("pixtone.pcm", include_bytes!("builtin/pixtone.pcm")), FSNode::File("touch.png", include_bytes!("builtin/touch.png")), FSNode::Directory("shaders", vec![ FSNode::File("basic_150.vert.glsl", include_bytes!("builtin/shaders/basic_150.vert.glsl")), FSNode::File("water_150.frag.glsl", include_bytes!("builtin/shaders/water_150.frag.glsl")), FSNode::File("basic_es300.vert.glsl", include_bytes!("builtin/shaders/basic_es300.vert.glsl")), FSNode::File("water_es300.frag.glsl", include_bytes!("builtin/shaders/water_es300.frag.glsl")), ]), FSNode::Directory("lightmap", vec![ FSNode::File("spot.png", include_bytes!("builtin/lightmap/spot.png")), FSNode::File("direct.png", include_bytes!("builtin/lightmap/direct.png")), ]), ]) ], } } fn get_node(&self, path: &Path) -> GameResult { let mut iter = path.components().peekable(); if let Some(Component::RootDir) = iter.next() { let mut curr_dir = &self.root; if iter.peek().is_none() { return Ok(FSNode::Directory("", self.root.clone())); } while let Some(comp) = iter.next() { let comp_name = comp.as_os_str().to_string_lossy(); for file in curr_dir { match file { FSNode::File(name, _) if comp_name.eq(name) => { return if iter.peek().is_some() { Err(FilesystemError(format!("Expected a directory, found a file: {:?}", path))) } else { Ok(file.clone()) }; } FSNode::Directory(name, contents) if comp_name.eq(name) => { if iter.peek().is_some() { curr_dir = contents; break; } else { return Ok(file.clone()); } } _ => {} } } } } else { return Err(FilesystemError("Path must be absolute.".to_string())); } Err(FilesystemError("File not found.".to_string())) } } impl Debug for BuiltinFS { fn fmt(&self, f: &mut fmt::Formatter) -> Result<(), fmt::Error> { write!(f, "") } } impl VFS for BuiltinFS { fn open_options(&self, path: &Path, open_options: OpenOptions) -> GameResult> { if open_options.write || open_options.create || open_options.append || open_options.truncate { let msg = format!( "Cannot alter file {:?} in root {:?}, filesystem read-only", path, self ); return Err(FilesystemError(msg)); } self.get_node(path)?.to_file() } fn mkdir(&self, _path: &Path) -> GameResult<()> { Err(FilesystemError("Tried to make directory {} but FS is read-only".to_string())) } fn rm(&self, _path: &Path) -> GameResult<()> { Err(FilesystemError("Tried to remove file {} but FS is read-only".to_string())) } fn rmrf(&self, _path: &Path) -> GameResult<()> { Err(FilesystemError("Tried to remove file/dir {} but FS is read-only".to_string())) } fn exists(&self, path: &Path) -> bool { self.get_node(path).is_ok() } fn metadata(&self, path: &Path) -> GameResult> { self.get_node(path).map(|v| v.to_metadata()) } fn read_dir(&self, path: &Path) -> GameResult>>> { match self.get_node(path) { Ok(FSNode::Directory(_, contents)) => { let mut vec = Vec::new(); for node in contents { vec.push(Ok(PathBuf::from(node.get_name()))) } Ok(Box::new(vec.into_iter())) } Ok(FSNode::File(_, _)) => { Err(FilesystemError(format!("Expected a directory, found a file: {:?}", path))) } Err(e) => { Err(e) } } } fn to_path_buf(&self) -> Option { None } } /*#[test] fn test_builtin_fs() { let fs = BuiltinFS { root: vec![ FSNode::File("test.txt", &[]), FSNode::Directory("memes", vec![ FSNode::File("nothing.txt", &[]), FSNode::Directory("secret stuff", vec![ FSNode::File("passwords.txt", &[b'1', b'2', b'3', b'4', b'5', b'6',]), ]), ]), FSNode::File("test2.txt", &[]), ], }; println!("{:?}", fs.get_node(Path::new("/")).unwrap()); println!("{:?}", fs.get_node(Path::new("/test.txt")).unwrap()); println!("{:?}", fs.get_node(Path::new("/memes")).unwrap()); println!("{:?}", fs.get_node(Path::new("/memes/nothing.txt")).unwrap()); println!("{:?}", fs.get_node(Path::new("/memes/secret stuff/passwords.txt")).unwrap()); }*/