use crate::{
execution::BackupExecutionJournal,
journal::DownloadJournal,
manifest::DeploymentBackupManifest,
persistence::{
BackupExecutionIntegrityReport, BackupIntegrityReport, PersistenceError,
integrity::{verify_execution_integrity, verify_layout_integrity},
json::{create_json_durable, read_json, write_json_durable},
},
plan::BackupPlan,
};
use std::path::{Path, PathBuf};
const MANIFEST_FILE_NAME: &str = "deployment-backup-manifest.json";
const BACKUP_PLAN_FILE_NAME: &str = "backup-plan.json";
const JOURNAL_FILE_NAME: &str = "download-journal.json";
const EXECUTION_JOURNAL_FILE_NAME: &str = "backup-execution-journal.json";
#[derive(Clone, Debug)]
pub struct BackupLayout {
root: PathBuf,
}
impl BackupLayout {
#[must_use]
pub const fn new(root: PathBuf) -> Self {
Self { root }
}
#[must_use]
pub fn root(&self) -> &Path {
&self.root
}
#[must_use]
pub fn manifest_path(&self) -> PathBuf {
self.root.join(MANIFEST_FILE_NAME)
}
#[must_use]
pub fn backup_plan_path(&self) -> PathBuf {
self.root.join(BACKUP_PLAN_FILE_NAME)
}
#[must_use]
pub fn journal_path(&self) -> PathBuf {
self.root.join(JOURNAL_FILE_NAME)
}
#[must_use]
pub fn execution_journal_path(&self) -> PathBuf {
self.root.join(EXECUTION_JOURNAL_FILE_NAME)
}
pub fn publish_manifest(
&self,
manifest: &DeploymentBackupManifest,
) -> Result<(), PersistenceError> {
manifest.validate()?;
let path = self.manifest_path();
if path.exists() {
return self.require_exact_manifest(manifest);
}
match create_json_durable(&path, manifest) {
Ok(()) => Ok(()),
Err(PersistenceError::Io(error))
if error.kind() == std::io::ErrorKind::AlreadyExists =>
{
self.require_exact_manifest(manifest)
}
Err(error) => Err(error),
}
}
pub fn read_manifest(&self) -> Result<DeploymentBackupManifest, PersistenceError> {
let manifest = read_json(&self.manifest_path())?;
DeploymentBackupManifest::validate(&manifest)?;
Ok(manifest)
}
fn require_exact_manifest(
&self,
expected: &DeploymentBackupManifest,
) -> Result<(), PersistenceError> {
let path = self.manifest_path();
let metadata = std::fs::symlink_metadata(&path)?;
if metadata.file_type().is_symlink() || !metadata.is_file() {
return Err(PersistenceError::ManifestConflict {
path: path.display().to_string(),
});
}
let actual = self.read_manifest()?;
if serde_json::to_vec(&actual)? == serde_json::to_vec(expected)? {
std::fs::File::open(&path)?.sync_all()?;
if let Some(parent) = path.parent() {
std::fs::File::open(parent)?.sync_all()?;
}
Ok(())
} else {
Err(PersistenceError::ManifestConflict {
path: path.display().to_string(),
})
}
}
#[cfg(test)]
pub(crate) fn publish_manifest_at_barriers(
&self,
manifest: &DeploymentBackupManifest,
before_publication: impl FnMut(),
after_directory_sync: impl FnMut(),
) -> Result<(), PersistenceError> {
use crate::persistence::json::create_json_durable_at_barriers;
manifest.validate()?;
create_json_durable_at_barriers(
&self.manifest_path(),
manifest,
before_publication,
after_directory_sync,
)
}
pub fn write_backup_plan(&self, plan: &BackupPlan) -> Result<(), PersistenceError> {
plan.validate()?;
write_json_durable(&self.backup_plan_path(), plan)
}
pub fn read_backup_plan(&self) -> Result<BackupPlan, PersistenceError> {
let plan = read_json(&self.backup_plan_path())?;
BackupPlan::validate(&plan)?;
Ok(plan)
}
pub fn write_journal(&self, journal: &DownloadJournal) -> Result<(), PersistenceError> {
journal.validate()?;
write_json_durable(&self.journal_path(), journal)
}
pub fn read_journal(&self) -> Result<DownloadJournal, PersistenceError> {
let journal = read_json(&self.journal_path())?;
DownloadJournal::validate(&journal)?;
Ok(journal)
}
pub fn write_execution_journal(
&self,
journal: &BackupExecutionJournal,
) -> Result<(), PersistenceError> {
journal.validate()?;
write_json_durable(&self.execution_journal_path(), journal)
}
pub fn read_execution_journal(&self) -> Result<BackupExecutionJournal, PersistenceError> {
let journal = read_json(&self.execution_journal_path())?;
BackupExecutionJournal::validate(&journal)?;
Ok(journal)
}
pub fn verify_integrity(&self) -> Result<BackupIntegrityReport, PersistenceError> {
let manifest = self.read_manifest()?;
let journal = self.read_journal()?;
verify_layout_integrity(self, &manifest, &journal)
}
pub fn verify_execution_integrity(
&self,
) -> Result<BackupExecutionIntegrityReport, PersistenceError> {
let plan = self.read_backup_plan()?;
let journal = self.read_execution_journal()?;
verify_execution_integrity(&plan, &journal)
}
}