use std::path::PathBuf;
use anyhow::Context;
use bytes::Bytes;
use iroh_base::rpc::RpcError;
use serde::{Deserialize, Serialize};
use tracing::trace;
use crate::{
format::collection::Collection,
store::{BaoBlobSize, ExportFormat, ExportMode, MapEntry, Store as BaoStore},
util::progress::{IdGenerator, ProgressSender},
Hash,
};
pub async fn export<D: BaoStore>(
db: &D,
hash: Hash,
outpath: PathBuf,
format: ExportFormat,
mode: ExportMode,
progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
) -> anyhow::Result<()> {
match format {
ExportFormat::Blob => export_blob(db, hash, outpath, mode, progress).await,
ExportFormat::Collection => export_collection(db, hash, outpath, mode, progress).await,
}
}
pub async fn export_collection<D: BaoStore>(
db: &D,
hash: Hash,
outpath: PathBuf,
mode: ExportMode,
progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
) -> anyhow::Result<()> {
tokio::fs::create_dir_all(&outpath).await?;
let collection = Collection::load_db(db, &hash).await?;
for (name, hash) in collection.into_iter() {
#[allow(clippy::needless_borrow)]
let path = outpath.join(pathbuf_from_name(&name));
export_blob(db, hash, path, mode, progress.clone()).await?;
}
Ok(())
}
pub async fn export_blob<D: BaoStore>(
db: &D,
hash: Hash,
outpath: PathBuf,
mode: ExportMode,
progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
) -> anyhow::Result<()> {
if let Some(parent) = outpath.parent() {
tokio::fs::create_dir_all(parent).await?;
}
trace!("exporting blob {} to {}", hash, outpath.display());
let id = progress.new_id();
let entry = db.get(&hash).await?.context("entry not there")?;
progress
.send(ExportProgress::Found {
id,
hash,
outpath: outpath.clone(),
size: entry.size(),
meta: None,
})
.await?;
let progress1 = progress.clone();
db.export(
hash,
outpath,
mode,
Box::new(move |offset| Ok(progress1.try_send(ExportProgress::Progress { id, offset })?)),
)
.await?;
progress.send(ExportProgress::Done { id }).await?;
Ok(())
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum ExportProgress {
Found {
id: u64,
hash: Hash,
size: BaoBlobSize,
outpath: PathBuf,
meta: Option<Bytes>,
},
Progress {
id: u64,
offset: u64,
},
Done {
id: u64,
},
AllDone,
Abort(RpcError),
}
fn pathbuf_from_name(name: &str) -> PathBuf {
let mut path = PathBuf::new();
for part in name.split('/') {
path.push(part);
}
path
}