create-sia-app 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (112) hide show
  1. package/dist/index.d.ts +1 -0
  2. package/dist/index.js +179 -0
  3. package/package.json +29 -0
  4. package/template/CLAUDE.md +160 -0
  5. package/template/README.md +102 -0
  6. package/template/_gitignore +5 -0
  7. package/template/biome.json +40 -0
  8. package/template/index.html +13 -0
  9. package/template/package.json +30 -0
  10. package/template/rust/README.md +16 -0
  11. package/template/rust/sia-sdk-rs/.changeset/added_cancel_function_to_cancel_inflight_packed_uploads.md +6 -0
  12. package/template/rust/sia-sdk-rs/.changeset/check_if_we_have_enough_hosts_prior_to_encoding_in_upload_slabs.md +16 -0
  13. package/template/rust/sia-sdk-rs/.changeset/fix_slab_length_in_packed_object.md +5 -0
  14. package/template/rust/sia-sdk-rs/.changeset/fix_upload_racing_race_conditon.md +13 -0
  15. package/template/rust/sia-sdk-rs/.changeset/improved_parallelism_of_packed_uploads.md +5 -0
  16. package/template/rust/sia-sdk-rs/.changeset/progress_callback_will_now_be_called_as_expected_for_packed_uploads.md +5 -0
  17. package/template/rust/sia-sdk-rs/.github/dependabot.yml +10 -0
  18. package/template/rust/sia-sdk-rs/.github/workflows/main.yml +36 -0
  19. package/template/rust/sia-sdk-rs/.github/workflows/prepare-release.yml +34 -0
  20. package/template/rust/sia-sdk-rs/.github/workflows/release.yml +30 -0
  21. package/template/rust/sia-sdk-rs/.rustfmt.toml +4 -0
  22. package/template/rust/sia-sdk-rs/Cargo.lock +4127 -0
  23. package/template/rust/sia-sdk-rs/Cargo.toml +3 -0
  24. package/template/rust/sia-sdk-rs/LICENSE +21 -0
  25. package/template/rust/sia-sdk-rs/README.md +30 -0
  26. package/template/rust/sia-sdk-rs/indexd/CHANGELOG.md +79 -0
  27. package/template/rust/sia-sdk-rs/indexd/Cargo.toml +79 -0
  28. package/template/rust/sia-sdk-rs/indexd/benches/upload.rs +258 -0
  29. package/template/rust/sia-sdk-rs/indexd/src/app_client.rs +1710 -0
  30. package/template/rust/sia-sdk-rs/indexd/src/builder.rs +354 -0
  31. package/template/rust/sia-sdk-rs/indexd/src/download.rs +379 -0
  32. package/template/rust/sia-sdk-rs/indexd/src/hosts.rs +659 -0
  33. package/template/rust/sia-sdk-rs/indexd/src/lib.rs +827 -0
  34. package/template/rust/sia-sdk-rs/indexd/src/mock.rs +162 -0
  35. package/template/rust/sia-sdk-rs/indexd/src/object_encryption.rs +125 -0
  36. package/template/rust/sia-sdk-rs/indexd/src/quic.rs +575 -0
  37. package/template/rust/sia-sdk-rs/indexd/src/rhp4.rs +52 -0
  38. package/template/rust/sia-sdk-rs/indexd/src/slabs.rs +497 -0
  39. package/template/rust/sia-sdk-rs/indexd/src/upload.rs +629 -0
  40. package/template/rust/sia-sdk-rs/indexd/src/wasm_time.rs +41 -0
  41. package/template/rust/sia-sdk-rs/indexd/src/web_transport.rs +398 -0
  42. package/template/rust/sia-sdk-rs/indexd_ffi/CHANGELOG.md +76 -0
  43. package/template/rust/sia-sdk-rs/indexd_ffi/Cargo.toml +47 -0
  44. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/README.md +10 -0
  45. package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/example.py +130 -0
  46. package/template/rust/sia-sdk-rs/indexd_ffi/src/bin/uniffi-bindgen.rs +3 -0
  47. package/template/rust/sia-sdk-rs/indexd_ffi/src/builder.rs +377 -0
  48. package/template/rust/sia-sdk-rs/indexd_ffi/src/io.rs +155 -0
  49. package/template/rust/sia-sdk-rs/indexd_ffi/src/lib.rs +1039 -0
  50. package/template/rust/sia-sdk-rs/indexd_ffi/src/logging.rs +58 -0
  51. package/template/rust/sia-sdk-rs/indexd_ffi/src/tls.rs +23 -0
  52. package/template/rust/sia-sdk-rs/indexd_wasm/Cargo.toml +33 -0
  53. package/template/rust/sia-sdk-rs/indexd_wasm/src/lib.rs +818 -0
  54. package/template/rust/sia-sdk-rs/knope.toml +54 -0
  55. package/template/rust/sia-sdk-rs/sia_derive/CHANGELOG.md +38 -0
  56. package/template/rust/sia-sdk-rs/sia_derive/Cargo.toml +19 -0
  57. package/template/rust/sia-sdk-rs/sia_derive/src/lib.rs +278 -0
  58. package/template/rust/sia-sdk-rs/sia_sdk/CHANGELOG.md +91 -0
  59. package/template/rust/sia-sdk-rs/sia_sdk/Cargo.toml +59 -0
  60. package/template/rust/sia-sdk-rs/sia_sdk/benches/merkle_root.rs +12 -0
  61. package/template/rust/sia-sdk-rs/sia_sdk/src/blake2.rs +22 -0
  62. package/template/rust/sia-sdk-rs/sia_sdk/src/consensus.rs +767 -0
  63. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v1.rs +257 -0
  64. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v2.rs +291 -0
  65. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding.rs +26 -0
  66. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async/v2.rs +367 -0
  67. package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async.rs +6 -0
  68. package/template/rust/sia-sdk-rs/sia_sdk/src/encryption.rs +303 -0
  69. package/template/rust/sia-sdk-rs/sia_sdk/src/erasure_coding.rs +347 -0
  70. package/template/rust/sia-sdk-rs/sia_sdk/src/lib.rs +15 -0
  71. package/template/rust/sia-sdk-rs/sia_sdk/src/macros.rs +435 -0
  72. package/template/rust/sia-sdk-rs/sia_sdk/src/merkle.rs +112 -0
  73. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/merkle.rs +357 -0
  74. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/rpc.rs +1507 -0
  75. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/types.rs +146 -0
  76. package/template/rust/sia-sdk-rs/sia_sdk/src/rhp.rs +7 -0
  77. package/template/rust/sia-sdk-rs/sia_sdk/src/seed.rs +278 -0
  78. package/template/rust/sia-sdk-rs/sia_sdk/src/signing.rs +236 -0
  79. package/template/rust/sia-sdk-rs/sia_sdk/src/types/common.rs +677 -0
  80. package/template/rust/sia-sdk-rs/sia_sdk/src/types/currency.rs +450 -0
  81. package/template/rust/sia-sdk-rs/sia_sdk/src/types/specifier.rs +110 -0
  82. package/template/rust/sia-sdk-rs/sia_sdk/src/types/spendpolicy.rs +778 -0
  83. package/template/rust/sia-sdk-rs/sia_sdk/src/types/utils.rs +117 -0
  84. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v1.rs +1737 -0
  85. package/template/rust/sia-sdk-rs/sia_sdk/src/types/v2.rs +1726 -0
  86. package/template/rust/sia-sdk-rs/sia_sdk/src/types/work.rs +59 -0
  87. package/template/rust/sia-sdk-rs/sia_sdk/src/types.rs +16 -0
  88. package/template/scripts/setup-rust.js +29 -0
  89. package/template/src/App.tsx +13 -0
  90. package/template/src/components/DevNote.tsx +21 -0
  91. package/template/src/components/auth/ApproveScreen.tsx +84 -0
  92. package/template/src/components/auth/AuthFlow.tsx +77 -0
  93. package/template/src/components/auth/ConnectScreen.tsx +214 -0
  94. package/template/src/components/auth/LoadingScreen.tsx +8 -0
  95. package/template/src/components/auth/RecoveryScreen.tsx +182 -0
  96. package/template/src/components/upload/UploadZone.tsx +314 -0
  97. package/template/src/index.css +9 -0
  98. package/template/src/lib/constants.ts +8 -0
  99. package/template/src/lib/format.ts +35 -0
  100. package/template/src/lib/hex.ts +13 -0
  101. package/template/src/lib/sdk.ts +25 -0
  102. package/template/src/lib/wasm-env.ts +5 -0
  103. package/template/src/main.tsx +12 -0
  104. package/template/src/stores/auth.ts +86 -0
  105. package/template/tsconfig.app.json +31 -0
  106. package/template/tsconfig.json +7 -0
  107. package/template/tsconfig.node.json +26 -0
  108. package/template/vite.config.ts +18 -0
  109. package/template/wasm/indexd_wasm/indexd_wasm.d.ts +309 -0
  110. package/template/wasm/indexd_wasm/indexd_wasm.js +1507 -0
  111. package/template/wasm/indexd_wasm/indexd_wasm_bg.wasm +0 -0
  112. package/template/wasm/indexd_wasm/package.json +31 -0
