//! Backup generations of various kinds.
use crate::backup_reason::Reason;
use crate::chunkid::ChunkId;
use crate::dbgen::{FileId, SCHEMA_MAJOR, SCHEMA_MINOR};
use crate::fsentry::FilesystemEntry;
use rusqlite::Connection;
use serde::Serialize;
use std::collections::HashMap;
use std::fmt;
use std::path::{Path, PathBuf};
/// An identifier for a generation.
#[derive(Debug, Clone)]
pub struct GenId {
id: ChunkId,
}
impl GenId {
/// Create a generation identifier from a chunk identifier.
pub fn from_chunk_id(id: ChunkId) -> Self {
Self { id }
}
/// Convert a generation identifier into a chunk identifier.
pub fn as_chunk_id(&self) -> &ChunkId {
&self.id
}
}
impl fmt::Display for GenId {
/// Format an identifier for display.
///
/// The output can be parsed to re-created an identical identifier.
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(f, "{}", self.id)
}
}
/// A nascent backup generation.
///
/// A nascent generation is one that is being prepared. It isn't
/// finished yet, and it's not actually on the server until the upload
/// of its generation chunk.
pub struct NascentGeneration {
conn: Connection,
fileno: FileId,
}
/// Possible errors from nascent backup generations.
#[derive(Debug, thiserror::Error)]
pub enum NascentError {
/// Error backing up a backup root.
#[error("Could not back up a backup root directory: {0}: {1}")]
BackupRootFailed(PathBuf, crate::fsiter::FsIterError),
/// Error using a local generation.
#[error(transparent)]
LocalGenerationError(#[from] LocalGenerationError),
/// Error from an SQL transaction.
#[error("SQL transaction error: {0}")]
Transaction(rusqlite::Error),
/// Error from committing an SQL transaction.
#[error("SQL commit error: {0}")]
Commit(rusqlite::Error),
/// Error creating a temporary file.
#[error("Failed to create temporary file: {0}")]
TempFile(#[from] std::io::Error),
}
impl NascentGeneration {
/// Create a new nascent generation.
pub fn create
(filename: P) -> Result
where
P: AsRef,
{
let conn = sql::create_db(filename.as_ref())?;
Ok(Self { conn, fileno: 0 })
}
/// How many files are there now in the nascent generation?
pub fn file_count(&self) -> FileId {
self.fileno
}
/// Insert a new file system entry into a nascent generation.
pub fn insert(
&mut self,
e: FilesystemEntry,
ids: &[ChunkId],
reason: Reason,
is_cachedir_tag: bool,
) -> Result<(), NascentError> {
let t = self.conn.transaction().map_err(NascentError::Transaction)?;
self.fileno += 1;
sql::insert_one(&t, e, self.fileno, ids, reason, is_cachedir_tag)?;
t.commit().map_err(NascentError::Commit)?;
Ok(())
}
}
/// A finished generation on the server.
///
/// A generation is finished when it's on the server. It can be
/// fetched so it can be used as a [`LocalGeneration`].
#[derive(Debug, Clone)]
pub struct FinishedGeneration {
id: GenId,
ended: String,
}
impl FinishedGeneration {
/// Create a new finished generation.
pub fn new(id: &str, ended: &str) -> Self {
let id = GenId::from_chunk_id(id.parse().unwrap()); // this never fails
Self {
id,
ended: ended.to_string(),
}
}
/// Get the generation's identifier.
pub fn id(&self) -> &GenId {
&self.id
}
/// When was generation finished?
pub fn ended(&self) -> &str {
&self.ended
}
}
/// A local representation of a finished generation.
///
/// This is for querying an existing generation, and other read-only
/// operations.
pub struct LocalGeneration {
conn: Connection,
}
/// Possible errors from using local generations.
#[derive(Debug, thiserror::Error)]
pub enum LocalGenerationError {
/// Duplicate file names.
#[error("Generation has more than one file with the name {0}")]
TooManyFiles(PathBuf),
/// No 'meta' table in generation.
#[error("Generation does not have a 'meta' table")]
NoMeta,
/// Missing from from 'meta' table.
#[error("Generation 'meta' table does not have a row {0}")]
NoMetaKey(String),
/// Bad data in 'meta' table.
#[error("Generation 'meta' row {0} has badly formed integer: {1}")]
BadMetaInteger(String, std::num::ParseIntError),
/// Local generation uses a schema version that this version of
/// Obnam isn't compatible with.
#[error("Backup is not compatible with this version of Obnam: {0}.{1}")]
Incompatible(u32, u32),
/// Error from SQL.
#[error(transparent)]
RusqliteError(#[from] rusqlite::Error),
/// Error from JSON.
#[error(transparent)]
SerdeJsonError(#[from] serde_json::Error),
/// Error from I/O.
#[error(transparent)]
IoError(#[from] std::io::Error),
}
/// A backed up file in a local generation.
pub struct BackedUpFile {
fileno: FileId,
entry: FilesystemEntry,
reason: Reason,
}
impl BackedUpFile {
/// Create a new `BackedUpFile`.
pub fn new(fileno: FileId, entry: FilesystemEntry, reason: &str) -> Self {
let reason = Reason::from(reason);
Self {
fileno,
entry,
reason,
}
}
/// Return id for file in its local generation.
pub fn fileno(&self) -> FileId {
self.fileno
}
/// Return file system entry for file.
pub fn entry(&self) -> &FilesystemEntry {
&self.entry
}
/// Return reason why file is in its local generation.
pub fn reason(&self) -> Reason {
self.reason
}
}
impl LocalGeneration {
fn new(conn: Connection) -> Self {
Self { conn }
}
/// Open a local file as a local generation.
pub fn open
(filename: P) -> Result
where
P: AsRef,
{
let conn = sql::open_db(filename.as_ref())?;
let gen = Self::new(conn);
let schema = gen.meta()?.schema_version();
let our_schema = SchemaVersion::new(SCHEMA_MAJOR, SCHEMA_MINOR);
if !our_schema.is_compatible_with(&schema) {
return Err(LocalGenerationError::Incompatible(
schema.major,
schema.minor,
));
}
Ok(gen)
}
/// Return generation metadata for local generation.
pub fn meta(&self) -> Result {
let map = sql::meta(&self.conn)?;
GenMeta::from(map)
}
/// How many files are there in the local generation?
pub fn file_count(&self) -> Result {
sql::file_count(&self.conn)
}
/// Return all files in the local generation.
pub fn files(&self) -> Result, LocalGenerationError> {
sql::files(&self.conn)
}
/// Return ids for all chunks in local generation.
pub fn chunkids(
&self,
fileno: FileId,
) -> Result, LocalGenerationError> {
sql::chunkids(&self.conn, fileno)
}
/// Return entry for a file, given its pathname.
pub fn get_file(
&self,
filename: &Path,
) -> Result