2024-11-17 22:34:43 +01:00
|
|
|
//! The treehouse virtual file system.
|
|
|
|
//!
|
|
|
|
//! Unlike traditional file systems, there is no separation between directories and files.
|
|
|
|
//! Instead, our file system is based on _entries_, which may have specific, optional, well-typed
|
|
|
|
//! metadata attached to them.
|
|
|
|
//! A directory is formed by returning a list of paths from [`dir`][Dir::dir], and a file is
|
|
|
|
//! formed by returning `Some` from [`content`][Dir::content].
|
|
|
|
//!
|
|
|
|
//! This makes using the file system simpler, as you do not have to differentiate between different
|
|
|
|
//! entry kinds. All paths act as if they _could_ return byte content, and all paths act as if they
|
|
|
|
//! _could_ have children.
|
|
|
|
//!
|
|
|
|
//! # Composability
|
|
|
|
//!
|
|
|
|
//! [`Dir`]s are composable. The [`Dir`] itself starts off with the root path ([`VPath::ROOT`]),
|
|
|
|
//! which may contain further [`dir`][Dir::dir] entries, or content by itself.
|
|
|
|
//! This makes it possible to nest a [`Dir`] under another [`Dir`].
|
|
|
|
//!
|
|
|
|
//! Additionally, there's also the inverse operation, [`Cd`] (named after the `cd`
|
|
|
|
//! _change directory_ shell command), which returns a [`Dir`] viewing a subpath within another
|
|
|
|
//! [`Dir`].
|
|
|
|
//!
|
|
|
|
//! # Building directories
|
|
|
|
//!
|
|
|
|
//! In-memory directories can be composed using the following primitives:
|
|
|
|
//!
|
|
|
|
//! - [`EmptyEntry`] - has no metadata whatsoever.
|
|
|
|
//! - [`BufferedFile`] - root path content is the provided byte vector.
|
|
|
|
//! - [`MemDir`] - a [`Dir`] containing a single level of other [`Dir`]s inside.
|
|
|
|
//!
|
|
|
|
//! Additionally, for interfacing with the OS file system, [`PhysicalDir`] is available,
|
|
|
|
//! representing a directory stored on the disk.
|
|
|
|
//!
|
|
|
|
//! # Virtual paths
|
|
|
|
//!
|
|
|
|
//! Entries within directories are referenced using [`VPath`]s (**v**irtual **path**s).
|
|
|
|
//! A virtual path is composed out of any amount of `/`-separated components.
|
|
|
|
//!
|
|
|
|
//! There are no special directories like `.` and `..` (those are just normal entries, though using
|
|
|
|
//! them is discouraged). [`VPath`]s are always relative to the root of the [`Dir`] you're querying.
|
|
|
|
//!
|
|
|
|
//! A leading or trailing slash is not allowed, because they would have no meaning.
|
|
|
|
//!
|
|
|
|
//! [`VPath`] also has an owned version, [`VPathBuf`].
|
|
|
|
|
2024-11-16 18:33:41 +01:00
|
|
|
use std::{
|
|
|
|
fmt::{self, Debug},
|
|
|
|
ops::{ControlFlow, Deref},
|
2024-11-17 22:34:43 +01:00
|
|
|
sync::Arc,
|
2024-11-16 18:33:41 +01:00
|
|
|
};
|
2024-11-08 14:52:32 +01:00
|
|
|
|
2024-11-16 18:33:41 +01:00
|
|
|
mod anchored;
|
2024-11-17 22:34:43 +01:00
|
|
|
pub mod asynch;
|
2024-11-16 18:33:41 +01:00
|
|
|
mod cd;
|
2024-11-23 18:29:03 +01:00
|
|
|
mod content_version_cache;
|
2024-11-17 22:34:43 +01:00
|
|
|
mod edit;
|
2024-11-16 18:33:41 +01:00
|
|
|
mod empty;
|
|
|
|
mod file;
|
2024-11-17 22:34:43 +01:00
|
|
|
mod mem_dir;
|
|
|
|
mod overlay;
|
|
|
|
mod path;
|
2024-11-16 18:33:41 +01:00
|
|
|
mod physical;
|
|
|
|
|
|
|
|
pub use anchored::*;
|
|
|
|
pub use cd::*;
|
2024-11-23 18:29:03 +01:00
|
|
|
pub use content_version_cache::*;
|
2024-11-17 22:34:43 +01:00
|
|
|
pub use edit::*;
|
2024-11-16 18:33:41 +01:00
|
|
|
pub use empty::*;
|
|
|
|
pub use file::*;
|
2024-11-17 22:34:43 +01:00
|
|
|
pub use mem_dir::*;
|
|
|
|
pub use overlay::*;
|
|
|
|
pub use path::*;
|
2024-11-16 18:33:41 +01:00
|
|
|
pub use physical::*;
|
2024-11-08 14:52:32 +01:00
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)]
|
|
|
|
pub struct DirEntry {
|
|
|
|
pub path: VPathBuf,
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
pub trait Dir: Debug {
|
|
|
|
/// List all entries under the provided path.
|
|
|
|
fn dir(&self, path: &VPath) -> Vec<DirEntry>;
|
2024-11-08 14:52:32 +01:00
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
/// Return the byte content of the entry at the given path.
|
|
|
|
fn content(&self, path: &VPath) -> Option<Vec<u8>>;
|
2024-11-08 14:52:32 +01:00
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
/// Get a string signifying the current version of the provided path's content.
|
|
|
|
/// If the content changes, the version must also change.
|
|
|
|
///
|
|
|
|
/// Returns None if there is no content or no version string is available.
|
|
|
|
fn content_version(&self, path: &VPath) -> Option<String>;
|
2024-11-08 14:52:32 +01:00
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
/// Returns a path relative to `config.site` indicating where the file will be available
|
|
|
|
/// once served.
|
|
|
|
///
|
|
|
|
/// May return `None` if the file is not served.
|
|
|
|
fn anchor(&self, _path: &VPath) -> Option<VPathBuf> {
|
|
|
|
None
|
2024-11-16 18:33:41 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
/// If a file can be written persistently, returns an [`EditPath`] representing the file in
|
|
|
|
/// persistent storage.
|
|
|
|
///
|
|
|
|
/// An edit path can then be made into an [`Edit`].
|
|
|
|
fn edit_path(&self, _path: &VPath) -> Option<EditPath> {
|
|
|
|
None
|
2024-11-16 18:33:41 +01:00
|
|
|
}
|
2024-11-17 22:34:43 +01:00
|
|
|
}
|
2024-11-16 18:33:41 +01:00
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
impl<T> Dir for &T
|
|
|
|
where
|
|
|
|
T: Dir,
|
|
|
|
{
|
|
|
|
fn dir(&self, path: &VPath) -> Vec<DirEntry> {
|
|
|
|
(**self).dir(path)
|
2024-11-16 18:33:41 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
fn content(&self, path: &VPath) -> Option<Vec<u8>> {
|
|
|
|
(**self).content(path)
|
2024-11-16 18:33:41 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
fn content_version(&self, path: &VPath) -> Option<String> {
|
|
|
|
(**self).content_version(path)
|
2024-11-16 18:33:41 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
fn anchor(&self, path: &VPath) -> Option<VPathBuf> {
|
|
|
|
(**self).anchor(path)
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
fn edit_path(&self, path: &VPath) -> Option<EditPath> {
|
|
|
|
(**self).edit_path(path)
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
#[derive(Clone)]
|
|
|
|
pub struct DynDir {
|
|
|
|
arc: Arc<dyn Dir + Send + Sync>,
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
impl Dir for DynDir {
|
|
|
|
fn dir(&self, path: &VPath) -> Vec<DirEntry> {
|
|
|
|
self.arc.dir(path)
|
2024-11-16 18:33:41 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
fn content(&self, path: &VPath) -> Option<Vec<u8>> {
|
|
|
|
self.arc.content(path)
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
fn content_version(&self, path: &VPath) -> Option<String> {
|
|
|
|
self.arc.content_version(path)
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
fn anchor(&self, path: &VPath) -> Option<VPathBuf> {
|
|
|
|
self.arc.anchor(path)
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
fn edit_path(&self, path: &VPath) -> Option<EditPath> {
|
|
|
|
self.arc.edit_path(path)
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
impl fmt::Debug for DynDir {
|
2024-11-08 14:52:32 +01:00
|
|
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
2024-11-17 22:34:43 +01:00
|
|
|
fmt::Debug::fmt(&*self.arc, f)
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
impl Deref for DynDir {
|
|
|
|
type Target = dyn Dir + Send + Sync;
|
2024-11-16 18:33:41 +01:00
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
fn deref(&self) -> &Self::Target {
|
|
|
|
&*self.arc
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
pub trait ToDynDir {
|
|
|
|
fn to_dyn(self) -> DynDir;
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
impl<T> ToDynDir for T
|
|
|
|
where
|
|
|
|
T: Dir + Send + Sync + 'static,
|
|
|
|
{
|
|
|
|
fn to_dyn(self) -> DynDir {
|
|
|
|
DynDir {
|
|
|
|
arc: Arc::new(self),
|
2024-11-16 18:33:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
pub trait AnchoredAtExt {
|
|
|
|
fn anchored_at(self, at: VPathBuf) -> Anchored<Self>
|
|
|
|
where
|
|
|
|
Self: Sized;
|
|
|
|
}
|
|
|
|
|
|
|
|
impl<T> AnchoredAtExt for T
|
|
|
|
where
|
2024-11-17 22:34:43 +01:00
|
|
|
T: Dir,
|
2024-11-16 18:33:41 +01:00
|
|
|
{
|
|
|
|
fn anchored_at(self, at: VPathBuf) -> Anchored<Self> {
|
|
|
|
Anchored::new(self, at)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
pub fn walk_dir_rec(dir: &dyn Dir, path: &VPath, f: &mut dyn FnMut(&VPath) -> ControlFlow<(), ()>) {
|
|
|
|
for entry in dir.dir(path) {
|
2024-11-16 18:33:41 +01:00
|
|
|
match f(&entry.path) {
|
|
|
|
ControlFlow::Continue(_) => (),
|
|
|
|
ControlFlow::Break(_) => return,
|
|
|
|
}
|
2024-11-17 22:34:43 +01:00
|
|
|
walk_dir_rec(dir, &entry.path, f);
|
2024-11-16 18:33:41 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-11-17 22:34:43 +01:00
|
|
|
pub fn url(site: &str, dir: &dyn Dir, path: &VPath) -> Option<String> {
|
|
|
|
let anchor = dir.anchor(path)?;
|
|
|
|
if let Some(version) = dir.content_version(path) {
|
|
|
|
Some(format!("{}/{anchor}?v={version}", site))
|
2024-11-16 18:33:41 +01:00
|
|
|
} else {
|
2024-11-17 22:34:43 +01:00
|
|
|
Some(format!("{}/{anchor}", site))
|
2024-11-16 18:33:41 +01:00
|
|
|
}
|
2024-11-08 14:52:32 +01:00
|
|
|
}
|