1use camino::Utf8PathBuf;
8
9use crate::cid;
10use crate::crypto::{
11 CommitReader, ContentKey, EncryptedCommit, EncryptedManifest, EncryptedMetadata,
12 EncryptedRepoManifest, EncryptedShard, KeyVault,
13};
14use crate::metadata::{self, MetadataBundle};
15use crate::store::{FsStore, IpfsStore, ObjectStoreExt, RemoteStore};
16use std::sync::Arc;
17use crate::support::ToVoidCid;
18use crate::{Result, VoidError};
19
20pub struct ImportOptions {
22 pub store: FsStore,
24 pub remote: Arc<dyn RemoteStore>,
26 pub commit_cid: String,
28 pub content_key: ContentKey,
30 pub on_shard_progress: Option<Box<dyn Fn(usize, usize)>>,
32}
33
34pub struct ForeignCommit {
36 pub commit: metadata::Commit,
38 pub commit_encrypted: EncryptedCommit,
40 pub reader: CommitReader,
42 pub metadata: MetadataBundle,
44 pub repo_name: Option<String>,
46}
47
48pub fn fetch_published_commit(
53 opts: ImportOptions,
54) -> Result<ForeignCommit> {
55 let commit_cid = cid::parse(&opts.commit_cid)?;
57
58 let commit_encrypted = EncryptedCommit::from_bytes(opts.remote.fetch_raw(&commit_cid)?);
59 opts.store.put_blob(&commit_encrypted)?;
60
61 let vault = KeyVault::from_content_key(opts.content_key);
62 let (commit_plaintext, reader) = CommitReader::open_with_vault(&vault, &commit_encrypted)?;
63 let commit = commit_plaintext.parse()?;
64
65 let metadata_cid = commit.metadata_bundle.to_void_cid()?;
67 let metadata_encrypted = EncryptedMetadata::from_bytes(opts.remote.fetch_raw(&metadata_cid)?);
68 opts.store.put_blob(&metadata_encrypted)?;
69
70 let metadata: MetadataBundle = reader.decrypt_metadata(&metadata_encrypted)?;
71
72 if let Some(ref manifest_cid) = commit.manifest_cid {
74 let mcid = cid::from_bytes(manifest_cid.as_bytes())?;
75 let manifest_encrypted = EncryptedManifest::from_bytes(opts.remote.fetch_raw(&mcid)?);
76 opts.store.put_blob(&manifest_encrypted)?;
77 }
78
79 let total_shards = metadata.shard_map.ranges.iter().filter(|r| r.cid.is_some()).count();
81 let mut fetched = 0usize;
82 for range in &metadata.shard_map.ranges {
83 let shard_cid_typed = match range.cid.as_ref() {
84 Some(c) => c,
85 None => continue,
86 };
87 let shard_cid = cid::from_bytes(shard_cid_typed.as_bytes())?;
88 let shard_encrypted = EncryptedShard::from_bytes(opts.remote.fetch_raw(&shard_cid)?);
89 opts.store.put_blob(&shard_encrypted)?;
90 fetched += 1;
91 if let Some(ref on_progress) = opts.on_shard_progress {
92 on_progress(fetched, total_shards);
93 }
94 }
95
96 let repo_name = extract_repo_name(&commit, &reader, opts.remote.as_ref());
98
99 Ok(ForeignCommit {
100 commit,
101 commit_encrypted,
102 reader,
103 metadata,
104 repo_name,
105 })
106}
107
108pub fn objects_store(void_dir: &std::path::Path) -> Result<FsStore> {
110 let objects_dir = Utf8PathBuf::try_from(void_dir.join("objects"))
111 .map_err(|e| VoidError::Io(std::io::Error::new(
112 std::io::ErrorKind::InvalidInput,
113 format!("invalid objects path: {e}"),
114 )))?;
115 FsStore::new(objects_dir)
116}
117
118fn extract_repo_name(
120 commit: &metadata::Commit,
121 reader: &CommitReader,
122 remote: &dyn RemoteStore,
123) -> Option<String> {
124 let rm_cid_bytes = commit.repo_manifest_cid.as_ref()?;
125 let rm_cid = rm_cid_bytes.to_void_cid().ok()?;
126 let rm_encrypted = EncryptedRepoManifest::from_bytes(remote.fetch_raw(&rm_cid).ok()?);
127 let manifest = reader.decrypt_repo_manifest(&rm_encrypted).ok()?;
128 let rm_json = manifest.to_json().ok()?;
129 let value: serde_json::Value = serde_json::from_slice(&rm_json).ok()?;
130 value
131 .get("repoName")
132 .and_then(|v| v.as_str())
133 .map(String::from)
134}