use std::env;
use std::fmt;
use std::io;
use std::path;
use app_dirs::*;
use GameError;
use GameResult;
use conf;
use vfs::{self, VFS};
pub use vfs::OpenOptions;
const CONFIG_NAME: &'static str = "/conf.toml";
#[derive(Debug)]
pub struct Filesystem {
vfs: vfs::OverlayFS,
resources_path: path::PathBuf,
zip_path: path::PathBuf,
user_config_path: path::PathBuf,
user_data_path: path::PathBuf,
}
pub enum File {
VfsFile(Box<vfs::VFile>),
}
impl fmt::Debug for File {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
match *self {
File::VfsFile(ref _file) => write!(f, "VfsFile"),
}
}
}
impl io::Read for File {
fn read(&mut self, buf: &mut [u8]) -> io::Result<usize> {
match *self {
File::VfsFile(ref mut f) => f.read(buf),
}
}
}
impl io::Write for File {
fn write(&mut self, buf: &[u8]) -> io::Result<usize> {
match *self {
File::VfsFile(ref mut f) => f.write(buf),
}
}
fn flush(&mut self) -> io::Result<()> {
match *self {
File::VfsFile(ref mut f) => f.flush(),
}
}
}
impl Filesystem {
pub fn new(id: &'static str, author: &'static str) -> GameResult<Filesystem> {
let app_info = AppInfo {
name: id,
author: author,
};
let mut root_path = env::current_exe()?;
if root_path.file_name().is_some() {
root_path.pop();
}
let mut overlay = vfs::OverlayFS::new();
let mut resources_path;
let mut resources_zip_path;
let user_data_path;
let user_config_path;
{
resources_path = root_path.clone();
resources_path.push("resources");
let physfs = vfs::PhysicalFS::new(&resources_path, true);
overlay.push_back(Box::new(physfs));
}
{
resources_zip_path = root_path.clone();
resources_zip_path.push("resources.zip");
if resources_zip_path.exists() {
let zipfs = vfs::ZipFS::new(&resources_zip_path)?;
overlay.push_back(Box::new(zipfs));
}
}
{
user_data_path = app_root(AppDataType::UserData, &app_info)?;
let physfs = vfs::PhysicalFS::new(&user_data_path, true);
overlay.push_back(Box::new(physfs));
}
{
user_config_path = app_root(AppDataType::UserConfig, &app_info)?;
let physfs = vfs::PhysicalFS::new(&user_config_path, false);
overlay.push_back(Box::new(physfs));
}
#[cfg(feature = "cargo-resource-root")]
{
if let Ok(manifest_dir) = env::var("CARGO_MANIFEST_DIR") {
let mut path = path::PathBuf::from(manifest_dir);
path.push("resources");
let physfs = vfs::PhysicalFS::new(&path, true);
overlay.push_back(Box::new(physfs));
}
}
let fs = Filesystem {
vfs: overlay,
resources_path: resources_path,
zip_path: resources_zip_path,
user_config_path: user_config_path,
user_data_path: user_data_path,
};
Ok(fs)
}
pub fn open<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
self.vfs.open(path.as_ref()).map(|f| File::VfsFile(f))
}
pub fn open_options<P: AsRef<path::Path>>(&mut self,
path: P,
options: &OpenOptions)
-> GameResult<File> {
self.vfs
.open_options(path.as_ref(), options)
.map(|f| File::VfsFile(f))
.map_err(|e| {
GameError::ResourceLoadError(format!(
"Tried to open {:?} but got error: {:?}",
path.as_ref(),
e
))
})
}
pub fn create<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<File> {
self.vfs.create(path.as_ref()).map(|f| File::VfsFile(f))
}
pub fn create_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
self.vfs.mkdir(path.as_ref())
}
pub fn delete<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
self.vfs.rm(path.as_ref())
}
pub fn delete_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<()> {
self.vfs.rmrf(path.as_ref())
}
pub fn exists<P: AsRef<path::Path>>(&self, path: P) -> bool {
self.vfs.exists(path.as_ref())
}
pub fn is_file<P: AsRef<path::Path>>(&self, path: P) -> bool {
self.vfs
.metadata(path.as_ref())
.map(|m| m.is_file())
.unwrap_or(false)
}
pub fn is_dir<P: AsRef<path::Path>>(&self, path: P) -> bool {
self.vfs
.metadata(path.as_ref())
.map(|m| m.is_dir())
.unwrap_or(false)
}
pub fn get_user_data_dir(&self) -> &path::Path {
&self.user_data_path
}
pub fn get_user_config_dir(&self) -> &path::Path {
&self.user_config_path
}
pub fn get_resources_dir(&self) -> &path::Path {
&self.resources_path
}
pub fn read_dir<P: AsRef<path::Path>>(&mut self, path: P) -> GameResult<Vec<path::PathBuf>> {
let itr = self.vfs
.read_dir(path.as_ref())?
.map(|fname| fname.unwrap())
.collect();
Ok(itr)
}
pub fn print_all(&mut self) -> GameResult<()> {
for vfs in self.vfs.roots() {
println!("Source {:?}", vfs);
for itm in vfs.read_dir(path::Path::new("/"))? {
println!(" {:?}", itm);
}
}
Ok(())
}
pub fn mount(&mut self, path: &path::Path, readonly: bool) {
let physfs = vfs::PhysicalFS::new(&path, readonly);
self.vfs.push_back(Box::new(physfs));
}
pub fn read_config(&mut self) -> GameResult<conf::Conf> {
let conf_path = path::Path::new(CONFIG_NAME);
if self.is_file(conf_path) {
let mut file = self.open(conf_path)?;
let c = conf::Conf::from_toml_file(&mut file)?;
Ok(c)
} else {
Err(GameError::ConfigError(String::from("Config file not found")))
}
}
pub fn write_config(&mut self, conf: &conf::Conf) -> GameResult<()> {
let conf_path = path::Path::new(CONFIG_NAME);
let mut file = self.create(conf_path)?;
conf.to_toml_file(&mut file)?;
if self.is_file(conf_path) {
Ok(())
} else {
Err(GameError::ConfigError(format!("Failed to write config file at {}",
conf_path.to_string_lossy())))
}
}
}
#[cfg(test)]
mod tests {
use error::*;
use filesystem::*;
use std::path;
use std::io::{Read, Write};
use conf;
fn get_dummy_fs_for_tests() -> Filesystem {
let mut path = path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
path.push("resources");
let physfs = vfs::PhysicalFS::new(&path, false);
let mut ofs = vfs::OverlayFS::new();
ofs.push_front(Box::new(physfs));
Filesystem {
vfs: ofs,
resources_path: "".into(),
zip_path: "".into(),
user_config_path: "".into(),
user_data_path: "".into(),
}
}
#[test]
fn test_file_exists() {
let f = get_dummy_fs_for_tests();
let tile_file = path::Path::new("/tile.png");
assert!(f.exists(tile_file));
assert!(f.is_file(tile_file));
let tile_file = path::Path::new("/oglebog.png");
assert!(!f.exists(tile_file));
assert!(!f.is_file(tile_file));
assert!(!f.is_dir(tile_file));
}
#[test]
fn test_read_dir() {
let mut f = get_dummy_fs_for_tests();
let dir_contents_size = f.read_dir("/").unwrap().len();
assert!(dir_contents_size > 0);
}
#[test]
fn test_create_delete_file() {
let mut fs = get_dummy_fs_for_tests();
let test_file = path::Path::new("/testfile.txt");
let bytes = "test".as_bytes();
{
let mut file = fs.create(test_file).unwrap();
file.write(bytes).unwrap();
}
{
let mut buffer = Vec::new();
let mut file = fs.open(test_file).unwrap();
file.read_to_end(&mut buffer).unwrap();
assert_eq!(bytes, buffer.as_slice());
}
fs.delete(test_file).unwrap();
}
#[test]
fn test_file_not_found() {
let mut fs = get_dummy_fs_for_tests();
{
let rel_file = "testfile.txt";
match fs.open(rel_file) {
Err(GameError::ResourceNotFound(_, _)) => (),
Err(e) => panic!("Invalid error for opening file with relative path: {:?}", e),
Ok(f) => panic!("Should have gotten an error but instead got {:?}!", f),
}
}
{
match fs.open("/ooglebooglebarg.txt") {
Err(GameError::ResourceNotFound(_, _)) => (),
Err(e) => panic!("Invalid error for opening nonexistent file: {}", e),
Ok(f) => panic!("Should have gotten an error but instead got {:?}", f),
}
}
}
#[test]
fn test_write_config() {
let mut f = get_dummy_fs_for_tests();
let conf = conf::Conf::new();
match f.write_config(&conf) {
Ok(_) => (),
Err(e) => panic!("{:?}", e),
}
f.delete(CONFIG_NAME).unwrap();
}
}