use std::collections::HashMap;
use std::path::Path;
use trusty_common::log_drain::{
LogDestination, ObjectStoreDestination, prunable_candidates, prune_confirmed,
};
use crate::core::trusty_tools_config::ResolvedLogDrain;
use crate::session_manager::RetentionDebounce;
use super::{DrainOutcome, LogDrainDestinationStatus};
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
struct DestinationIdentity {
cache_namespace: String,
target_key: String,
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub struct PruneCandidate {
destination_id: DestinationIdentity,
relative_file: String,
}
pub async fn apply(
plan: &ResolvedLogDrain,
state_dir: &Path,
statuses: &mut [LogDrainDestinationStatus],
debounce: &mut RetentionDebounce<PruneCandidate>,
) {
let mut all_candidates = Vec::new();
let mut connections: HashMap<DestinationIdentity, (usize, ObjectStoreDestination)> =
HashMap::new();
for (index, group) in plan.destinations.iter().enumerate() {
if statuses[index].outcome == DrainOutcome::Failed {
continue;
}
let dest = match ObjectStoreDestination::connect(&group.destination).await {
Ok(dest) => dest,
Err(_) => continue,
};
let destination_id = DestinationIdentity {
cache_namespace: dest.cache_namespace().to_string(),
target_key: group.target.key_prefix(),
};
if let Ok(files) =
prunable_candidates(&dest, &group.target, state_dir, plan.prune_retention).await
{
all_candidates.extend(files.into_iter().map(|relative_file| PruneCandidate {
destination_id: destination_id.clone(),
relative_file,
}));
}
connections.insert(destination_id, (index, dest));
}
let confirmed = debounce.confirm(&all_candidates);
if confirmed.is_empty() {
return;
}
let mut by_destination: HashMap<DestinationIdentity, Vec<String>> = HashMap::new();
for candidate in confirmed {
by_destination
.entry(candidate.destination_id)
.or_default()
.push(candidate.relative_file);
}
for (destination_id, files) in by_destination {
let Some((index, dest)) = connections.get(&destination_id) else {
continue;
};
let group = &plan.destinations[*index];
match prune_confirmed(dest, &group.target, state_dir, &files).await {
Ok(outcome) => {
if let Some(refusal) = &outcome.refused {
statuses[*index].apply_prune_refusal(refusal);
} else {
statuses[*index].apply_prune_outcome(outcome.pruned, &outcome.errors);
}
}
Err(e) => statuses[*index]
.apply_prune_outcome(0, &[("<manifest>".to_string(), e.to_string())]),
}
}
}
#[cfg(test)]
#[path = "log_drain_prune_tests.rs"]
mod tests;