use std::collections::BTreeMap;
use std::sync::Arc;
use crate::Hlc;
use crate::codec::keys::{KeyKind, LogicalKey};
use crate::codec::snapshot::{self, SnapshotReader, VersionVector};
use crate::database::{Inner, Origin, SyncReport};
use crate::error::{Error, ErrorKind, Result};
use crate::model::{DbId, DeviceId};
use crate::remote::PutOutcome;
const DAY_MS: u64 = 24 * 60 * 60 * 1000;
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct CompactReport {
pub segments: Vec<String>,
pub snapshots: Vec<String>,
}
pub(crate) async fn create_snapshot(inner: &Arc<Inner>, db_id: DbId) -> Result<String> {
for _ in 0..3 {
let device = inner.device();
let key = super::sync_key(inner).await?;
let (hlc, vv, bytes) = inner
.store(move |s| {
let hlc = s.tick()?;
let compress = s.compress()?;
let (vv, bytes) = s.build_snapshot(db_id, hlc, compress, key.as_ref())?;
Ok((hlc, vv, bytes))
})
.await?;
let key = LogicalKey::snapshot(db_id, device, hlc).to_string();
inner.store(move |s| s.begin_own_snapshot(hlc, &vv)).await?;
match inner.remote.put_new(&key, &bytes, None).await? {
PutOutcome::Created | PutOutcome::AlreadyExists { same_content: true } => {
inner.store(move |s| s.confirm_own_snapshot(hlc)).await?;
return Ok(key);
}
PutOutcome::AlreadyExists {
same_content: false,
} => {
inner.store(move |s| s.abandon_own_snapshot(hlc)).await?;
super::fork(inner, db_id, &mut SyncReport::default()).await?;
}
}
}
Err(Error::new(
ErrorKind::RemoteInconsistent,
"snapshot keys keep colliding",
))
}
type Candidate = (String, DeviceId, Hlc);
async fn newest_snapshots(inner: &Inner, db_id: DbId) -> Result<Vec<Candidate>> {
let mut newest: BTreeMap<DeviceId, (Hlc, String)> = BTreeMap::new();
for e in inner.remote.list(&format!("{db_id}/snapshots/")).await? {
let Some(key) = LogicalKey::parse(&e.key) else {
continue;
};
if let KeyKind::Snapshot(dev, hlc) = key.kind
&& key.db_id == db_id
&& newest.get(&dev).is_none_or(|(h, _)| hlc > *h)
{
newest.insert(dev, (hlc, e.key));
}
}
Ok(newest.into_iter().map(|(d, (h, k))| (k, d, h)).collect())
}
async fn fetch(
inner: &Arc<Inner>,
db_id: DbId,
(key, device, hlc): &Candidate,
report: &mut SyncReport,
) -> Result<Option<(Vec<u8>, VersionVector)>> {
let Some(bytes) = inner.remote.get(key).await? else {
return Ok(None);
};
report.snapshots_down += 1;
let expected = snapshot::Expected {
db_id,
device_id: *device,
hlc: *hlc,
};
let data_key = super::sync_key(inner).await?;
match SnapshotReader::open_with(&bytes, &expected, data_key.as_ref()) {
Ok(r) => {
let vv = r.header().vv.clone();
Ok(Some((bytes, vv)))
}
Err(e) if e.kind() == ErrorKind::Corrupt => {
report.skipped.push(format!("{key}: {}", e.message()));
Ok(None)
}
Err(e) => Err(e),
}
}
async fn apply(
inner: &Arc<Inner>,
db_id: DbId,
key: String,
device: DeviceId,
hlc: Hlc,
bytes: Vec<u8>,
report: &mut SyncReport,
) -> Result<()> {
let data_key = super::sync_key(inner).await?;
let changed = inner
.store(move |s| {
let expected = snapshot::Expected {
db_id,
device_id: device,
hlc,
};
let reader = SnapshotReader::open_with(&bytes, &expected, data_key.as_ref())?;
s.apply_snapshot(&key, &reader)
})
.await?;
if let Some(changed) = changed {
report.snapshots_applied += 1;
report.changed_docs += changed.len();
for (c, d) in changed {
inner.emit(c, d, Origin::Remote);
}
}
Ok(())
}
pub(crate) async fn join_from_snapshot(
inner: &Arc<Inner>,
db_id: DbId,
report: &mut SyncReport,
) -> Result<()> {
let mut best: Option<((u64, Hlc), Candidate, Vec<u8>)> = None;
for cand in newest_snapshots(inner, db_id).await? {
if let Some((bytes, vv)) = fetch(inner, db_id, &cand, report).await? {
let rank = (vv.values().sum::<u64>(), cand.2);
if best.as_ref().is_none_or(|(r, _, _)| rank > *r) {
best = Some((rank, cand, bytes));
}
}
}
if let Some((_, (key, device, hlc), bytes)) = best {
apply(inner, db_id, key, device, hlc, bytes, report).await?;
}
Ok(())
}
pub(crate) async fn fill_gaps(
inner: &Arc<Inner>,
db_id: DbId,
report: &mut SyncReport,
) -> Result<()> {
let gaps = inner.store(|s| s.gaps()).await?;
if gaps.is_empty() {
return Ok(());
}
for cand in newest_snapshots(inner, db_id).await? {
let key = cand.0.clone();
if cand.1 == inner.device() || inner.store(move |s| s.is_snapshot_applied(&key)).await? {
continue;
}
let Some((bytes, vv)) = fetch(inner, db_id, &cand, report).await? else {
continue;
};
if gaps
.iter()
.any(|(d, need)| vv.get(d).is_some_and(|s| s >= need))
{
let (key, device, hlc) = cand;
apply(inner, db_id, key, device, hlc, bytes, report).await?;
}
}
report.gaps = inner.store(|s| s.gaps()).await?;
Ok(())
}
pub(crate) async fn auto(inner: &Arc<Inner>, db_id: DbId, report: &mut SyncReport) -> Result<()> {
let since = inner.store(|s| s.since_snapshot()).await?;
if since.segments >= inner.policy.every_segments || since.bytes >= inner.policy.every_bytes {
create_snapshot(inner, db_id).await?;
report.snapshots_up += 1;
}
let done = compact(inner, db_id, false).await?;
report.deleted_files += done.segments.len() + done.snapshots.len();
Ok(())
}
pub(crate) async fn prune_device(
inner: &Arc<Inner>,
db_id: DbId,
target: DeviceId,
) -> Result<Vec<String>> {
if target == inner.device() {
return Err(Error::invalid_input(
"cannot prune this device; use compact",
));
}
let watermark = inner.store(move |s| s.watermark(target)).await?;
if watermark == 0 {
return Ok(Vec::new());
}
create_snapshot(inner, db_id).await?;
let mut seqs: Vec<(u64, String)> = inner
.remote
.list(&format!("{db_id}/devices/{target}/seg-"))
.await?
.into_iter()
.filter_map(|e| match LogicalKey::parse(&e.key)?.kind {
KeyKind::Segment(_, seq) => Some((seq, e.key)),
_ => None,
})
.collect();
seqs.sort();
let newest = seqs.last().map(|(s, _)| *s);
let mut deleted = Vec::new();
for (seq, key) in seqs {
if seq <= watermark && Some(seq) != newest {
inner.remote.delete(&key).await?;
deleted.push(key);
}
}
Ok(deleted)
}
pub(crate) async fn compact(
inner: &Arc<Inner>,
db_id: DbId,
dry_run: bool,
) -> Result<CompactReport> {
let me = inner.device();
let (own, now, grace_days, done_upto, uploaded) = inner
.store(|s| {
Ok((
s.own_snapshots()?,
s.wall_ms(),
s.grace_period_days()?,
s.compacted_upto()?,
s.uploaded_watermark()?,
))
})
.await?;
let grace = u64::from(grace_days) * DAY_MS;
let old_enough = |h: &Hlc| h.physical_ms().saturating_add(grace) <= now;
let mut report = CompactReport::default();
let covered = own
.iter()
.filter(|(h, _)| old_enough(h))
.filter_map(|(_, vv)| vv.get(&me).copied())
.max()
.unwrap_or(0)
.min(uploaded.saturating_sub(1));
if covered > done_upto {
for seq in done_upto + 1..=covered {
let key = LogicalKey::segment(db_id, me, seq).to_string();
if !dry_run {
inner.remote.delete(&key).await?;
}
report.segments.push(key);
}
if !dry_run {
inner
.store(move |s| {
s.prune_outbox(covered)?;
s.set_compacted_upto(covered)
})
.await?;
}
}
let dominates = |a: &VersionVector, b: &VersionVector| {
b.iter().all(|(d, s)| a.get(d).is_some_and(|x| x >= s))
};
for (i, (h, vv)) in own.iter().enumerate() {
let superseded = own[i + 1..]
.iter()
.any(|(nh, nvv)| old_enough(nh) && dominates(nvv, vv));
if superseded {
let key = LogicalKey::snapshot(db_id, me, *h).to_string();
if !dry_run {
inner.remote.delete(&key).await?;
let h = *h;
inner.store(move |s| s.forget_own_snapshot(h)).await?;
}
report.snapshots.push(key);
}
}
Ok(report)
}