iroh_bytes/
export.rs

1//! Functions to export data from a store
2
3use 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
18/// Export a hash to the local file system.
19///
20/// This exports a single hash, or a collection `recursive` is true, from the `db` store to the
21/// local filesystem. Depending on `mode` the data is either copied or reflinked (if possible).
22///
23/// Progress is reported as [`ExportProgress`] through a [`ProgressSender`]. Note that the
24/// [`ExportProgress::AllDone`] event is not emitted from here, but left to an upper layer to send,
25/// if desired.
26pub 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
40/// Export all entries of a collection, recursively, to files on the local fileystem.
41pub 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
58/// Export a single blob to a file on the local fileystem.
59pub 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/// Progress events for an export operation
94#[derive(Debug, Clone, Serialize, Deserialize)]
95pub enum ExportProgress {
96    /// The download part is done for this id, we are now exporting the data
97    /// to the specified out path.
98    Found {
99        /// Unique id of the entry.
100        id: u64,
101        /// The hash of the entry.
102        hash: Hash,
103        /// The size of the entry in bytes.
104        size: BaoBlobSize,
105        /// The path to the file where the data is exported.
106        outpath: PathBuf,
107        /// Operation-specific metadata.
108        meta: Option<Bytes>,
109    },
110    /// We have made progress exporting the data.
111    ///
112    /// This is only sent for large blobs.
113    Progress {
114        /// Unique id of the entry that is being exported.
115        id: u64,
116        /// The offset of the progress, in bytes.
117        offset: u64,
118    },
119    /// We finished exporting a blob
120    Done {
121        /// Unique id of the entry that is being exported.
122        id: u64,
123    },
124    /// We are done with the whole operation.
125    AllDone,
126    /// We got an error and need to abort.
127    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}