62 lines
2.3 KiB
Rust
62 lines
2.3 KiB
Rust
//! `rad lfs fetch` — plumbing command invoked by the Git LFS custom
|
|
//! transfer agent (`rad-lfs-transfer`) to download one LFS object's
|
|
//! content from the local IPFS (Kubo) node, decrypting it first if the
|
|
//! repository is private.
|
|
|
|
use std::path::PathBuf;
|
|
|
|
use anyhow::{Context as _, anyhow};
|
|
|
|
use crate::ipfs;
|
|
use crate::lfs_crypto;
|
|
use crate::terminal as term;
|
|
|
|
pub fn run(oid: String, size: i64, out: PathBuf, ctx: impl term::Context) -> anyhow::Result<()> {
|
|
let profile = ctx.profile()?;
|
|
let (repo, rid) = radicle::rad::cwd()
|
|
.context("`rad lfs fetch` must be run inside a Radicle repository working copy")?;
|
|
|
|
let pointer_text =
|
|
format!("version https://git-lfs.github.com/spec/v1\noid sha256:{oid}\nsize {size}\n");
|
|
let blob_oid = repo
|
|
.blob(pointer_text.as_bytes())
|
|
.context("failed to write LFS pointer blob")?;
|
|
|
|
let candidates = lfs_crypto::find_envelopes(&repo, blob_oid)
|
|
.with_context(|| format!("failed to read LFS notes for oid {oid}"))?;
|
|
if candidates.is_empty() {
|
|
anyhow::bail!(
|
|
"no CID recorded for oid {oid}; the peer that has this object needs to push its \
|
|
{} ref",
|
|
lfs_crypto::NOTES_REF
|
|
);
|
|
}
|
|
|
|
ipfs::check_daemon()?;
|
|
|
|
// Almost always exactly one candidate. More than one only happens if
|
|
// two peers independently encrypted byte-identical content (same
|
|
// oid/size) producing different ciphertext each time -- try each
|
|
// until one actually decrypts for us.
|
|
let mut last_err = None;
|
|
for envelope in candidates {
|
|
let result = match &envelope.enc {
|
|
None => ipfs::cat(&envelope.cid),
|
|
Some(enc) => ipfs::cat(&envelope.cid).and_then(|ciphertext| {
|
|
let signer = lfs_crypto::load_signer(&profile)?;
|
|
lfs_crypto::decrypt_with(&ciphertext, enc, &signer, profile.did(), &rid, &oid)
|
|
}),
|
|
};
|
|
match result {
|
|
Ok(bytes) => {
|
|
std::fs::write(&out, bytes)
|
|
.with_context(|| format!("failed to write `{}`", out.display()))?;
|
|
return Ok(());
|
|
}
|
|
Err(err) => last_err = Some(err),
|
|
}
|
|
}
|
|
|
|
Err(last_err.unwrap_or_else(|| anyhow!("no CID recorded for oid {oid}")))
|
|
}
|