pub(crate) mod cache;
pub(crate) mod childstdout;
pub(crate) mod decrypt;
pub(crate) mod dry_run;
pub(crate) mod hotcold;
pub(crate) mod ignore;
pub(crate) mod local_destination;
pub(crate) mod node;
pub(crate) mod stdin;
pub(crate) mod warm_up;
use std::{io::Read, ops::Deref, path::PathBuf, sync::Arc};
use bytes::Bytes;
use enum_map::Enum;
use log::trace;
#[cfg(test)]
use mockall::mock;
use serde_derive::{Deserialize, Serialize};
use crate::{
backend::node::{Metadata, Node, NodeType},
error::RusticResult,
id::Id,
};
#[derive(thiserror::Error, Debug, displaydoc::Display)]
#[non_exhaustive]
pub enum BackendErrorKind {
PathNotAllowed(PathBuf),
}
pub(crate) type BackendResult<T> = Result<T, BackendErrorKind>;
pub const ALL_FILE_TYPES: [FileType; 4] = [
FileType::Key,
FileType::Snapshot,
FileType::Index,
FileType::Pack,
];
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize, Enum, derive_more::Display)]
pub enum FileType {
#[serde(rename = "config")]
Config,
#[serde(rename = "index")]
Index,
#[serde(rename = "key")]
Key,
#[serde(rename = "snapshot")]
Snapshot,
#[serde(rename = "pack")]
Pack,
}
impl FileType {
#[must_use]
pub const fn dirname(self) -> &'static str {
match self {
Self::Config => "config",
Self::Snapshot => "snapshots",
Self::Index => "index",
Self::Key => "keys",
Self::Pack => "data",
}
}
const fn is_cacheable(self) -> bool {
match self {
Self::Config | Self::Key | Self::Pack => false,
Self::Snapshot | Self::Index => true,
}
}
}
pub trait ReadBackend: Send + Sync + 'static {
fn location(&self) -> String;
fn list_with_size(&self, tpe: FileType) -> RusticResult<Vec<(Id, u32)>>;
fn list(&self, tpe: FileType) -> RusticResult<Vec<Id>> {
Ok(self
.list_with_size(tpe)?
.into_iter()
.map(|(id, _)| id)
.collect())
}
fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult<Bytes>;
fn read_partial(
&self,
tpe: FileType,
id: &Id,
cacheable: bool,
offset: u32,
length: u32,
) -> RusticResult<Bytes>;
fn warmup_path(&self, tpe: FileType, id: &Id) -> String;
fn needs_warm_up(&self) -> bool {
false
}
fn warm_up(&self, _tpe: FileType, _id: &Id) -> RusticResult<()> {
Ok(())
}
}
pub trait FindInBackend: ReadBackend {
fn find_starts_with<T: AsRef<str>>(&self, tpe: FileType, vec: &[T]) -> RusticResult<Vec<Id>> {
Id::find_starts_with_from_iter(vec, self.list(tpe)?)
}
fn find_id(&self, tpe: FileType, id: &str) -> RusticResult<Id> {
Ok(self.find_ids(tpe, &[id.to_string()])?.remove(0))
}
fn find_ids<T: AsRef<str>>(&self, tpe: FileType, ids: &[T]) -> RusticResult<Vec<Id>> {
ids.iter()
.map(|id| id.as_ref().parse())
.collect::<RusticResult<Vec<_>>>()
.or_else(|err|{
trace!("no valid IDs given: {err}, searching for ID starting with given strings instead");
self.find_starts_with(tpe, ids)})
}
}
impl<T: ReadBackend> FindInBackend for T {}
pub trait WriteBackend: ReadBackend {
fn create(&self) -> RusticResult<()> {
Ok(())
}
fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()>;
fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()>;
}
#[cfg(test)]
mock! {
pub(crate) Backend {}
impl ReadBackend for Backend{
fn location(&self) -> String;
fn list_with_size(&self, tpe: FileType) -> RusticResult<Vec<(Id, u32)>>;
fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult<Bytes>;
fn read_partial(
&self,
tpe: FileType,
id: &Id,
cacheable: bool,
offset: u32,
length: u32,
) -> RusticResult<Bytes>;
fn warmup_path(&self, tpe: FileType, id: &Id) -> String;
}
impl WriteBackend for Backend {
fn create(&self) -> RusticResult<()>;
fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()>;
fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()>;
}
}
impl WriteBackend for Arc<dyn WriteBackend> {
fn create(&self) -> RusticResult<()> {
self.deref().create()
}
fn write_bytes(&self, tpe: FileType, id: &Id, cacheable: bool, buf: Bytes) -> RusticResult<()> {
self.deref().write_bytes(tpe, id, cacheable, buf)
}
fn remove(&self, tpe: FileType, id: &Id, cacheable: bool) -> RusticResult<()> {
self.deref().remove(tpe, id, cacheable)
}
}
impl ReadBackend for Arc<dyn WriteBackend> {
fn location(&self) -> String {
self.deref().location()
}
fn list_with_size(&self, tpe: FileType) -> RusticResult<Vec<(Id, u32)>> {
self.deref().list_with_size(tpe)
}
fn list(&self, tpe: FileType) -> RusticResult<Vec<Id>> {
self.deref().list(tpe)
}
fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult<Bytes> {
self.deref().read_full(tpe, id)
}
fn read_partial(
&self,
tpe: FileType,
id: &Id,
cacheable: bool,
offset: u32,
length: u32,
) -> RusticResult<Bytes> {
self.deref()
.read_partial(tpe, id, cacheable, offset, length)
}
fn warmup_path(&self, tpe: FileType, id: &Id) -> String {
self.deref().warmup_path(tpe, id)
}
fn needs_warm_up(&self) -> bool {
self.deref().needs_warm_up()
}
fn warm_up(&self, tpe: FileType, id: &Id) -> RusticResult<()> {
self.deref().warm_up(tpe, id)
}
}
impl std::fmt::Debug for dyn WriteBackend {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "WriteBackend{{{}}}", self.location())
}
}
#[derive(Debug, Clone)]
pub struct ReadSourceEntry<O> {
pub path: PathBuf,
pub node: Node,
pub open: Option<O>,
}
impl<O> ReadSourceEntry<O> {
fn from_path(path: PathBuf, open: Option<O>) -> BackendResult<Self> {
let node = Node::new_node(
path.file_name()
.ok_or_else(|| BackendErrorKind::PathNotAllowed(path.clone()))?,
NodeType::File,
Metadata::default(),
);
Ok(Self { path, node, open })
}
}
pub trait ReadSourceOpen {
type Reader: Read + Send + 'static;
fn open(self) -> RusticResult<Self::Reader>;
}
impl<T: Read + Send + 'static> ReadSourceOpen for T {
type Reader = T;
fn open(self) -> RusticResult<Self::Reader> {
Ok(self)
}
}
pub trait ReadSource: Sync + Send {
type Open: ReadSourceOpen;
type Iter: Iterator<Item = RusticResult<ReadSourceEntry<Self::Open>>>;
fn size(&self) -> RusticResult<Option<u64>>;
fn entries(&self) -> Self::Iter;
}
pub trait WriteSource: Clone {
fn create<P: Into<PathBuf>>(&self, path: P, node: Node);
fn set_metadata<P: Into<PathBuf>>(&self, path: P, node: Node);
fn write_at<P: Into<PathBuf>>(&self, path: P, offset: u64, data: Bytes);
}
#[derive(Debug, Clone)]
pub struct RepositoryBackends {
repository: Arc<dyn WriteBackend>,
repo_hot: Option<Arc<dyn WriteBackend>>,
}
impl RepositoryBackends {
pub fn new(repository: Arc<dyn WriteBackend>, repo_hot: Option<Arc<dyn WriteBackend>>) -> Self {
Self {
repository,
repo_hot,
}
}
#[must_use]
pub fn repository(&self) -> Arc<dyn WriteBackend> {
self.repository.clone()
}
#[must_use]
pub fn repo_hot(&self) -> Option<Arc<dyn WriteBackend>> {
self.repo_hot.clone()
}
}