use crate::backup_reason::Reason;
use crate::chunkid::ChunkId;
use crate::fsentry::FilesystemEntry;
use rusqlite::Connection;
use serde::Serialize;
use std::collections::HashMap;
use std::fmt;
use std::path::{Path, PathBuf};
/// Current generation database schema major version.
const SCHEMA_MAJOR: u32 = 0;
/// Current generation database schema minor version.
const SCHEMA_MINOR: u32 = 0;
/// An identifier for a file in a generation.
type FileId = i64;
/// An identifier for a generation.
#[derive(Debug, Clone)]
pub struct GenId {
id: ChunkId,
}
impl GenId {
pub fn from_chunk_id(id: ChunkId) -> Self {
Self { id }
}
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,
}
#[derive(Debug, thiserror::Error)]
pub enum NascentError {
#[error(transparent)]
LocalGenerationError(#[from] LocalGenerationError),
#[error("SQL transaction error: {0}")]
Transaction(rusqlite::Error),
#[error("SQL commit error: {0}")]
Commit(rusqlite::Error),
#[error("Failed to create temporary file: {0}")]
TempFile(#[from] std::io::Error),
}
impl NascentGeneration {
pub fn create
(filename: P) -> Result
where
P: AsRef,
{
let conn = sql::create_db(filename.as_ref())?;
Ok(Self { conn, fileno: 0 })
}
pub fn file_count(&self) -> FileId {
self.fileno
}
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.
///
/// A generation is finished when it's on the server. It can be restored.
#[derive(Debug, Clone)]
pub struct FinishedGeneration {
id: GenId,
ended: String,
}
impl FinishedGeneration {
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(),
}
}
pub fn id(&self) -> &GenId {
&self.id
}
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,
}
#[derive(Debug, thiserror::Error)]
pub enum LocalGenerationError {
#[error("Generation has more than one file with the name {0}")]
TooManyFiles(PathBuf),
#[error("Generation does not have a 'meta' table")]
NoMeta,
#[error("Generation 'meta' table does not have a row {0}")]
NoMetaKey(String),
#[error("Generation 'meta' row {0} has badly formed integer: {1}")]
BadMetaInteger(String, std::num::ParseIntError),
#[error(transparent)]
RusqliteError(#[from] rusqlite::Error),
#[error(transparent)]
SerdeJsonError(#[from] serde_json::Error),
#[error(transparent)]
IoError(#[from] std::io::Error),
}
pub struct BackedUpFile {
fileno: FileId,
entry: FilesystemEntry,
reason: Reason,
}
impl BackedUpFile {
pub fn new(fileno: FileId, entry: FilesystemEntry, reason: &str) -> Self {
let reason = Reason::from(reason);
Self {
fileno,
entry,
reason,
}
}
pub fn fileno(&self) -> FileId {
self.fileno
}
pub fn entry(&self) -> &FilesystemEntry {
&self.entry
}
pub fn reason(&self) -> Reason {
self.reason
}
}
impl LocalGeneration {
pub fn open
(filename: P) -> Result
where
P: AsRef,
{
let conn = sql::open_db(filename.as_ref())?;
Ok(Self { conn })
}
pub fn meta(&self) -> Result {
let map = sql::meta(&self.conn)?;
GenMeta::from(map)
}
pub fn file_count(&self) -> Result {
sql::file_count(&self.conn)
}
pub fn files(&self) -> Result, LocalGenerationError> {
sql::files(&self.conn)
}
pub fn chunkids(
&self,
fileno: FileId,
) -> Result, LocalGenerationError> {
sql::chunkids(&self.conn, fileno)
}
pub fn get_file(
&self,
filename: &Path,
) -> Result