zebflow 0.11.14

Reactive web automation platform — pipelines, templates, and real-time collaboration in a single binary.
Documentation
//! The store a project's files live in, whichever backend it declared.
//!
//! One enum rather than a trait object so that every caller keeps writing
//! `layout.open_files().get(path)` and the compiler, not a vtable, says which
//! implementation answers. A third backend is one more variant here and its
//! arm in each verb; the callers do not change.

use std::path::{Path, PathBuf};

use super::backend::FileBackend;
use super::error::ZebFsError;
use super::local::LocalZebFs;
use super::model::{ZebFsEntry, ZebFsObject, ZebFsStat};
use super::s3::S3ZebFs;

/// A project's file store.
#[derive(Debug, Clone)]
pub enum ZebFs {
    Local(LocalZebFs),
    S3(S3ZebFs),
}

impl ZebFs {
    /// The declared word this store answers to.
    pub fn backend(&self) -> FileBackend {
        match self {
            Self::Local(_) => FileBackend::Zebfs,
            Self::S3(_) => FileBackend::S3,
        }
    }

    /// The directory a local store keeps its bytes in; `None` for a bucket.
    ///
    /// For the few callers that stream from a file path — a map engine, a
    /// table engine — and can only do so when there is one.
    pub fn local_root(&self) -> Option<&Path> {
        match self {
            Self::Local(local) => Some(local.root()),
            Self::S3(_) => None,
        }
    }

    /// The local filesystem path of an object, when the store is a
    /// directory. A bucket has no such path, and answers `None` rather than
    /// a path that does not exist.
    pub fn local_path(&self, path: &str) -> Result<Option<PathBuf>, ZebFsError> {
        match self {
            Self::Local(local) => local.resolve_object_path(path).map(|(_, abs)| Some(abs)),
            Self::S3(_) => Ok(None),
        }
    }

    pub fn put(&self, path: &str, bytes: &[u8]) -> Result<ZebFsStat, ZebFsError> {
        match self {
            Self::Local(s) => s.put(path, bytes),
            Self::S3(s) => s.put(path, bytes),
        }
    }

    /// Copies one object into `dest` without holding it in memory.
    pub fn get_to_file(&self, path: &str, dest: &Path) -> Result<ZebFsStat, ZebFsError> {
        match self {
            Self::Local(s) => {
                let (rel, abs) = s.resolve_object_path(path)?;
                if !abs.is_file() {
                    return Err(ZebFsError::new("ZEBFS_NOT_FOUND", "object not found"));
                }
                std::fs::copy(&abs, dest)?;
                s.head(&rel)
            }
            Self::S3(s) => s.get_to_file(path, dest),
        }
    }

    /// `len` bytes of one object from `offset`, as a blocking reader — what a
    /// streamed answer pulls from (`web.response.send` with a stored file).
    /// Nothing is held in memory; a bucket is asked for exactly that range.
    pub fn open_range(&self, path: &str, offset: u64, len: u64) -> Result<Box<dyn std::io::Read + Send>, ZebFsError> {
        match self {
            Self::Local(s) => {
                use std::io::{Read, Seek, SeekFrom};
                let (_, abs) = s.resolve_object_path(path)?;
                if !abs.is_file() {
                    return Err(ZebFsError::new("ZEBFS_NOT_FOUND", "object not found"));
                }
                let mut file = std::fs::File::open(&abs)?;
                file.seek(SeekFrom::Start(offset))?;
                Ok(Box::new(file.take(len)))
            }
            Self::S3(s) => s.open_range(path, offset, len),
        }
    }

    /// Writes one object from a local file without holding it in memory.
    pub fn put_from_file(&self, path: &str, src: &Path) -> Result<ZebFsStat, ZebFsError> {
        match self {
            Self::Local(s) => s.put_from_file(path, src),
            Self::S3(s) => s.put_from_file(path, src),
        }
    }

    pub fn get(&self, path: &str) -> Result<ZebFsObject, ZebFsError> {
        match self {
            Self::Local(s) => s.get(path),
            Self::S3(s) => s.get(path),
        }
    }

    pub fn head(&self, path: &str) -> Result<ZebFsStat, ZebFsError> {
        match self {
            Self::Local(s) => s.head(path),
            Self::S3(s) => s.head(path),
        }
    }

    pub fn list(&self, prefix: &str) -> Result<Vec<ZebFsEntry>, ZebFsError> {
        match self {
            Self::Local(s) => s.list(prefix),
            Self::S3(s) => s.list(prefix),
        }
    }

    pub fn create_prefix(&self, prefix: &str) -> Result<ZebFsStat, ZebFsError> {
        match self {
            Self::Local(s) => s.create_prefix(prefix),
            Self::S3(s) => s.create_prefix(prefix),
        }
    }

    pub fn delete(&self, path: &str) -> Result<(), ZebFsError> {
        match self {
            Self::Local(s) => s.delete(path),
            Self::S3(s) => s.delete(path),
        }
    }

    pub fn copy(&self, from: &str, to: &str) -> Result<ZebFsStat, ZebFsError> {
        match self {
            Self::Local(s) => s.copy(from, to),
            Self::S3(s) => s.copy(from, to),
        }
    }

    /// A document under the reserved `.zebfs/` prefix — the ACL manifest —
    /// which the user verbs above refuse. `None` when it was never written.
    pub fn read_reserved(&self, path: &str) -> Result<Option<Vec<u8>>, ZebFsError> {
        match self {
            Self::Local(s) => s.read_reserved(path),
            Self::S3(s) => s.read_reserved(path),
        }
    }

    pub fn write_reserved(&self, path: &str, bytes: &[u8]) -> Result<(), ZebFsError> {
        match self {
            Self::Local(s) => s.write_reserved(path, bytes),
            Self::S3(s) => s.write_reserved(path, bytes),
        }
    }
}