use std::fs;
use std::io::ErrorKind;
use std::path::PathBuf;
use super::VacuumOptions;
use super::error::VacuumError;
use super::report::{SweepBlocker, SweepSummary, VacuumMode, VacuumReport};
pub fn run(options: &VacuumOptions, execute: bool) -> Result<VacuumReport, VacuumError> {
let (mut report, plan) = super::stats::run(options, VacuumMode::Sweep)?;
if let Some(refusal) = first_refusal(&report) {
return Err(refusal);
}
let deleted_nodes = plan.len();
let deleted_bytes = report.totals.unmarked_bytes;
if execute {
unlink_all(&plan)?;
}
report.sweep = Some(SweepSummary {
executed: execute,
deleted_nodes,
deleted_bytes,
deleted_paths: plan,
});
Ok(report)
}
fn unlink_all(plan: &[PathBuf]) -> Result<(), VacuumError> {
for path in plan {
match fs::remove_file(path) {
Ok(()) => {}
Err(error) if error.kind() == ErrorKind::NotFound => {}
Err(error) => {
return Err(VacuumError::Io {
path: path.clone(),
error,
});
}
}
}
Ok(())
}
fn first_refusal(report: &VacuumReport) -> Option<VacuumError> {
report
.sweep_blockers
.iter()
.min_by_key(|blocker| precedence_rank(blocker))
.map(|blocker| blocker_refusal(report, blocker))
}
const fn precedence_rank(blocker: &SweepBlocker) -> u8 {
match blocker {
SweepBlocker::UnsupportedDurability => 0,
SweepBlocker::ShardBeyondCount { .. } | SweepBlocker::MalformedStoreEntries { .. } => 1,
SweepBlocker::MetadataUnattested | SweepBlocker::StaleManifest { .. } => 2,
SweepBlocker::ManifestEntryUnsettled { .. } => 3,
SweepBlocker::ManifestSourceMissing { .. }
| SweepBlocker::SourceUnreadable { .. }
| SweepBlocker::SuppliedPathMissing { .. } => 4,
}
}
fn blocker_refusal(report: &VacuumReport, blocker: &SweepBlocker) -> VacuumError {
match blocker {
SweepBlocker::UnsupportedDurability => VacuumError::UnsupportedDurability,
SweepBlocker::ShardBeyondCount { id, shard_count } => VacuumError::ShardBeyondCount {
id: *id,
shard_count: *shard_count,
},
SweepBlocker::MalformedStoreEntries { shard_id, .. } => {
malformed_refusal(report, *shard_id)
}
SweepBlocker::MetadataUnattested => VacuumError::MetadataUnattested {
unlisted_canonical: None,
},
SweepBlocker::StaleManifest { unlisted } => VacuumError::MetadataUnattested {
unlisted_canonical: Some(unlisted.clone()),
},
SweepBlocker::ManifestEntryUnsettled { path, state } => {
VacuumError::MetadataSourceUnsettled {
path: path.clone(),
state: state.clone(),
}
}
SweepBlocker::ManifestSourceMissing { path }
| SweepBlocker::SourceUnreadable { path, .. }
| SweepBlocker::SuppliedPathMissing { path } => {
VacuumError::MetadataPathMissing { path: path.clone() }
}
}
}
fn malformed_refusal(report: &VacuumReport, shard_id: usize) -> VacuumError {
let store = report
.data_dir
.join(format!("shard-{shard_id}"))
.join("store");
let (path, reason) = report
.shards
.iter()
.find(|shard| shard.shard_id == shard_id)
.and_then(|shard| shard.malformed.first())
.map_or_else(
|| (store.clone(), "malformed store entry".to_owned()),
|entry| (entry.path.clone(), entry.reason.clone()),
);
VacuumError::UnexpectedLayout {
store,
path,
reason,
}
}