use futures_util::StreamExt;
use super::keyspace::Keyspace;
use crate::error::Error;
use crate::namespace::Namespace;
const COMPLETE: &str = ".refs/.complete";
const CONCURRENCY: usize = 16;
pub(crate) fn key(ns: &Namespace, oid: &str) -> String {
format!(".refs/{oid}/{}/{}", ns.org(), ns.repo())
}
fn prefix(oid: &str) -> String {
format!(".refs/{oid}/")
}
pub(crate) async fn ready(keys: &Keyspace) -> bool {
keys.head(COMPLETE).await.is_ok()
}
pub(crate) async fn write(keys: &Keyspace, ns: &Namespace, oid: &str) -> Result<(), Error> {
keys.put(&key(ns, oid), reqwest::Body::from(Vec::new()), 0)
.await
}
pub(crate) async fn claimed_by_another(keys: &Keyspace, ns: &Namespace, oid: &str) -> bool {
let ours = key(ns, oid);
match keys.keys(&prefix(oid)).await {
Ok(holders) => holders.iter().any(|holder| holder != &ours),
Err(error) => {
tracing::warn!(
%error,
oid,
"the claim index could not be read, so the object is kept"
);
true
}
}
}
fn from_marker(marker: &str) -> Option<String> {
let mut parts = marker.split('/');
let org = parts.next()?;
let repo = parts.next()?;
let aa = parts.next()?;
let bb = parts.next()?;
let oid = parts.next()?;
if parts.next().is_some() || crate::storage::LocalStore::validate_oid(oid).is_err() {
return None;
}
(oid.starts_with(aa) && oid[2..].starts_with(bb) && !org.is_empty() && !repo.is_empty())
.then(|| format!(".refs/{oid}/{org}/{repo}"))
}
pub(crate) async fn backfill(keys: &Keyspace, markers: &[String]) -> Result<(), Error> {
let refs: Vec<String> = markers.iter().filter_map(|key| from_marker(key)).collect();
tracing::info!(
count = refs.len(),
"building the claim index for a bucket that predates it"
);
let mut writes = futures_util::stream::iter(refs.into_iter().map(|key| {
let keys = keys.clone();
async move { keys.put(&key, reqwest::Body::from(Vec::new()), 0).await }
}))
.buffer_unordered(CONCURRENCY);
while let Some(written) = writes.next().await {
written?;
}
keys.put(COMPLETE, reqwest::Body::from(Vec::new()), 0).await
}