added filesystem abstraction layer and implemented basic filesystem - testing not complete yet, still features missing
Continuous integration / build (push) Has been cancelled
Continuous integration / build (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,103 @@
|
||||
use std::{
|
||||
collections::HashMap,
|
||||
io,
|
||||
path::{Path, PathBuf},
|
||||
sync::LazyLock,
|
||||
};
|
||||
|
||||
use serde::{Serialize, de::DeserializeOwned};
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
use crate::PROJECT_FOLDER;
|
||||
use crate::filesystem::native::NativeFileSystem;
|
||||
|
||||
#[cfg(feature = "native")]
|
||||
pub mod native;
|
||||
|
||||
#[cfg(feature = "web")]
|
||||
pub mod web;
|
||||
|
||||
pub static FILESYSTEM: LazyLock<NativeFileSystem> = LazyLock::new(|| {
|
||||
#[cfg(feature = "native")]
|
||||
return NativeFileSystem::new(PROJECT_FOLDER.clone());
|
||||
#[cfg(feature = "web")]
|
||||
return Box::new(web::WebFileSystem::new());
|
||||
});
|
||||
|
||||
pub trait LegacyFileSystem {
|
||||
fn read<T: DeserializeOwned>(&self, path: &Path) -> Result<T, FsError>;
|
||||
fn read_bytes(&self, path: &Path) -> Result<Vec<u8>, FsError>;
|
||||
fn write<T: Serialize>(&self, path: &Path, data: T) -> Result<(), FsError>;
|
||||
fn delete(&self, path: &Path) -> Result<(), FsError>;
|
||||
fn mkdir(&self, path: &Path) -> Result<(), FsError>;
|
||||
fn rename(&self, path: &Path, new_path: &Path) -> Result<(), FsError>;
|
||||
|
||||
#[allow(unused)]
|
||||
fn exists(&self, path: &Path) -> bool;
|
||||
|
||||
fn config_path(&self) -> PathBuf;
|
||||
}
|
||||
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// Custom error type
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
#[derive(Debug)]
|
||||
pub enum FsError {
|
||||
Io(io::Error),
|
||||
Serde(serde_json::Error),
|
||||
}
|
||||
|
||||
impl std::fmt::Display for FsError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
FsError::Io(e) => write!(f, "IO error: {e}"),
|
||||
FsError::Serde(e) => write!(f, "Serialization error: {e}"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for FsError {
|
||||
fn source(&self) -> Option<&(dyn std::error::Error + 'static)> {
|
||||
match self {
|
||||
FsError::Io(e) => Some(e),
|
||||
FsError::Serde(e) => Some(e),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Convert the two underlying error types into our own
|
||||
impl From<io::Error> for FsError {
|
||||
fn from(err: io::Error) -> Self {
|
||||
FsError::Io(err)
|
||||
}
|
||||
}
|
||||
impl From<serde_json::Error> for FsError {
|
||||
fn from(err: serde_json::Error) -> Self {
|
||||
FsError::Serde(err)
|
||||
}
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Id(String);
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub trait FileSystem {
|
||||
fn load<T: DeserializeOwned>(&self, id: Id) -> Result<T, FsError>;
|
||||
fn save<T: Serialize>(&self, id: Id, data: T) -> Result<(), FsError>;
|
||||
fn mkdir(&self, path: Path) -> Result<(), FsError>;
|
||||
fn exists(&self, path: Path) -> bool;
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Index {
|
||||
file_cache: HashMap<Id, PathBuf>,
|
||||
project_root: Directory,
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
pub struct Directory {
|
||||
name: String,
|
||||
id: Id,
|
||||
children: HashMap<Id, Directory>,
|
||||
files: Vec<Id>,
|
||||
}
|
||||
@@ -0,0 +1,105 @@
|
||||
// ────────────────────────────────────────────────────────────────
|
||||
// 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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user