// ──────────────────────────────────────────────────────────────── // Imports // ──────────────────────────────────────────────────────────────── use std::fs; use std::io::{ErrorKind, Read}; use std::path::{Path, PathBuf}; use serde::Serialize; use serde::de::DeserializeOwned; use crate::filesystem::{FsError, LegacyFileSystem}; // ──────────────────────────────────────────────────────────────── // Concrete implementation // ──────────────────────────────────────────────────────────────── /// The concrete file‑system. All paths are interpreted relative to /// `project_root`. 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) } } // ──────────────────────────────────────────────────────────────── // Implementation of the trait // ──────────────────────────────────────────────────────────────── impl LegacyFileSystem for NativeFileSystem { fn read(&self, path: &Path) -> Result { let full_path = self.full_path(path); let file = fs::File::open(full_path).map_err(FsError::Io)?; serde_json::from_reader(file).map_err(FsError::Serde) } 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 file = fs::File::create(full_path)?; serde_json::to_writer(file, &data).map_err(FsError::Serde) } 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 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.json"), Err(_) => { eprintln!( "XDG_CONFIG_HOME not set, using default path of ~/.config/worldcoder/settings.json" ); "~/.config/worldcoder/settings.json".into() } } } }