Skip to main content
← dvs documentation Rust API reference

dvs/backends/
mod.rs

1use std::path::{Path, PathBuf};
2
3use anyhow::Result;
4use serde::{Deserialize, Serialize};
5use uuid::Uuid;
6
7use crate::Hashes;
8use crate::audit::AuditEntry;
9use crate::config::Compression;
10use crate::paths::ProjectPath;
11
12pub mod local;
13pub mod server;
14
15use local::LocalBackend;
16use server::ServerBackend;
17
18/// Common fields needed for storing a file across backends
19pub struct StoreRequest<'a> {
20    pub hashes: &'a Hashes,
21    pub source: &'a Path,
22    /// The compression the caller would like. It could be overriden eg in the case of a dvs
23    /// server backend if the project was init with a different alg
24    pub compression: Compression,
25    pub path: ProjectPath,
26    pub operation_id: Uuid,
27    pub message: Option<&'a str>,
28    pub on_bytes: Option<&'a (dyn Fn(u64) + Send + Sync)>,
29}
30
31impl<'a> StoreRequest<'a> {
32    pub fn new_local(
33        hashes: &'a Hashes,
34        source: &'a Path,
35        path: ProjectPath,
36        compression: Compression,
37    ) -> Self {
38        Self {
39            hashes,
40            source,
41            compression,
42            path,
43            operation_id: Uuid::nil(),
44            message: None,
45            on_bytes: None,
46        }
47    }
48}
49
50/// What the backend actually did with the file.
51#[derive(Debug)]
52pub struct StoreResult {
53    /// Size of the stored blob, after compression.
54    pub stored_size: u64,
55    /// The compression that was actually applied.
56    pub compression: Compression,
57    /// Whether this call created the blob, or it was already there (dedup).
58    pub created: bool,
59}
60
61/// Common fields needed for retrieving a file across backends
62pub struct RetrieveRequest<'a> {
63    pub hashes: &'a Hashes,
64    pub target: &'a Path,
65    pub compression: Compression,
66    pub path: ProjectPath,
67    pub operation_id: Uuid,
68    pub on_bytes: Option<&'a (dyn Fn(u64) + Send + Sync)>,
69}
70
71impl<'a> RetrieveRequest<'a> {
72    pub fn new_local(
73        hashes: &'a Hashes,
74        target: &'a Path,
75        path: ProjectPath,
76        compression: Compression,
77    ) -> Self {
78        Self {
79            hashes,
80            target,
81            compression,
82            path,
83            operation_id: Uuid::nil(),
84            on_bytes: None,
85        }
86    }
87}
88
89#[derive(Debug, Serialize, Deserialize, Clone, PartialEq)]
90#[serde(untagged)]
91pub enum Backend {
92    Local(LocalBackend),
93    Server(ServerBackend),
94}
95
96impl Backend {
97    /// Initialize the backend storage
98    /// Idempotent: returns `true` if the backend was already initialized.
99    pub fn init(&self, compression: Compression) -> Result<bool> {
100        match self {
101            Backend::Local(b) => b.init(compression),
102            Backend::Server(s) => s.init(compression),
103        }
104    }
105
106    /// Check that the current user can access the backend.
107    /// Called once before batch operations so auth/permission problems fail
108    /// fast with a single error instead of once per file.
109    pub fn check_access(&self) -> Result<()> {
110        match self {
111            // The filesystem permissions are the access control for local storage.
112            Backend::Local(_) => Ok(()),
113            Backend::Server(s) => s.check_access(),
114        }
115    }
116
117    /// Store file to backend under `req.hashes`, compressing it.
118    /// The source is passed uncompressed, the hash we send is only for (optional) verification by
119    /// a server
120    pub fn store(&self, req: StoreRequest<'_>) -> Result<StoreResult> {
121        match self {
122            Backend::Local(b) => b.store(req),
123            Backend::Server(s) => s.store(req),
124        }
125    }
126
127    /// Retrieve content by hash to target path, optionally decompressing.
128    /// Returns true if the file was copied to the target path.
129    pub fn retrieve(&self, req: RetrieveRequest<'_>) -> Result<bool> {
130        match self {
131            Backend::Local(b) => b.retrieve(req),
132            Backend::Server(s) => s.retrieve(req),
133        }
134    }
135
136    /// Check if the file exists in the backend
137    pub fn exists(&self, hash: &Hashes) -> Result<bool> {
138        match self {
139            Backend::Local(b) => b.exists(hash),
140            Backend::Server(s) => s.exists(hash),
141        }
142    }
143
144    /// Remove content by hash (for rollback). Best-effort, may silently fail.
145    pub fn remove(&self, hash: &Hashes) -> Result<()> {
146        match self {
147            Backend::Local(b) => b.remove(hash),
148            Backend::Server(s) => s.remove(hash),
149        }
150    }
151
152    /// Read the whole audit file, filtered by the given file paths.
153    /// If `files` is empty, return the full audit log
154    pub fn get_audit_entries(&self, files: &[PathBuf]) -> Result<Vec<AuditEntry>> {
155        match self {
156            Backend::Local(b) => b.get_audit_entries(files),
157            Backend::Server(s) => s.get_audit_entries(files),
158        }
159    }
160}