use std::time::Duration;
use super::lease::{self, ClaimOutcome};
use super::{LeanError, Syncer};
pub fn env_list(name: &str) -> Option<Vec<String>> {
let raw = std::env::var(name).ok()?;
let v: Vec<String> =
raw.split(',').map(|e| e.trim().to_string()).filter(|e| !e.is_empty()).collect();
if v.is_empty() {
return None;
}
Some(v)
}
pub enum Step {
Checkout,
Barrier,
Sync,
RecoverStaged,
Rescope(Option<Vec<String>>),
}
impl Step {
pub fn installs_nothing_in_the_bucket(&self) -> bool {
match self {
Step::Checkout => true,
Step::Barrier | Step::Sync | Step::RecoverStaged | Step::Rescope(_) => false,
}
}
}
pub async fn run_verb(sc: &mut Syncer, step: Step) -> Result<(), LeanError> {
if step.installs_nothing_in_the_bucket() {
read_only_then(sc, step).await
} else {
claim_then(sc, step).await
}
}
pub async fn claim(sc: &mut Syncer) -> Result<(), LeanError> {
lease::verify_claim(sc).await?;
lease::warn_if_prefix_is_shared(sc).await;
let mut answered_owed = false;
loop {
match lease::claim_step(sc).await? {
ClaimOutcome::Claimed(lease) => {
eprintln!("flint-sync: holding epoch {}", lease.epoch);
return Ok(());
}
ClaimOutcome::Waiting { quiet_polls } => {
if !answered_owed {
answered_owed = true;
match sc.refuse_what_this_incarnation_can_never_honor().await {
Ok(true) => eprintln!(
"flint-sync: a foreign holder stands and this incarnation owes an \
ack it can never honor — refused-fenced written, marker fenced"
),
Ok(false) => {}
Err(e) => {
answered_owed = false;
eprintln!("flint-sync: could not settle owed acks while waiting: {e}");
}
}
}
eprintln!("flint-sync: waiting on the standing lease (quiet {quiet_polls}/6)");
tokio::time::sleep(Duration::from_secs(10)).await;
}
}
}
}
pub async fn claim_then(sc: &mut Syncer, step: Step) -> Result<(), LeanError> {
claim(sc).await?;
let out = run_step(sc, step).await;
let _ = lease::release(sc).await;
out
}
pub async fn read_only_then(sc: &mut Syncer, step: Step) -> Result<(), LeanError> {
lease::verify_claim(sc).await?;
lease::warn_if_prefix_is_shared(sc).await;
run_step(sc, step).await
}
pub async fn run_step(sc: &mut Syncer, step: Step) -> Result<(), LeanError> {
{
match step {
Step::Checkout => {
let r = sc.checkout_scoped(env_list("FLINT_SYNC_CHECKOUT_SCOPE")).await?;
eprintln!(
"flint-sync: checkout — {} materialized, {} present, live-tree={}",
r.materialized, r.skipped_present, r.resumed_live_tree
);
if let Some(sc) = &r.scope {
eprintln!(
"flint-sync: checkout SCOPED to {:?} — {} citations declined",
sc, r.out_of_scope
);
}
eprintln!(
"flint-sync: phase manifest={:.3}s fetch={:.3}s commit={:.3}s bytes={} ranged={}",
r.manifest_secs, r.fetch_secs, r.commit_secs, r.bytes, r.ranged
);
}
Step::Rescope(target) => {
let r = sc.rescope(target).await?;
eprintln!(
"flint-sync: rescope to {:?} — uncited {}, unlinked {}, materialised {} \
({} bytes), already held {}",
r.target, r.uncited, r.unlinked, r.materialized, r.bytes, r.already_held
);
if !r.kept_dirty.is_empty() {
eprintln!(
"flint-sync: rescope KEPT {} path(s) with unpublished changes: {:?} — \
publish them, then rescope again to drop them",
r.kept_dirty.len(),
r.kept_dirty
);
}
}
Step::Barrier => {
let r = sc.run_barrier().await?;
eprintln!(
"flint-sync: barrier seq={:?} up={} del={} parked={} consumed={}",
r.seq,
r.uploaded.len(),
r.deleted.len(),
r.parked.len(),
r.consumed
);
}
Step::Sync => {
let r = sc.sync().await?;
println!("{}", serde_json::to_string_pretty(&r).unwrap());
}
Step::RecoverStaged => {
let r = sc.recover_staged().await?;
eprintln!(
"flint-sync: recover-staged seq={:?} recited={} dangling={} unrecoverable={}",
r.seq,
r.recited.len(),
r.dangling.len(),
r.unrecoverable.len()
);
for p in &r.recited {
eprintln!("flint-sync: recited {p}");
}
for p in &r.unrecoverable {
eprintln!("flint-sync: UNRECOVERABLE {p}");
}
if !r.unrecoverable.is_empty() {
return Err(LeanError::State(format!(
"{} path(s) have no surviving version to cite",
r.unrecoverable.len()
)));
}
}
}
Ok(())
}
}