Documentation Index
Fetch the complete documentation index at: https://mintlify.com/rustic-rs/rustic_core/llms.txt
Use this file to discover all available pages before exploring further.
The backend system in rustic is built around three core traits that define how data is read from and written to repositories. These traits enable rustic to support multiple storage backends with a consistent interface.
ReadBackend Trait
The ReadBackend trait defines read-only operations for accessing repository data. All backends must implement this trait.
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>>;
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(()) }
}
Key Methods
location
Returns the location identifier of the backend (e.g., "local:/path/to/repo", "rest:https://example.com/repo").
let location = backend.location();
println!("Repository location: {}", location);
list_with_size
Lists all files of a given type with their sizes. This is the primary listing method that other methods build upon.
use rustic_core::FileType;
let snapshots = backend.list_with_size(FileType::Snapshot)?;
for (id, size) in snapshots {
println!("Snapshot {}: {} bytes", id, size);
}
list
Lists all files of a given type (without sizes). This is a convenience method that calls list_with_size internally.
let index_ids = backend.list(FileType::Index)?;
read_full
Reads the complete contents of a file.
let config_data = backend.read_full(FileType::Config, &Id::default())?;
read_partial
Reads a portion of a file, useful for efficient data access in large pack files.
// Read 1024 bytes starting at offset 512
let partial_data = backend.read_partial(
FileType::Pack,
&pack_id,
true, // cacheable
512, // offset
1024, // length
)?;
warmup_path
Returns a backend-specific path string that can be used for warming up cold storage.
let path = backend.warmup_path(FileType::Pack, &pack_id);
// Use path with external warm-up command
WriteBackend Trait
The WriteBackend trait extends ReadBackend with write and delete operations.
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<()>;
}
Key Methods
create
Initializes a new repository structure in the backend.
backend.create()?;
// Creates directory structure: keys/, snapshots/, index/, data/, config
write_bytes
Writes data to a file in the repository.
use bytes::Bytes;
let data = Bytes::from("file contents");
backend.write_bytes(
FileType::Snapshot,
&snapshot_id,
true, // cacheable
data,
)?;
remove
Deletes a file from the repository.
backend.remove(FileType::Pack, &pack_id, false)?;
FindInBackend Trait
The FindInBackend trait provides helper methods for finding files by ID prefix. It’s automatically implemented for all types that implement ReadBackend.
pub trait FindInBackend: ReadBackend {
fn find_starts_with<T: AsRef<str>>(
&self,
tpe: FileType,
vec: &[T]
) -> RusticResult<Vec<Id>>;
fn find_id(&self, tpe: FileType, id: &str) -> RusticResult<Id>;
fn find_ids<T: AsRef<str>>(
&self,
tpe: FileType,
ids: &[T]
) -> RusticResult<Vec<Id>>;
}
Usage Examples
// Find snapshot by partial ID
let snapshot_id = backend.find_id(FileType::Snapshot, "a1b2c3")?;
// Find multiple snapshots
let ids = backend.find_ids(
FileType::Snapshot,
&["a1b2c3", "d4e5f6"]
)?;
FileType Enum
The FileType enum identifies different types of files stored in a repository:
pub enum FileType {
Config, // Repository configuration
Index, // Pack file indices
Key, // Encryption keys
Snapshot, // Snapshot metadata
Pack, // Data pack files
}
Each file type has its own directory:
let dir = FileType::Pack.dirname(); // "data"
let dir = FileType::Snapshot.dirname(); // "snapshots"
let dir = FileType::Index.dirname(); // "index"
let dir = FileType::Key.dirname(); // "keys"
Implementing a Custom Backend
To create a custom backend, implement the ReadBackend trait (and optionally WriteBackend):
use rustic_core::{ReadBackend, WriteBackend, FileType, Id, RusticResult};
use bytes::Bytes;
use std::collections::HashMap;
pub struct CustomBackend {
data: HashMap<(FileType, Id), Vec<u8>>,
}
impl ReadBackend for CustomBackend {
fn location(&self) -> String {
"custom://my-backend".to_string()
}
fn list_with_size(&self, tpe: FileType) -> RusticResult<Vec<(Id, u32)>> {
let items: Vec<_> = self.data
.iter()
.filter(|((t, _), _)| *t == tpe)
.map(|((_, id), data)| (*id, data.len() as u32))
.collect();
Ok(items)
}
fn read_full(&self, tpe: FileType, id: &Id) -> RusticResult<Bytes> {
self.data
.get(&(tpe, *id))
.map(|data| Bytes::from(data.clone()))
.ok_or_else(|| /* error */)
}
fn read_partial(
&self,
tpe: FileType,
id: &Id,
_cacheable: bool,
offset: u32,
length: u32,
) -> RusticResult<Bytes> {
let data = self.data.get(&(tpe, *id))
.ok_or_else(|| /* error */)?;
let start = offset as usize;
let end = start + length as usize;
Ok(Bytes::from(data[start..end].to_vec()))
}
fn warmup_path(&self, tpe: FileType, id: &Id) -> String {
format!("{}/{}", tpe.dirname(), id)
}
}
impl WriteBackend for CustomBackend {
fn write_bytes(
&self,
tpe: FileType,
id: &Id,
_cacheable: bool,
buf: Bytes,
) -> RusticResult<()> {
// Implementation
Ok(())
}
fn remove(
&self,
tpe: FileType,
id: &Id,
_cacheable: bool,
) -> RusticResult<()> {
// Implementation
Ok(())
}
}
Thread Safety
All backend implementations must be:
Send: Can be transferred between threads
Sync: Can be safely shared between threads
'static: No non-static references
This allows backends to be used in concurrent operations:
use std::sync::Arc;
use rustic_core::WriteBackend;
let backend: Arc<dyn WriteBackend> = Arc::new(my_backend);
// Can be cloned and used across threads
let backend_clone = backend.clone();