1use std::path::PathBuf;
4
5use anyhow::Context;
6use bytes::Bytes;
7use iroh_base::rpc::RpcError;
8use serde::{Deserialize, Serialize};
9use tracing::trace;
10
11use crate::{
12 format::collection::Collection,
13 store::{BaoBlobSize, ExportFormat, ExportMode, MapEntry, Store as BaoStore},
14 util::progress::{IdGenerator, ProgressSender},
15 Hash,
16};
17
18pub async fn export<D: BaoStore>(
27 db: &D,
28 hash: Hash,
29 outpath: PathBuf,
30 format: ExportFormat,
31 mode: ExportMode,
32 progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
33) -> anyhow::Result<()> {
34 match format {
35 ExportFormat::Blob => export_blob(db, hash, outpath, mode, progress).await,
36 ExportFormat::Collection => export_collection(db, hash, outpath, mode, progress).await,
37 }
38}
39
40pub async fn export_collection<D: BaoStore>(
42 db: &D,
43 hash: Hash,
44 outpath: PathBuf,
45 mode: ExportMode,
46 progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
47) -> anyhow::Result<()> {
48 tokio::fs::create_dir_all(&outpath).await?;
49 let collection = Collection::load(db, &hash).await?;
50 for (name, hash) in collection.into_iter() {
51 #[allow(clippy::needless_borrow)]
52 let path = outpath.join(pathbuf_from_name(&name));
53 export_blob(db, hash, path, mode, progress.clone()).await?;
54 }
55 Ok(())
56}
57
58pub async fn export_blob<D: BaoStore>(
60 db: &D,
61 hash: Hash,
62 outpath: PathBuf,
63 mode: ExportMode,
64 progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
65) -> anyhow::Result<()> {
66 if let Some(parent) = outpath.parent() {
67 tokio::fs::create_dir_all(parent).await?;
68 }
69 trace!("exporting blob {} to {}", hash, outpath.display());
70 let id = progress.new_id();
71 let entry = db.get(&hash).await?.context("entry not there")?;
72 progress
73 .send(ExportProgress::Found {
74 id,
75 hash,
76 outpath: outpath.clone(),
77 size: entry.size(),
78 meta: None,
79 })
80 .await?;
81 let progress1 = progress.clone();
82 db.export(
83 hash,
84 outpath,
85 mode,
86 Box::new(move |offset| Ok(progress1.try_send(ExportProgress::Progress { id, offset })?)),
87 )
88 .await?;
89 progress.send(ExportProgress::Done { id }).await?;
90 Ok(())
91}
92
93#[derive(Debug, Clone, Serialize, Deserialize)]
95pub enum ExportProgress {
96 Found {
99 id: u64,
101 hash: Hash,
103 size: BaoBlobSize,
105 outpath: PathBuf,
107 meta: Option<Bytes>,
109 },
110 Progress {
114 id: u64,
116 offset: u64,
118 },
119 Done {
121 id: u64,
123 },
124 AllDone,
126 Abort(RpcError),
128}
129
130fn pathbuf_from_name(name: &str) -> PathBuf {
131 let mut path = PathBuf::new();
132 for part in name.split('/') {
133 path.push(part);
134 }
135 path
136}