@@ -0,0 +1,1039 @@
1
+ uniffi::setup_scaffolding!();
2
+
3
+ use base64::prelude::*;
4
+ use indexd::{SealedObjectError, Url};
5
+ use log::debug;
6
+ use sia::rhp::SECTOR_SIZE;
7
+ use sia::signing::{PublicKey, Signature};
8
+ use sia::types::{self, Hash256, HexParseError};
9
+ use sia::{encoding, encryption};
10
+ use std::str::FromStr;
11
+ use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
12
+ use std::sync::{Arc, LazyLock, Mutex};
13
+ use std::time::SystemTime;
14
+ use thiserror::Error;
15
+ use tokio::io::{AsyncWriteExt, BufWriter};
16
+ use tokio::runtime::{self, Runtime};
17
+ use tokio::sync::{mpsc, oneshot};
18
+ use tokio_util::task::AbortOnDropHandle;
19
+
20
+ mod tls;
21
+
22
+ mod logging;
23
+ pub use logging::*;
24
+
25
+ mod builder;
26
+ pub use builder::*;
27
+
28
+ mod io;
29
+ pub use io::*;
30
+
31
+ static RUNTIME: LazyLock<Runtime> = LazyLock::new(|| {
32
+ runtime::Builder::new_multi_thread()
33
+ .enable_all()
34
+ .build()
35
+ .expect("failed to create global runtime")
36
+ });
37
+
38
+ /// A helper that spawns a future onto the global runtime and returns an AbortOnDropHandle
39
+ /// to ensure cancellation works with Uniffi.
40
+ fn spawn<F, T>(future: F) -> AbortOnDropHandle<T>
41
+ where
42
+ F: std::future::Future<Output = T> + Send + 'static,
43
+ T: Send + 'static,
44
+ {
45
+ AbortOnDropHandle::new(RUNTIME.spawn(future))
46
+ }
47
+
48
+ #[uniffi::export(with_foreign)]
49
+ pub trait UploadProgressCallback: Send + Sync {
50
+ fn progress(&self, uploaded: u64, encoded_size: u64);
51
+ }
52
+
53
+ #[derive(Debug, Error, uniffi::Error)]
54
+ #[uniffi(flat_error)]
55
+ pub enum Error {
56
+ #[error("{0}")]
57
+ SDK(#[from] indexd::Error),
58
+
59
+ #[error("hex error: {0}")]
60
+ HexParseError(#[from] sia::types::HexParseError),
61
+
62
+ #[error("sealed object error: {0}")]
63
+ SealedObject(#[from] SealedObjectError),
64
+
65
+ #[error("task error: {0}")]
66
+ JoinError(#[from] tokio::task::JoinError),
67
+
68
+ #[error("error: {0}")]
69
+ Custom(String),
70
+ }
71
+
72
+ #[derive(Debug, Error, uniffi::Error)]
73
+ #[uniffi(flat_error)]
74
+ pub enum ConnectError {
75
+ #[error("app client error: {0}")]
76
+ AppClient(#[from] indexd::app_client::Error),
77
+ #[error("task error: {0}")]
78
+ JoinError(#[from] tokio::task::JoinError),
79
+ #[error("error: {0}")]
80
+ Custom(String),
81
+ }
82
+
83
+ #[derive(Debug, Error, uniffi::Error)]
84
+ #[uniffi(flat_error)]
85
+ pub enum UploadError {
86
+ #[error("buffer closed")]
87
+ Closed,
88
+
89
+ #[error("i/o error: {0}")]
90
+ Io(#[from] std::io::Error),
91
+
92
+ #[error("{0}")]
93
+ Upload(#[from] indexd::UploadError),
94
+
95
+ #[error("task error: {0}")]
96
+ JoinError(#[from] tokio::task::JoinError),
97
+
98
+ #[error("custom error: {0}")]
99
+ Custom(String),
100
+ }
101
+
102
+ #[derive(Debug, Error, uniffi::Error)]
103
+ #[uniffi(flat_error)]
104
+ pub enum DownloadError {
105
+ #[error("{0}")]
106
+ Download(#[from] indexd::DownloadError),
107
+
108
+ #[error("task error: {0}")]
109
+ JoinError(#[from] tokio::task::JoinError),
110
+ }
111
+
112
+ /// Metadata about an application connecting to the indexer.
113
+ #[derive(uniffi::Record)]
114
+ pub struct AppMeta {
115
+ pub id: Vec<u8>,
116
+ pub name: String,
117
+ pub description: String,
118
+ pub service_url: String,
119
+ pub logo_url: Option<String>,
120
+ pub callback_url: Option<String>,
121
+ }
122
+
123
+ /// The protocol used in a network address.
124
+ #[derive(uniffi::Enum)]
125
+ pub enum AddressProtocol {
126
+ SiaMux,
127
+ Quic,
128
+ }
129
+
130
+ /// A network address of a storage provider on the Sia network.
131
+ #[derive(uniffi::Record)]
132
+ pub struct NetAddress {
133
+ pub protocol: AddressProtocol,
134
+ pub address: String,
135
+ }
136
+
137
+ #[derive(Debug, Error, uniffi::Error)]
138
+ #[uniffi(flat_error)]
139
+ pub enum EncryptionKeyParseError {
140
+ #[error("failed to decode base64: {0}")]
141
+ Base64(#[from] base64::DecodeError),
142
+ #[error("invalid key length: {0}, expected 32 bytes")]
143
+ KeyLength(usize),
144
+ }
145
+
146
+ #[derive(uniffi::Object)]
147
+ pub struct EncryptionKey(encryption::EncryptionKey);
148
+
149
+ #[uniffi::export]
150
+ impl EncryptionKey {
151
+ #[uniffi::constructor]
152
+ pub fn parse(str: String) -> Result<Self, EncryptionKeyParseError> {
153
+ let data = BASE64_STANDARD.decode(str.as_bytes())?;
154
+ if data.len() != 32 {
155
+ return Err(EncryptionKeyParseError::KeyLength(data.len()));
156
+ }
157
+ Ok(Self(encryption::EncryptionKey::from(
158
+ <[u8; 32]>::try_from(data).unwrap(),
159
+ )))
160
+ }
161
+
162
+ /// Exports the key as a base64 encoded string.
163
+ ///
164
+ /// This should be used to store the key securely.
165
+ /// The key should never be shared or transmitted
166
+ /// in plaintext.
167
+ pub fn export(&self) -> String {
168
+ BASE64_STANDARD.encode(self.0.as_ref())
169
+ }
170
+ }
171
+
172
+ impl From<encryption::EncryptionKey> for EncryptionKey {
173
+ fn from(key: encryption::EncryptionKey) -> Self {
174
+ Self(key)
175
+ }
176
+ }
177
+
178
+ #[derive(Debug, Error, uniffi::Error)]
179
+ #[uniffi(flat_error)]
180
+ pub enum ObjectError {
181
+ #[error("sealed object error: {0}")]
182
+ SealedObject(#[from] SealedObjectError),
183
+
184
+ #[error("encoding error: {0}")]
185
+ Encoding(#[from] encoding::Error),
186
+ }
187
+
188
+ /// An object that has been pinned to an indexer. Objects are immutable
189
+ /// data stored on the Sia network. The data is erasure-coded and distributed across
190
+ /// multiple storage providers. The object is encrypted with a unique encryption key,
191
+ /// which is used to encrypt the metadata.
192
+ ///
193
+ /// Custom user-defined metadata can be associated with the object. It is
194
+ /// recommended to use a portable format like JSON for metadata.
195
+ ///
196
+ /// It can be sealed for secure offline storage or transmission and
197
+ /// later opened using the app key.
198
+ ///
199
+ /// It has no public fields to prevent accidental leakage or corruption.
200
+ #[derive(uniffi::Object)]
201
+ pub struct PinnedObject {
202
+ inner: Arc<Mutex<indexd::Object>>,
203
+ }
204
+
205
+ impl PinnedObject {
206
+ fn object(&self) -> indexd::Object {
207
+ self.inner.lock().unwrap().clone()
208
+ }
209
+ }
210
+
211
+ #[uniffi::export]
212
+ impl PinnedObject {
213
+ /// Opens a sealed object using the provided app key.
214
+ ///
215
+ /// # Arguments
216
+ /// * `app_key` - The app key that was used to seal the object.
217
+ /// * `sealed` - The sealed object to open.
218
+ ///
219
+ /// # Returns
220
+ /// The unsealed object or an error if the object could not be opened.
221
+ #[uniffi::constructor]
222
+ pub fn open(app_key: Arc<AppKey>, sealed: SealedObject) -> Result<Self, ObjectError> {
223
+ let sealed = indexd::SealedObject {
224
+ encrypted_data_key: sealed.encrypted_data_key,
225
+ encrypted_metadata_key: sealed.encrypted_metadata_key,
226
+ slabs: sealed
227
+ .slabs
228
+ .into_iter()
229
+ .map(|s| s.try_into().unwrap())
230
+ .collect(),
231
+ encrypted_metadata: sealed.encrypted_metadata,
232
+ data_signature: Signature::try_from(sealed.data_signature.as_ref())?,
233
+ metadata_signature: Signature::try_from(sealed.metadata_signature.as_ref())?,
234
+ created_at: sealed.created_at.into(),
235
+ updated_at: sealed.updated_at.into(),
236
+ };
237
+ let obj = sealed.open(app_key.private_key())?;
238
+ Ok(Self {
239
+ inner: Arc::new(Mutex::new(obj)),
240
+ })
241
+ }
242
+
243
+ /// Seal the object for offline storage.
244
+ /// # Arguments
245
+ /// * `app_key` - The app key used to derive the master key to encrypt the object's encryption key.
246
+ ///
247
+ /// # Returns
248
+ /// The sealed object.
249
+ pub fn seal(&self, app_key: Arc<AppKey>) -> SealedObject {
250
+ let inner = self.inner.lock().unwrap();
251
+ SealedObject::from(inner.seal(app_key.private_key()))
252
+ }
253
+
254
+ /// Returns the object's ID, which is the Blake2b hash of its slabs.
255
+ pub fn id(&self) -> String {
256
+ let inner = self.inner.lock().unwrap();
257
+ inner.id().to_string()
258
+ }
259
+
260
+ /// Returns the total size of the object by summing the lengths of its slabs.
261
+ pub fn size(&self) -> u64 {
262
+ let inner = self.inner.lock().unwrap();
263
+ inner.size()
264
+ }
265
+
266
+ /// Returns the slabs that make up the object.
267
+ pub fn slabs(&self) -> Vec<Slab> {
268
+ let inner = self.inner.lock().unwrap();
269
+ inner.slabs().iter().cloned().map(|s| s.into()).collect()
270
+ }
271
+
272
+ /// Returns the metadata associated with the object.
273
+ pub fn metadata(&self) -> Vec<u8> {
274
+ let inner = self.inner.lock().unwrap();
275
+ inner.metadata.clone()
276
+ }
277
+
278
+ /// Updates the metadata associated with the object.
279
+ pub fn update_metadata(&self, metadata: Vec<u8>) {
280
+ let mut inner = self.inner.lock().unwrap();
281
+ inner.metadata = metadata;
282
+ }
283
+
284
+ /// Returns the time the object was created.
285
+ pub fn created_at(&self) -> SystemTime {
286
+ let inner = self.inner.lock().unwrap();
287
+ (*inner.created_at()).into()
288
+ }
289
+
290
+ /// Returns the time the object was last updated.
291
+ pub fn updated_at(&self) -> SystemTime {
292
+ let inner = self.inner.lock().unwrap();
293
+ (*inner.updated_at()).into()
294
+ }
295
+ }
296
+
297
+ /// A sealed object represents an object that has been encrypted
298
+ /// for secure offline storage or processing. It can be opened using
299
+ /// an app key to retrieve the original object.
300
+ #[derive(uniffi::Record)]
301
+ pub struct SealedObject {
302
+ pub id: String,
303
+ pub encrypted_data_key: Vec<u8>,
304
+ pub encrypted_metadata_key: Vec<u8>,
305
+ pub slabs: Vec<Slab>,
306
+ pub encrypted_metadata: Vec<u8>,
307
+ pub data_signature: Vec<u8>,
308
+ pub metadata_signature: Vec<u8>,
309
+
310
+ pub created_at: SystemTime,
311
+ pub updated_at: SystemTime,
312
+ }
313
+
314
+ impl From<indexd::SealedObject> for SealedObject {
315
+ fn from(o: indexd::SealedObject) -> Self {
316
+ Self {
317
+ id: o.id().to_string(),
318
+ encrypted_data_key: o.encrypted_data_key,
319
+ encrypted_metadata_key: o.encrypted_metadata_key,
320
+ slabs: o.slabs.into_iter().map(|s| s.into()).collect(),
321
+ encrypted_metadata: o.encrypted_metadata,
322
+ data_signature: o.data_signature.as_ref().to_vec(),
323
+ metadata_signature: o.metadata_signature.as_ref().to_vec(),
324
+ created_at: o.created_at.into(),
325
+ updated_at: o.updated_at.into(),
326
+ }
327
+ }
328
+ }
329
+
330
+ impl TryInto<indexd::SealedObject> for SealedObject {
331
+ type Error = SealedObjectError;
332
+
333
+ fn try_into(self) -> Result<indexd::SealedObject, Self::Error> {
334
+ let sealed = indexd::SealedObject {
335
+ encrypted_data_key: self.encrypted_data_key,
336
+ encrypted_metadata_key: self.encrypted_metadata_key,
337
+ slabs: self
338
+ .slabs
339
+ .into_iter()
340
+ .map(|s| s.try_into().unwrap())
341
+ .collect(),
342
+ encrypted_metadata: self.encrypted_metadata,
343
+ data_signature: Signature::try_from(self.data_signature.as_ref())?,
344
+ metadata_signature: Signature::try_from(self.metadata_signature.as_ref())?,
345
+ created_at: self.created_at.into(),
346
+ updated_at: self.updated_at.into(),
347
+ };
348
+ if sealed.id().to_string() != self.id {
349
+ return Err(SealedObjectError::ContentsMismatch);
350
+ }
351
+ Ok(sealed)
352
+ }
353
+ }
354
+
355
+ /// An ObjectEvent represents an object and whether it was deleted or not.
356
+ #[derive(uniffi::Record)]
357
+ pub struct ObjectEvent {
358
+ pub id: String,
359
+ pub deleted: bool,
360
+ pub updated_at: SystemTime,
361
+ pub object: Option<Arc<PinnedObject>>,
362
+ }
363
+
364
+ /// Information about a storage provider on the
365
+ /// Sia network.
366
+ #[derive(uniffi::Record)]
367
+ pub struct Host {
368
+ pub public_key: String,
369
+ pub addresses: Vec<NetAddress>,
370
+ pub country_code: String,
371
+ pub latitude: f64,
372
+ pub longitude: f64,
373
+ pub good_for_upload: bool,
374
+ }
375
+
376
+ impl From<sia::rhp::Host> for Host {
377
+ fn from(h: sia::rhp::Host) -> Self {
378
+ Self {
379
+ public_key: h.public_key.to_string(),
380
+ addresses: h
381
+ .addresses
382
+ .iter()
383
+ .map(|a| NetAddress {
384
+ protocol: match a.protocol {
385
+ types::v2::Protocol::SiaMux => AddressProtocol::SiaMux,
386
+ types::v2::Protocol::QUIC => AddressProtocol::Quic,
387
+ },
388
+ address: a.address.clone(),
389
+ })
390
+ .collect(),
391
+ country_code: h.country_code,
392
+ latitude: h.latitude,
393
+ longitude: h.longitude,
394
+ good_for_upload: h.good_for_upload,
395
+ }
396
+ }
397
+ }
398
+
399
+ impl TryInto<sia::rhp::Host> for Host {
400
+ type Error = HexParseError;
401
+
402
+ fn try_into(self) -> Result<sia::rhp::Host, Self::Error> {
403
+ Ok(sia::rhp::Host {
404
+ public_key: PublicKey::from_str(self.public_key.as_str())?,
405
+ addresses: self
406
+ .addresses
407
+ .into_iter()
408
+ .map(|a| {
409
+ Ok(types::v2::NetAddress {
410
+ protocol: match a.protocol {
411
+ AddressProtocol::SiaMux => types::v2::Protocol::SiaMux,
412
+ AddressProtocol::Quic => types::v2::Protocol::QUIC,
413
+ },
414
+ address: a.address,
415
+ })
416
+ })
417
+ .collect::<Result<Vec<types::v2::NetAddress>, HexParseError>>()?,
418
+ country_code: self.country_code,
419
+ latitude: self.latitude,
420
+ longitude: self.longitude,
421
+ good_for_upload: self.good_for_upload,
422
+ })
423
+ }
424
+ }
425
+
426
+ /// A sector stored on a specific host.
427
+ #[derive(Clone, uniffi::Record)]
428
+ pub struct PinnedSector {
429
+ pub root: String,
430
+ pub host_key: String,
431
+ }
432
+
433
+ /// A PinnedSlab represents a slab that has been pinned to the indexer.
434
+ #[derive(uniffi::Record)]
435
+ pub struct PinnedSlab {
436
+ pub id: String,
437
+ pub encryption_key: Vec<u8>,
438
+ pub min_shards: u8,
439
+ pub sectors: Vec<PinnedSector>,
440
+ }
441
+
442
+ impl From<indexd::PinnedSlab> for PinnedSlab {
443
+ fn from(s: indexd::PinnedSlab) -> Self {
444
+ Self {
445
+ id: s.id.to_string(),
446
+ encryption_key: s.encryption_key.as_ref().to_vec(),
447
+ min_shards: s.min_shards,
448
+ sectors: s
449
+ .sectors
450
+ .into_iter()
451
+ .map(|sec| PinnedSector {
452
+ root: sec.root.to_string(),
453
+ host_key: sec.host_key.to_string(),
454
+ })
455
+ .collect(),
456
+ }
457
+ }
458
+ }
459
+
460
+ /// A Slab represents a contiguous erasure-coded segment of a file stored on the Sia network.
461
+ #[derive(uniffi::Record)]
462
+ pub struct Slab {
463
+ pub encryption_key: Vec<u8>,
464
+ pub min_shards: u8,
465
+ pub sectors: Vec<PinnedSector>,
466
+ pub offset: u32,
467
+ pub length: u32,
468
+ }
469
+
470
+ impl From<indexd::Slab> for Slab {
471
+ fn from(s: indexd::Slab) -> Self {
472
+ Self {
473
+ encryption_key: s.encryption_key.as_ref().to_vec(),
474
+ min_shards: s.min_shards,
475
+ sectors: s
476
+ .sectors
477
+ .into_iter()
478
+ .map(|sec| PinnedSector {
479
+ root: sec.root.to_string(),
480
+ host_key: sec.host_key.to_string(),
481
+ })
482
+ .collect(),
483
+ offset: s.offset,
484
+ length: s.length,
485
+ }
486
+ }
487
+ }
488
+
489
+ impl TryInto<indexd::Slab> for Slab {
490
+ type Error = String;
491
+
492
+ fn try_into(self) -> Result<indexd::Slab, Self::Error> {
493
+ Ok(indexd::Slab {
494
+ encryption_key: encryption::EncryptionKey::try_from(self.encryption_key.as_slice())?,
495
+ min_shards: self.min_shards,
496
+ sectors: self
497
+ .sectors
498
+ .into_iter()
499
+ .map(|sec| sec.try_into())
500
+ .collect::<Result<Vec<indexd::Sector>, HexParseError>>()
501
+ .map_err(|e| e.to_string())?,
502
+ offset: self.offset,
503
+ length: self.length,
504
+ })
505
+ }
506
+ }
507
+
508
+ impl TryInto<indexd::Sector> for PinnedSector {
509
+ type Error = HexParseError;
510
+
511
+ fn try_into(self) -> Result<indexd::Sector, Self::Error> {
512
+ Ok(indexd::Sector {
513
+ host_key: PublicKey::from_str(self.host_key.as_str())?,
514
+ root: Hash256::from_str(self.root.as_str())?,
515
+ })
516
+ }
517
+ }
518
+
519
+ /// Used to paginate through objects stored in the indexer.
520
+ ///
521
+ /// When syncing changes from an indexer, `after` should be set to the
522
+ /// last `updated_at` timestamp seen, and `key` should be set to the
523
+ /// last object's key seen.
524
+ #[derive(uniffi::Record)]
525
+ pub struct ObjectsCursor {
526
+ pub id: String,
527
+ pub after: SystemTime,
528
+ }
529
+
530
+ impl From<indexd::app_client::ObjectsCursor> for ObjectsCursor {
531
+ fn from(c: indexd::app_client::ObjectsCursor) -> Self {
532
+ Self {
533
+ id: c.id.to_string(),
534
+ after: c.after.into(),
535
+ }
536
+ }
537
+ }
538
+
539
+ #[derive(uniffi::Record)]
540
+ pub struct App {
541
+ pub id: String,
542
+ pub description: String,
543
+ pub service_url: Option<String>,
544
+ pub logo_url: Option<String>,
545
+ }
546
+
547
+ /// An account registered on the indexer.
548
+ #[derive(uniffi::Record)]
549
+ pub struct Account {
550
+ pub account_key: String,
551
+ pub max_pinned_data: u64,
552
+ pub pinned_data: u64,
553
+ pub app: App,
554
+ pub last_used: SystemTime,
555
+ }
556
+
557
+ impl From<indexd::app_client::Account> for Account {
558
+ fn from(a: indexd::app_client::Account) -> Self {
559
+ Self {
560
+ account_key: a.account_key.to_string(),
561
+ max_pinned_data: a.max_pinned_data,
562
+ pinned_data: a.pinned_data,
563
+ app: App {
564
+ id: a.app.id.to_string(),
565
+ description: a.app.description,
566
+ service_url: a.app.service_url,
567
+ logo_url: a.app.logo_url,
568
+ },
569
+ last_used: a.last_used.into(),
570
+ }
571
+ }
572
+ }
573
+
574
+ enum PackedUploadAction {
575
+ Add(Arc<dyn Reader>, oneshot::Sender<Result<u64, UploadError>>),
576
+ Finalize(oneshot::Sender<Result<Vec<indexd::Object>, UploadError>>),
577
+ }
578
+
579
+ /// A packed upload allows multiple objects to be uploaded together in a single upload. This can be more
580
+ /// efficient than uploading each object separately if the size of the object is less than the minimum
581
+ /// slab size.
582
+ #[derive(uniffi::Object)]
583
+ pub struct PackedUpload {
584
+ upload_task: AbortOnDropHandle<()>,
585
+ tx: mpsc::Sender<PackedUploadAction>,
586
+ slab_size: u64,
587
+ length: Arc<AtomicU64>,
588
+ closed: Arc<AtomicBool>,
589
+ }
590
+
591
+ #[uniffi::export]
592
+ impl PackedUpload {
593
+ /// Returns the number of bytes remaining until reaching the optimal
594
+ /// packed size. Adding objects larger than this will start a new slab.
595
+ /// To minimize padding, prioritize objects that fit within the remaining
596
+ /// size.
597
+ pub fn remaining(&self) -> u64 {
598
+ let length = self.length.load(Ordering::Acquire);
599
+ if length == 0 {
600
+ return self.slab_size;
601
+ }
602
+ (self.slab_size - (length % self.slab_size)) % self.slab_size
603
+ }
604
+
605
+ /// Returns the number of bytes added so far.
606
+ pub fn length(&self) -> u64 {
607
+ self.length.load(Ordering::Acquire)
608
+ }
609
+
610
+ /// Returns the number of slabs in the upload.
611
+ pub fn slabs(&self) -> u64 {
612
+ self.length.load(Ordering::Acquire).div_ceil(self.slab_size)
613
+ }
614
+
615
+ /// Adds a new object to the upload. The data will be read until EOF and packed into
616
+ /// the upload. The resulting object will contain the metadata needed to download the object. The caller
617
+ /// must call [finalize](Self::finalize) to get the resulting objects after all objects have been added.
618
+ pub async fn add(&self, reader: Arc<dyn Reader>) -> Result<u64, UploadError> {
619
+ if self.closed.load(Ordering::Acquire) {
620
+ return Err(UploadError::Closed);
621
+ }
622
+ let tx = self.tx.clone();
623
+
624
+ spawn(async move {
625
+ let (add_tx, add_rx) = oneshot::channel();
626
+ tx.send(PackedUploadAction::Add(reader, add_tx))
627
+ .await
628
+ .map_err(|_| UploadError::Closed)?;
629
+ add_rx.await.map_err(|_| UploadError::Closed)?
630
+ })
631
+ .await?
632
+ }
633
+
634
+ /// Cancels the upload. This will immediately cancel the upload and return.
635
+ pub async fn cancel(&self) -> Result<(), UploadError> {
636
+ if self.closed.swap(true, Ordering::AcqRel) {
637
+ return Err(UploadError::Closed);
638
+ }
639
+ self.upload_task.abort();
640
+ Ok(())
641
+ }
642
+
643
+ /// Finalizes the upload and returns the resulting objects. This will wait for all readers
644
+ /// to finish and all slabs to be uploaded before returning. The resulting objects will contain the metadata needed to download the objects.
645
+ ///
646
+ /// The caller must pin the resulting objects to the indexer when ready.
647
+ pub async fn finalize(&self) -> Result<Vec<Arc<PinnedObject>>, UploadError> {
648
+ if self.closed.swap(true, Ordering::AcqRel) {
649
+ return Err(UploadError::Closed);
650
+ }
651
+ let tx = self.tx.clone();
652
+ let objects = spawn(async move {
653
+ let (finalize_tx, finalize_rx) = oneshot::channel();
654
+ tx.send(PackedUploadAction::Finalize(finalize_tx))
655
+ .await
656
+ .map_err(|_| UploadError::Closed)?;
657
+ finalize_rx.await.map_err(|_| UploadError::Closed)?
658
+ })
659
+ .await??;
660
+ Ok(objects
661
+ .into_iter()
662
+ .map(|o| {
663
+ Arc::new(PinnedObject {
664
+ inner: Arc::new(Mutex::new(o)),
665
+ })
666
+ })
667
+ .collect())
668
+ }
669
+ }
670
+
671
+ /// Provides options for an upload operation.
672
+ #[derive(uniffi::Record)]
673
+ pub struct UploadOptions {
674
+ #[uniffi(default = 10)]
675
+ pub max_inflight: u8,
676
+ #[uniffi(default = 10)]
677
+ pub data_shards: u8,
678
+ #[uniffi(default = 20)]
679
+ pub parity_shards: u8,
680
+
681
+ /// Optional callback to report upload progress.
682
+ /// The callback will be called with the number of bytes uploaded
683
+ /// and the total encoded size of the upload.
684
+ #[uniffi(default = None)]
685
+ pub progress_callback: Option<Arc<dyn UploadProgressCallback>>,
686
+ }
687
+
688
+ /// Provides options for a download operation.
689
+ #[derive(uniffi::Record)]
690
+ pub struct DownloadOptions {
691
+ #[uniffi(default = 10)]
692
+ pub max_inflight: u8,
693
+ #[uniffi(default = 0)]
694
+ pub offset: u64,
695
+ #[uniffi(default = None)]
696
+ pub length: Option<u64>,
697
+ }
698
+
699
+ #[derive(uniffi::Object)]
700
+ pub struct SDK {
701
+ inner: indexd::SDK,
702
+ }
703
+
704
+ #[uniffi::export]
705
+ impl SDK {
706
+ /// Returns the application key used by the SDK.
707
+ ///
708
+ /// This should be kept secret and secure. Applications
709
+ /// must never share their app key publicly. Store
710
+ /// it safely.
711
+ pub fn app_key(&self) -> AppKey {
712
+ AppKey::from(self.inner.app_key().clone())
713
+ }
714
+
715
+ /// Creates a new packed upload. This allows multiple objects to be packed together
716
+ /// for more efficient uploads. The returned `PackedUpload` can be used to add objects to the upload, and then finalized to get the resulting objects.
717
+ ///
718
+ /// # Arguments
719
+ /// * `options` - The [UploadOptions] to use for the upload.
720
+ ///
721
+ /// # Returns
722
+ /// A [PackedUpload] that can be used to add objects and finalize the upload.
723
+ pub async fn upload_packed(&self, options: UploadOptions) -> PackedUpload {
724
+ let sdk = self.inner.clone();
725
+ let (action_tx, mut action_rx) = mpsc::channel(10);
726
+ let slab_size = options.data_shards as usize * SECTOR_SIZE;
727
+ let length = Arc::new(AtomicU64::new(0));
728
+ let closed = Arc::new(AtomicBool::new(false));
729
+
730
+ let task_length = length.clone();
731
+ let upload_task = spawn(async move {
732
+ let progress_tx = if let Some(callback) = options.progress_callback {
733
+ let total_shards = options.data_shards as u64 + options.parity_shards as u64;
734
+ let slab_size = total_shards * SECTOR_SIZE as u64;
735
+ let (tx, mut rx) = mpsc::unbounded_channel();
736
+ tokio::spawn(async move {
737
+ let mut sectors: u64 = 0;
738
+ while rx.recv().await.is_some() {
739
+ sectors += 1;
740
+ let size = sectors * SECTOR_SIZE as u64;
741
+ let slabs_size = sectors.div_ceil(total_shards) * slab_size;
742
+ callback.progress(size, slabs_size);
743
+ }
744
+ });
745
+ Some(tx)
746
+ } else {
747
+ None
748
+ };
749
+ let mut packed_upload = sdk.upload_packed(indexd::UploadOptions {
750
+ max_inflight: options.max_inflight as usize,
751
+ data_shards: options.data_shards,
752
+ parity_shards: options.parity_shards,
753
+ shard_uploaded: progress_tx,
754
+ });
755
+
756
+ while let Some(action) = action_rx.recv().await {
757
+ match action {
758
+ PackedUploadAction::Add(reader, add_tx) => {
759
+ let res = packed_upload
760
+ .add(adapt_ffi_reader(reader))
761
+ .await
762
+ .map_err(UploadError::from);
763
+ if let Ok(size) = res {
764
+ task_length.fetch_add(size, Ordering::AcqRel);
765
+ }
766
+ let _ = add_tx.send(res);
767
+ }
768
+ PackedUploadAction::Finalize(finalize_tx) => {
769
+ let result = packed_upload.finalize().await.map_err(|e| e.into());
770
+ let _ = finalize_tx.send(result);
771
+ return;
772
+ }
773
+ }
774
+ }
775
+ });
776
+
777
+ PackedUpload {
778
+ upload_task,
779
+ tx: action_tx,
780
+ slab_size: slab_size as u64,
781
+ length,
782
+ closed,
783
+ }
784
+ }
785
+
786
+ /// Uploads data to the Sia network and pins it to the indexer
787
+ ///
788
+ /// # Arguments
789
+ /// * `options` - The [UploadOptions] to use for the upload
790
+ ///
791
+ /// # Returns
792
+ /// An object representing the uploaded data.
793
+ pub async fn upload(
794
+ &self,
795
+ r: Arc<dyn Reader>,
796
+ options: UploadOptions,
797
+ ) -> Result<PinnedObject, UploadError> {
798
+ let sdk = self.inner.clone();
799
+ spawn(async move {
800
+ let r = adapt_ffi_reader(r);
801
+ let progress_tx = if let Some(callback) = options.progress_callback {
802
+ let total_shards = options.data_shards as u64 + options.parity_shards as u64;
803
+ let slab_size = total_shards * SECTOR_SIZE as u64;
804
+ let (tx, mut rx) = mpsc::unbounded_channel();
805
+ tokio::spawn(async move {
806
+ let mut sectors: u64 = 0;
807
+ while rx.recv().await.is_some() {
808
+ sectors += 1;
809
+ let size = sectors * SECTOR_SIZE as u64;
810
+ let slabs_size = sectors.div_ceil(total_shards) * slab_size;
811
+ callback.progress(size, slabs_size);
812
+ }
813
+ });
814
+ Some(tx)
815
+ } else {
816
+ None
817
+ };
818
+ let obj = sdk
819
+ .upload(
820
+ r,
821
+ indexd::UploadOptions {
822
+ max_inflight: options.max_inflight as usize,
823
+ data_shards: options.data_shards,
824
+ parity_shards: options.parity_shards,
825
+ shard_uploaded: progress_tx,
826
+ },
827
+ )
828
+ .await?;
829
+ Ok(PinnedObject {
830
+ inner: Arc::new(Mutex::new(obj)),
831
+ })
832
+ })
833
+ .await?
834
+ }
835
+
836
+ /// Initiates a download of the data referenced by the object, starting at `offset` and reading `length` bytes.
837
+ pub async fn download(
838
+ &self,
839
+ w: Arc<dyn Writer>,
840
+ object: Arc<PinnedObject>,
841
+ options: DownloadOptions,
842
+ ) -> Result<(), DownloadError> {
843
+ const CHUNK_SIZE: usize = 1 << 19; // 512KiB
844
+ let object = object.object();
845
+ let object_size = object.size();
846
+ let offset = options.offset;
847
+ let max_length = options.length.unwrap_or(object_size);
848
+ let max_inflight = options.max_inflight;
849
+ let sdk = self.inner.clone();
850
+ let w = adapt_ffi_writer(w);
851
+ let mut w = BufWriter::with_capacity(CHUNK_SIZE, w);
852
+ spawn(async move {
853
+ for offset in (offset..max_length).step_by(CHUNK_SIZE) {
854
+ sdk.download(
855
+ &mut w,
856
+ &object,
857
+ indexd::DownloadOptions {
858
+ offset,
859
+ length: Some(max_length.min(CHUNK_SIZE as u64)),
860
+ max_inflight: max_inflight as usize,
861
+ },
862
+ )
863
+ .await?;
864
+ }
865
+ if let Err(e) = w.shutdown().await {
866
+ debug!("error shutting down writer: {}", e);
867
+ }
868
+ Ok(())
869
+ })
870
+ .await?
871
+ }
872
+
873
+ /// Returns a list of all usable hosts.
874
+ pub async fn hosts(&self) -> Result<Vec<Host>, Error> {
875
+ let sdk = self.inner.clone();
876
+ spawn(async move {
877
+ let hosts = sdk.hosts(Default::default()).await?;
878
+ Ok(hosts.into_iter().map(|h| h.into()).collect())
879
+ })
880
+ .await?
881
+ }
882
+
883
+ /// Returns objects stored in the indexer. When syncing, the caller should
884
+ /// provide the last `updated_at` timestamp and `id` seen in the `cursor`
885
+ /// parameter to avoid missing or duplicating objects.
886
+ ///
887
+ /// # Arguments
888
+ /// * `cursor` can be used to paginate through the results. If `cursor` is `None`, the first page of results will be returned.
889
+ /// * `limit` specifies the maximum number of objects to return.
890
+ pub async fn object_events(
891
+ &self,
892
+ cursor: Option<ObjectsCursor>,
893
+ limit: u32,
894
+ ) -> Result<Vec<ObjectEvent>, Error> {
895
+ let cursor = match cursor {
896
+ Some(c) => Some(indexd::app_client::ObjectsCursor {
897
+ after: c.after.into(),
898
+ id: Hash256::from_str(c.id.as_str())?,
899
+ }),
900
+ None => None,
901
+ };
902
+ let sdk = self.inner.clone();
903
+ spawn(async move {
904
+ let objects = sdk
905
+ .object_events(cursor, Some(limit as usize))
906
+ .await?
907
+ .into_iter()
908
+ .map(|event| {
909
+ Ok(ObjectEvent {
910
+ id: event.id.to_string(),
911
+ deleted: event.deleted,
912
+ updated_at: event.updated_at.into(),
913
+ object: event.object.map(|obj| {
914
+ Arc::new(PinnedObject {
915
+ inner: Arc::new(Mutex::new(obj)),
916
+ })
917
+ }),
918
+ })
919
+ })
920
+ .collect::<Result<Vec<ObjectEvent>, SealedObjectError>>()?;
921
+ Ok(objects)
922
+ })
923
+ .await?
924
+ }
925
+
926
+ /// Updates the metadata of an object stored in the indexer. The object must already be pinned to
927
+ /// the indexer.
928
+ pub async fn update_object_metadata(&self, object: Arc<PinnedObject>) -> Result<(), Error> {
929
+ let object = object.object();
930
+ let sdk = self.inner.clone();
931
+ spawn(async move {
932
+ sdk.update_object_metadata(&object).await?;
933
+ Ok(())
934
+ })
935
+ .await?
936
+ }
937
+
938
+ /// Deletes an object from the indexer.
939
+ pub async fn delete_object(&self, key: String) -> Result<(), Error> {
940
+ let key = Hash256::from_str(key.as_str())?;
941
+ let sdk = self.inner.clone();
942
+ spawn(async move {
943
+ sdk.delete_object(&key).await?;
944
+ Ok(())
945
+ })
946
+ .await?
947
+ }
948
+
949
+ /// Returns metadata about a specific object stored in the indexer.
950
+ pub async fn object(&self, key: String) -> Result<PinnedObject, Error> {
951
+ let key = Hash256::from_str(key.as_str())?;
952
+ let sdk = self.inner.clone();
953
+ spawn(async move {
954
+ let obj = sdk.object(&key).await?;
955
+ Ok(PinnedObject {
956
+ inner: Arc::new(Mutex::new(obj)),
957
+ })
958
+ })
959
+ .await?
960
+ }
961
+
962
+ /// Returns metadata about a slab stored in the indexer.
963
+ pub async fn slab(&self, slab_id: String) -> Result<PinnedSlab, Error> {
964
+ let slab_id = Hash256::from_str(slab_id.as_str())?;
965
+ let sdk = self.inner.clone();
966
+ spawn(async move {
967
+ let slab = sdk.slab(&slab_id).await?;
968
+ Ok(slab.into())
969
+ })
970
+ .await?
971
+ }
972
+
973
+ /// Unpins slabs not used by any object on the account.
974
+ pub async fn prune_slabs(&self) -> Result<(), Error> {
975
+ let sdk = self.inner.clone();
976
+ spawn(async move {
977
+ sdk.prune_slabs().await?;
978
+ Ok(())
979
+ })
980
+ .await?
981
+ }
982
+
983
+ /// Returns the current account.
984
+ pub async fn account(&self) -> Result<Account, Error> {
985
+ let sdk = self.inner.clone();
986
+ spawn(async move {
987
+ let account = sdk.account().await?;
988
+ Ok(account.into())
989
+ })
990
+ .await?
991
+ }
992
+
993
+ /// Creates a signed URL that can be used to share object metadata
994
+ /// with other people using an indexer.
995
+ pub fn share_object(
996
+ &self,
997
+ object: Arc<PinnedObject>,
998
+ valid_until: SystemTime,
999
+ ) -> Result<String, Error> {
1000
+ let u = self
1001
+ .inner
1002
+ .share_object(&object.object(), valid_until.into())?;
1003
+ Ok(u.to_string())
1004
+ }
1005
+
1006
+ /// Retrieves a shared object from a signed URL.
1007
+ pub async fn shared_object(&self, shared_url: &str) -> Result<PinnedObject, Error> {
1008
+ let shared_url: Url = shared_url
1009
+ .parse()
1010
+ .map_err(|e| Error::Custom(format!("{e}")))?;
1011
+ let sdk = self.inner.clone();
1012
+ spawn(async move {
1013
+ let object = sdk.shared_object(shared_url).await?;
1014
+ Ok(PinnedObject {
1015
+ inner: Arc::new(Mutex::new(object)),
1016
+ })
1017
+ })
1018
+ .await?
1019
+ }
1020
+
1021
+ /// Pins an object to the indexer
1022
+ pub async fn pin_object(&self, object: Arc<PinnedObject>) -> Result<(), Error> {
1023
+ let sdk = self.inner.clone();
1024
+ spawn(async move {
1025
+ sdk.pin_object(&object.object()).await?;
1026
+ Ok(())
1027
+ })
1028
+ .await?
1029
+ }
1030
+ }
1031
+
1032
+ /// Calculates the encoded size of data given the original size and erasure coding parameters.
1033
+ #[uniffi::export]
1034
+ pub fn encoded_size(size: u64, data_shards: u8, parity_shards: u8) -> u64 {
1035
+ let total_shards = data_shards as u64 + parity_shards as u64;
1036
+ let slab_size = total_shards * SECTOR_SIZE as u64;
1037
+ let slabs = size.div_ceil(data_shards as u64 * SECTOR_SIZE as u64);
1038
+ slabs * slab_size
1039
+ }