106 lines
4.2 KiB
Rust
106 lines
4.2 KiB
Rust
// ────────────────────────────────────────────────────────────────
|
||
// 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<PathBuf>) -> 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<T: DeserializeOwned>(&self, path: &Path) -> Result<T, FsError> {
|
||
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<Vec<u8>, 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<T: Serialize>(&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()
|
||
}
|
||
}
|
||
}
|
||
}
|