use serde::{Deserialize, Serialize};
use flint_store::{GenerationStamps, StoreError};
use super::sync::SyncReport;
use super::{inbox, manifest, LeanError, LeanResult, Syncer};
const PULLED_FROM: &str = "reader.json";
#[derive(Debug, Clone, Default, PartialEq, Eq, Serialize, Deserialize)]
struct PulledFrom {
manifest_etag: Option<String>,
inbox_etag: Option<String>,
}
#[derive(Debug, Default)]
pub struct Pull {
pub observed_seq: Option<u64>,
pub observed_etag: Option<String>,
pub sync: Option<SyncReport>,
}
impl Syncer {
pub fn refuse_if_read(&self, what: &str) -> LeanResult<()> {
if self.cfg.access.is_read() {
return Err(LeanError::Refused(format!(
"{what}: this syncer has read access (FLINT_SYNC_ACCESS=read) and never writes to \
the bucket"
)));
}
Ok(())
}
pub async fn reader_pull(&mut self) -> LeanResult<Pull> {
let ib = inbox::load(self.store.as_ref(), &self.cfg).await?;
let (observed_seq, manifest_etag) =
match manifest::load_pointer(self.store.as_ref(), &self.cfg).await? {
Some(p) => (Some(p.pointer.seq), Some(p.etag)),
None => match self.store.head(&self.cfg.manifest_key()).await {
Ok(meta) => {
(GenerationStamps::from_meta(&meta.meta).map(|s| s.generation), Some(meta.etag))
}
Err(StoreError::NotFound(_)) => (None, None),
Err(e) => return Err(e.into()),
},
};
let now = PulledFrom { manifest_etag: manifest_etag.clone(), inbox_etag: ib.etag };
let mut out = Pull { observed_seq, observed_etag: manifest_etag, sync: None };
let skip = self.load_pulled_from().as_ref() == Some(&now) && !self.still_owed()?;
self.trace("pull", serde_json::json!({"seq": observed_seq, "skipped": skip}));
if skip {
return Ok(out);
}
out.sync = Some(self.sync().await?);
self.save_pulled_from(&now)?;
Ok(out)
}
fn still_owed(&self) -> LeanResult<bool> {
let b = self.state.load_baseline()?;
if b.derived_etag.is_none() {
return Ok(true);
}
Ok(!b.skipped.iter().all(|path| {
super::barrier::check_contained(&self.cfg.root, path).is_ok()
&& super::barrier::local_dirty(&self.cfg.root.join(path), b.entries.get(path))
}))
}
fn load_pulled_from(&self) -> Option<PulledFrom> {
let bytes = std::fs::read(self.cfg.state_dir().join(PULLED_FROM)).ok()?;
serde_json::from_slice(&bytes).ok()
}
fn save_pulled_from(&self, p: &PulledFrom) -> LeanResult<()> {
let bytes =
serde_json::to_vec(p).map_err(|e| LeanError::State(format!("reader memo: {e}")))?;
let path = self.cfg.state_dir().join(PULLED_FROM);
super::safefs::check_parent(&path)?;
super::safefs::write_via_tmp(&path, &path.with_extension("tmp"), &bytes, None).map(|_| ())
}
}