iroh_blobs/
export.rs

1//! Functions to export data from a store
2
3use std::path::PathBuf;
4
5use anyhow::Context;
6use bytes::Bytes;
7use serde::{Deserialize, Serialize};
8use tracing::trace;
9
10use crate::{
11    format::collection::Collection,
12    store::{BaoBlobSize, ExportFormat, ExportMode, MapEntry, Store as BaoStore},
13    util::progress::{IdGenerator, ProgressSender},
14    Hash,
15};
16
17/// Export a hash to the local file system.
18///
19/// This exports a single hash, or a collection `recursive` is true, from the `db` store to the
20/// local filesystem. Depending on `mode` the data is either copied or reflinked (if possible).
21///
22/// Progress is reported as [`ExportProgress`] through a [`ProgressSender`]. Note that the
23/// [`ExportProgress::AllDone`] event is not emitted from here, but left to an upper layer to send,
24/// if desired.
25pub async fn export<D: BaoStore>(
26    db: &D,
27    hash: Hash,
28    outpath: PathBuf,
29    format: ExportFormat,
30    mode: ExportMode,
31    progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
32) -> anyhow::Result<()> {
33    match format {
34        ExportFormat::Blob => export_blob(db, hash, outpath, mode, progress).await,
35        ExportFormat::Collection => export_collection(db, hash, outpath, mode, progress).await,
36    }
37}
38
39/// Export all entries of a collection, recursively, to files on the local filesystem.
40pub async fn export_collection<D: BaoStore>(
41    db: &D,
42    hash: Hash,
43    outpath: PathBuf,
44    mode: ExportMode,
45    progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
46) -> anyhow::Result<()> {
47    tokio::fs::create_dir_all(&outpath).await?;
48    let collection = Collection::load_db(db, &hash).await?;
49    for (name, hash) in collection.into_iter() {
50        #[allow(clippy::needless_borrow)]
51        let path = outpath.join(pathbuf_from_name(&name));
52        export_blob(db, hash, path, mode, progress.clone()).await?;
53    }
54    Ok(())
55}
56
57/// Export a single blob to a file on the local filesystem.
58pub async fn export_blob<D: BaoStore>(
59    db: &D,
60    hash: Hash,
61    outpath: PathBuf,
62    mode: ExportMode,
63    progress: impl ProgressSender<Msg = ExportProgress> + IdGenerator,
64) -> anyhow::Result<()> {
65    if let Some(parent) = outpath.parent() {
66        tokio::fs::create_dir_all(parent).await?;
67    }
68    trace!("exporting blob {} to {}", hash, outpath.display());
69    let id = progress.new_id();
70    let entry = db.get(&hash).await?.context("entry not there")?;
71    progress
72        .send(ExportProgress::Found {
73            id,
74            hash,
75            outpath: outpath.clone(),
76            size: entry.size(),
77            meta: None,
78        })
79        .await?;
80    let progress1 = progress.clone();
81    db.export(
82        hash,
83        outpath,
84        mode,
85        Box::new(move |offset| Ok(progress1.try_send(ExportProgress::Progress { id, offset })?)),
86    )
87    .await?;
88    progress.send(ExportProgress::Done { id }).await?;
89    Ok(())
90}
91
92/// Progress events for an export operation
93#[derive(Debug, Clone, Serialize, Deserialize)]
94pub enum ExportProgress {
95    /// The download part is done for this id, we are now exporting the data
96    /// to the specified out path.
97    Found {
98        /// Unique id of the entry.
99        id: u64,
100        /// The hash of the entry.
101        hash: Hash,
102        /// The size of the entry in bytes.
103        size: BaoBlobSize,
104        /// The path to the file where the data is exported.
105        outpath: PathBuf,
106        /// Operation-specific metadata.
107        meta: Option<Bytes>,
108    },
109    /// We have made progress exporting the data.
110    ///
111    /// This is only sent for large blobs.
112    Progress {
113        /// Unique id of the entry that is being exported.
114        id: u64,
115        /// The offset of the progress, in bytes.
116        offset: u64,
117    },
118    /// We finished exporting a blob
119    Done {
120        /// Unique id of the entry that is being exported.
121        id: u64,
122    },
123    /// We are done with the whole operation.
124    AllDone,
125    /// We got an error and need to abort.
126    Abort(serde_error::Error),
127}
128
129fn pathbuf_from_name(name: &str) -> PathBuf {
130    let mut path = PathBuf::new();
131    for part in name.split('/') {
132        path.push(part);
133    }
134    path
135}