syncweb_core/node/
blob_store.rs1use bytes::Bytes;
2use iroh::{EndpointAddr, address_lookup::memory::MemoryLookup, endpoint::Endpoint};
3use iroh_blobs::{
4 BlobFormat, BlobsProtocol, Hash,
5 api::{
6 Store as BlobApi,
7 blobs::{AddPathOptions, ImportMode},
8 },
9 ticket::BlobTicket,
10};
11use n0_future::StreamExt;
12use std::path::Path;
13
14use crate::error::{Result, SyncwebError};
15
16#[derive(Clone)]
17pub struct BlobStore {
18 store: BlobApi,
19 address_lookup: MemoryLookup,
20}
21
22impl BlobStore {
23 #[must_use]
24 pub fn new(protocol: &BlobsProtocol) -> Self {
25 Self::new_with_address_lookup(protocol, MemoryLookup::new())
26 }
27
28 #[must_use]
29 pub fn new_with_address_lookup(protocol: &BlobsProtocol, address_lookup: MemoryLookup) -> Self {
30 Self {
31 store: protocol.store().clone(),
32 address_lookup,
33 }
34 }
35
36 #[must_use]
37 pub const fn inner(&self) -> &BlobApi {
38 &self.store
39 }
40
41 pub async fn add_bytes(&self, data: impl AsRef<[u8]>) -> Result<Hash> {
45 Ok(self
46 .store
47 .add_bytes(Bytes::copy_from_slice(data.as_ref()))
48 .await
49 .map_err(|error| SyncwebError::operation("failed to add blob bytes", error))?
50 .hash)
51 }
52
53 pub async fn add_file(&self, path: impl AsRef<Path>) -> Result<Hash> {
57 Ok(self
58 .store
59 .add_path(path)
60 .await
61 .map_err(|error| SyncwebError::operation("failed to add blob file", error))?
62 .hash)
63 }
64
65 pub async fn add_file_ref(&self, path: impl AsRef<Path>) -> Result<Hash> {
75 Ok(self
76 .store
77 .add_path_with_opts(AddPathOptions {
78 path: path.as_ref().to_owned(),
79 mode: ImportMode::TryReference,
80 format: BlobFormat::Raw,
81 })
82 .await
83 .map_err(|error| SyncwebError::operation("failed to add blob file (reference)", error))?
84 .hash)
85 }
86
87 pub async fn export_to_path(&self, hash: Hash, destination: impl AsRef<Path>) -> Result<u64> {
93 self.store
94 .export(hash, destination)
95 .await
96 .map_err(|error| SyncwebError::operation("failed to export blob", error))
97 }
98
99 pub async fn has(&self, hash: Hash) -> Result<bool> {
103 self.store
104 .has(hash)
105 .await
106 .map_err(|error| SyncwebError::operation("failed to query blob store", error))
107 }
108
109 pub async fn get(&self, hash: Hash) -> Result<Bytes> {
113 self.store
114 .get_bytes(hash)
115 .await
116 .map_err(|error| SyncwebError::operation("failed to read blob", error))
117 }
118
119 #[must_use]
120 pub fn ticket(&self, endpoint: &Endpoint, hash: Hash) -> BlobTicket {
121 self.ticket_for_addr(endpoint.addr(), hash)
122 }
123
124 #[must_use]
125 pub fn ticket_for_addr(&self, addr: EndpointAddr, hash: Hash) -> BlobTicket {
126 BlobTicket::new(addr, hash, iroh_blobs::BlobFormat::Raw)
127 }
128
129 pub async fn pin(&self, name: impl AsRef<str>, hash: Hash) -> Result<()> {
135 self.store
136 .tags()
137 .set(name.as_ref(), hash)
138 .await
139 .map_err(|error| SyncwebError::operation("failed to pin blob", error))
140 }
141
142 pub async fn unpin(&self, name: impl AsRef<str>) -> Result<()> {
148 self.store
149 .tags()
150 .delete(name.as_ref())
151 .await
152 .map(|_deleted| ())
153 .map_err(|error| SyncwebError::operation("failed to unpin blob", error))
154 }
155
156 pub async fn is_pinned(&self, name: impl AsRef<str>, hash: Hash) -> Result<bool> {
160 self.store
161 .tags()
162 .get(name.as_ref())
163 .await
164 .map(|tag_info| tag_info.is_some_and(|tag| tag.hash == hash))
165 .map_err(|error| SyncwebError::operation("failed to query blob pin", error))
166 }
167
168 pub async fn list_hashes(&self) -> Result<Vec<Hash>> {
174 self.store
175 .blobs()
176 .list()
177 .hashes()
178 .await
179 .map_err(|error| SyncwebError::operation("failed to list blobs", error))
180 }
181
182 pub async fn list_pins(&self, prefix: impl AsRef<[u8]>) -> Result<Vec<(String, Hash)>> {
188 let mut tags = self
189 .store
190 .tags()
191 .list_prefix(prefix)
192 .await
193 .map_err(|error| SyncwebError::operation("failed to list blob pins", error))?;
194 let mut pins = Vec::new();
195 while let Some(tag_result) = tags.next().await {
196 let tag = tag_result.map_err(|error| SyncwebError::operation("failed to read blob pin", error))?;
197 let name = String::from_utf8(tag.name.as_ref().to_vec())
198 .map_err(|error| SyncwebError::operation("blob pin name is not UTF-8", error))?;
199 pins.push((name, tag.hash));
200 }
201 Ok(pins)
202 }
203
204 pub async fn fetch(&self, endpoint: &Endpoint, ticket: &BlobTicket) -> Result<()> {
208 self.address_lookup.add_endpoint_info(ticket.addr().clone());
209 self.store
210 .downloader(endpoint)
211 .download(ticket.hash_and_format(), [ticket.addr().id])
212 .await
213 .map_err(|error| SyncwebError::operation("failed to fetch blob", error))?;
214 Ok(())
215 }
216}