use std::fs; use std::io::{ErrorKind, Read, Write}; use std::path::{Path, PathBuf}; use serde::Serialize; use serde::de::DeserializeOwned; use super::{FileSystem, FsError, LegacyFileSystem}; pub struct Fs { project_root: PathBuf, } impl Fs { /// Resolve the user supplied *relative* path against the project root. #[inline] fn full_path(&self, path: &Path) -> PathBuf { self.project_root.join(path) } pub fn new(root: impl Into) -> Self { Self { project_root: root.into(), } } } impl FileSystem for Fs { fn read(&self, path: &Path) -> Result, FsError> { fs::read(self.full_path(path)).map_err(FsError::Io) } fn write(&self, path: &Path, data: &[u8]) -> Result<(), FsError> { fs::File::create(self.full_path(path))? .write(data) .map_err(FsError::Io)?; Ok(()) } fn delete(&self, path: &Path) -> Result<(), FsError> { let full_path = self.full_path(path); match fs::remove_file(&full_path) { Ok(()) => Ok(()), Err(e) if e.kind() == ErrorKind::IsADirectory => { // Remove a directory tree. fs::remove_dir_all(&full_path).map_err(FsError::Io) } Err(e) => Err(FsError::Io(e)), } } } pub struct NativeFileSystem { project_root: PathBuf, } impl NativeFileSystem { /// Create a new instance. pub fn new(root: impl Into) -> Self { Self { project_root: root.into(), } } /// Resolve the user supplied *relative* path against the project root. #[inline] fn full_path(&self, path: &Path) -> PathBuf { self.project_root.join(path) } } impl LegacyFileSystem for NativeFileSystem { fn read(&self, path: &Path) -> Result { let full_path = self.full_path(path); let content = fs::read_to_string(full_path).map_err(FsError::Io)?; ron::from_str(&content).map_err(FsError::Spanned) } fn read_bytes(&self, path: &Path) -> Result, FsError> { let full_path = self.full_path(path); let mut contents = Vec::new(); fs::File::open(full_path)?.read_to_end(&mut contents)?; Ok(contents) } fn write(&self, path: &Path, data: T) -> Result<(), FsError> { let full_path = self.full_path(path); // Ensure the parent directory exists. if let Some(parent) = full_path.parent() { fs::create_dir_all(parent)?; } let content = ron::to_string(&data).map_err(FsError::Ron)?; fs::File::create(full_path)? .write(content.as_bytes()) .map_err(FsError::Io)?; Ok(()) } fn delete(&self, path: &Path) -> Result<(), FsError> { let full_path = self.full_path(path); match fs::remove_file(&full_path) { Ok(()) => Ok(()), Err(e) if e.kind() == ErrorKind::IsADirectory => { // Remove a directory tree. fs::remove_dir_all(full_path).map_err(FsError::Io) } Err(e) => Err(FsError::Io(e)), } } fn mkdir(&self, path: &Path) -> Result<(), FsError> { let full_path = self.full_path(path); fs::create_dir_all(full_path).map_err(FsError::Io) } fn lsfiles(&self, path: &Path) -> Result, FsError> { let full_path = self.full_path(path); let paths = fs::read_dir(full_path)? .filter_map(|res| res.ok()) .filter(|entry| entry.file_type().unwrap().is_file()) .map(|entry| entry.path()) .collect(); Ok(paths) } fn lsdirs(&self, path: &Path) -> Result, FsError> { let full_path = self.full_path(path); let paths = fs::read_dir(full_path)? .filter_map(|res| res.ok()) .filter(|entry| entry.file_type().unwrap().is_dir()) .map(|entry| entry.path()) .collect(); Ok(paths) } fn exists(&self, path: &Path) -> bool { let full_path = self.full_path(path); full_path.exists() } fn rename(&self, path: &Path, other: &Path) -> Result<(), FsError> { let full_path = self.full_path(path); let full_other = self.full_path(other); fs::rename(full_path, full_other).map_err(FsError::Io) } fn config_path(&self) -> PathBuf { match std::env::var("HOME") { Ok(path) => PathBuf::from(path + "/.config/worldcoder/settings.ron"), Err(_) => { eprintln!( "XDG_CONFIG_HOME not set, using default path of ~/.config/worldcoder/settings.ron" ); "~/.config/worldcoder/settings.ron".into() } } } }