use std::collections::HashMap;
use super::inbox::{self, InboxEntry};
use super::{manifest, LeanError, LeanResult, Syncer};
pub const UNTRACKED_AUTHOR: &str = "untracked-sweep";
impl Syncer {
pub async fn track_untracked(&self, now: u64) -> LeanResult<Vec<String>> {
let Some(loaded) = manifest::load(self.store.as_ref(), &self.cfg).await? else {
return Ok(vec![]);
};
let cited: HashMap<&str, (&str, &str)> = loaded
.manifest
.entries
.iter()
.map(|(p, e)| (e.key.as_str(), (p.as_str(), e.etag.as_str())))
.collect();
let doc = inbox::load(self.store.as_ref(), &self.cfg).await?.doc;
let same = |a: &str, b: &str| a.trim_matches('"') == b.trim_matches('"');
let listed = self.store.list(&format!("{}/files/", self.cfg.prefix)).await?;
let mut tracked = vec![];
for obj in listed {
let Some(&(path, cited_etag)) = cited.get(obj.key.as_str()) else { continue };
if same(cited_etag, &obj.etag) {
continue;
}
if doc.entries.iter().any(|e| e.path == path && same(&e.etag, &obj.etag)) {
continue;
}
let Some(modified) = obj.last_modified_unix else { continue };
if now.saturating_sub(modified) < self.cfg.untracked_grace_secs {
continue;
}
let entry = InboxEntry {
path: path.to_string(),
etag: obj.etag.clone(),
author: UNTRACKED_AUTHOR.into(),
added_unix: now,
crc64_b64: None,
};
match inbox::gateway_append(self.store.as_ref(), &self.cfg, entry).await {
Ok(()) => {
self.trace("untracked", serde_json::json!({"path": path, "etag": obj.etag, "cited": cited_etag}));
tracked.push(path.to_string());
}
Err(LeanError::State(m)) if m.contains("window open") => break,
Err(e) => return Err(e),
}
}
Ok(tracked)
}
pub async fn sweep_untracked_if_due(&self, now: u64) {
if self.cfg.untracked_sweep_secs == 0 || self.cfg.access.is_read() {
return;
}
let last = match self.state.load_untracked_sweep_at() {
Ok(t) => t,
Err(e) => {
eprintln!("flint-sync: untracked sweep clock unreadable (skipping): {e}");
return;
}
};
if last == 0 {
let _ = self.state.save_untracked_sweep_at(now);
return;
}
if now.saturating_sub(last) < self.cfg.untracked_sweep_secs {
return;
}
match self.track_untracked(now).await {
Ok(paths) => {
if !paths.is_empty() {
eprintln!(
"flint-sync: tracked {} upload(s) no commit cited, left by a lost writer: {paths:?}",
paths.len()
);
}
let _ = self.state.save_untracked_sweep_at(now);
}
Err(e) => eprintln!("flint-sync: untracked sweep failed (retrying next tick): {e}"),
}
}
}