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.
- package/dist/index.d.ts +1 -0
- package/dist/index.js +179 -0
- package/package.json +29 -0
- package/template/CLAUDE.md +160 -0
- package/template/README.md +102 -0
- package/template/_gitignore +5 -0
- package/template/biome.json +40 -0
- package/template/index.html +13 -0
- package/template/package.json +30 -0
- package/template/rust/README.md +16 -0
- package/template/rust/sia-sdk-rs/.changeset/added_cancel_function_to_cancel_inflight_packed_uploads.md +6 -0
- package/template/rust/sia-sdk-rs/.changeset/check_if_we_have_enough_hosts_prior_to_encoding_in_upload_slabs.md +16 -0
- package/template/rust/sia-sdk-rs/.changeset/fix_slab_length_in_packed_object.md +5 -0
- package/template/rust/sia-sdk-rs/.changeset/fix_upload_racing_race_conditon.md +13 -0
- package/template/rust/sia-sdk-rs/.changeset/improved_parallelism_of_packed_uploads.md +5 -0
- package/template/rust/sia-sdk-rs/.changeset/progress_callback_will_now_be_called_as_expected_for_packed_uploads.md +5 -0
- package/template/rust/sia-sdk-rs/.github/dependabot.yml +10 -0
- package/template/rust/sia-sdk-rs/.github/workflows/main.yml +36 -0
- package/template/rust/sia-sdk-rs/.github/workflows/prepare-release.yml +34 -0
- package/template/rust/sia-sdk-rs/.github/workflows/release.yml +30 -0
- package/template/rust/sia-sdk-rs/.rustfmt.toml +4 -0
- package/template/rust/sia-sdk-rs/Cargo.lock +4127 -0
- package/template/rust/sia-sdk-rs/Cargo.toml +3 -0
- package/template/rust/sia-sdk-rs/LICENSE +21 -0
- package/template/rust/sia-sdk-rs/README.md +30 -0
- package/template/rust/sia-sdk-rs/indexd/CHANGELOG.md +79 -0
- package/template/rust/sia-sdk-rs/indexd/Cargo.toml +79 -0
- package/template/rust/sia-sdk-rs/indexd/benches/upload.rs +258 -0
- package/template/rust/sia-sdk-rs/indexd/src/app_client.rs +1710 -0
- package/template/rust/sia-sdk-rs/indexd/src/builder.rs +354 -0
- package/template/rust/sia-sdk-rs/indexd/src/download.rs +379 -0
- package/template/rust/sia-sdk-rs/indexd/src/hosts.rs +659 -0
- package/template/rust/sia-sdk-rs/indexd/src/lib.rs +827 -0
- package/template/rust/sia-sdk-rs/indexd/src/mock.rs +162 -0
- package/template/rust/sia-sdk-rs/indexd/src/object_encryption.rs +125 -0
- package/template/rust/sia-sdk-rs/indexd/src/quic.rs +575 -0
- package/template/rust/sia-sdk-rs/indexd/src/rhp4.rs +52 -0
- package/template/rust/sia-sdk-rs/indexd/src/slabs.rs +497 -0
- package/template/rust/sia-sdk-rs/indexd/src/upload.rs +629 -0
- package/template/rust/sia-sdk-rs/indexd/src/wasm_time.rs +41 -0
- package/template/rust/sia-sdk-rs/indexd/src/web_transport.rs +398 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/CHANGELOG.md +76 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/Cargo.toml +47 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/README.md +10 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/examples/python/example.py +130 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/bin/uniffi-bindgen.rs +3 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/builder.rs +377 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/io.rs +155 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/lib.rs +1039 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/logging.rs +58 -0
- package/template/rust/sia-sdk-rs/indexd_ffi/src/tls.rs +23 -0
- package/template/rust/sia-sdk-rs/indexd_wasm/Cargo.toml +33 -0
- package/template/rust/sia-sdk-rs/indexd_wasm/src/lib.rs +818 -0
- package/template/rust/sia-sdk-rs/knope.toml +54 -0
- package/template/rust/sia-sdk-rs/sia_derive/CHANGELOG.md +38 -0
- package/template/rust/sia-sdk-rs/sia_derive/Cargo.toml +19 -0
- package/template/rust/sia-sdk-rs/sia_derive/src/lib.rs +278 -0
- package/template/rust/sia-sdk-rs/sia_sdk/CHANGELOG.md +91 -0
- package/template/rust/sia-sdk-rs/sia_sdk/Cargo.toml +59 -0
- package/template/rust/sia-sdk-rs/sia_sdk/benches/merkle_root.rs +12 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/blake2.rs +22 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/consensus.rs +767 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v1.rs +257 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding/v2.rs +291 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding.rs +26 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async/v2.rs +367 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encoding_async.rs +6 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/encryption.rs +303 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/erasure_coding.rs +347 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/lib.rs +15 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/macros.rs +435 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/merkle.rs +112 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/merkle.rs +357 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/rpc.rs +1507 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp/types.rs +146 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/rhp.rs +7 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/seed.rs +278 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/signing.rs +236 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/common.rs +677 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/currency.rs +450 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/specifier.rs +110 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/spendpolicy.rs +778 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/utils.rs +117 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/v1.rs +1737 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/v2.rs +1726 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types/work.rs +59 -0
- package/template/rust/sia-sdk-rs/sia_sdk/src/types.rs +16 -0
- package/template/scripts/setup-rust.js +29 -0
- package/template/src/App.tsx +13 -0
- package/template/src/components/DevNote.tsx +21 -0
- package/template/src/components/auth/ApproveScreen.tsx +84 -0
- package/template/src/components/auth/AuthFlow.tsx +77 -0
- package/template/src/components/auth/ConnectScreen.tsx +214 -0
- package/template/src/components/auth/LoadingScreen.tsx +8 -0
- package/template/src/components/auth/RecoveryScreen.tsx +182 -0
- package/template/src/components/upload/UploadZone.tsx +314 -0
- package/template/src/index.css +9 -0
- package/template/src/lib/constants.ts +8 -0
- package/template/src/lib/format.ts +35 -0
- package/template/src/lib/hex.ts +13 -0
- package/template/src/lib/sdk.ts +25 -0
- package/template/src/lib/wasm-env.ts +5 -0
- package/template/src/main.tsx +12 -0
- package/template/src/stores/auth.ts +86 -0
- package/template/tsconfig.app.json +31 -0
- package/template/tsconfig.json +7 -0
- package/template/tsconfig.node.json +26 -0
- package/template/vite.config.ts +18 -0
- package/template/wasm/indexd_wasm/indexd_wasm.d.ts +309 -0
- package/template/wasm/indexd_wasm/indexd_wasm.js +1507 -0
- package/template/wasm/indexd_wasm/indexd_wasm_bg.wasm +0 -0
- package/template/wasm/indexd_wasm/package.json +31 -0
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
use std::collections::VecDeque;
|
|
2
|
+
use std::fmt::Debug;
|
|
3
|
+
use std::sync::Arc;
|
|
4
|
+
use std::time::Duration;
|
|
5
|
+
|
|
6
|
+
use log::debug;
|
|
7
|
+
use sia::encryption::{EncryptionKey, encrypt_shard};
|
|
8
|
+
use sia::erasure_coding::{self, ErasureCoder};
|
|
9
|
+
use sia::rhp::SEGMENT_SIZE;
|
|
10
|
+
use sia::signing::PrivateKey;
|
|
11
|
+
use thiserror::Error;
|
|
12
|
+
use tokio::io::AsyncWriteExt;
|
|
13
|
+
use tokio::sync::{OwnedSemaphorePermit, Semaphore, mpsc};
|
|
14
|
+
use tokio::task::JoinSet;
|
|
15
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
16
|
+
use tokio::task::spawn_blocking;
|
|
17
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
18
|
+
use tokio::time::error::Elapsed;
|
|
19
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
20
|
+
use tokio::time::sleep;
|
|
21
|
+
#[cfg(target_arch = "wasm32")]
|
|
22
|
+
use crate::wasm_time::sleep;
|
|
23
|
+
|
|
24
|
+
use crate::rhp4::RHP4Client;
|
|
25
|
+
use crate::{Hosts, Object, Sector};
|
|
26
|
+
|
|
27
|
+
/// Spawns a task on a [`JoinSet`]. Uses `spawn` on native (requires `Send`)
|
|
28
|
+
/// and `spawn_local` on WASM (runs on the current [`tokio::task::LocalSet`]).
|
|
29
|
+
macro_rules! join_set_spawn {
|
|
30
|
+
($set:expr, $fut:expr) => {{
|
|
31
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
32
|
+
$set.spawn($fut);
|
|
33
|
+
#[cfg(target_arch = "wasm32")]
|
|
34
|
+
$set.spawn_local($fut);
|
|
35
|
+
}};
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
#[derive(Debug, Error)]
|
|
39
|
+
pub enum DownloadError {
|
|
40
|
+
#[error("I/O error: {0}")]
|
|
41
|
+
Io(#[from] std::io::Error),
|
|
42
|
+
|
|
43
|
+
#[error("encoder error: {0}")]
|
|
44
|
+
Encoder(#[from] erasure_coding::Error),
|
|
45
|
+
|
|
46
|
+
#[error("not enough shards: {0}/{1}")]
|
|
47
|
+
NotEnoughShards(u8, u8),
|
|
48
|
+
|
|
49
|
+
#[error("invalid range: {0}-{1}")]
|
|
50
|
+
OutOfRange(usize, usize),
|
|
51
|
+
|
|
52
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
53
|
+
#[error("timeout error: {0}")]
|
|
54
|
+
Timeout(#[from] Elapsed),
|
|
55
|
+
|
|
56
|
+
#[error("semaphore error: {0}")]
|
|
57
|
+
SemaphoreError(#[from] tokio::sync::AcquireError),
|
|
58
|
+
|
|
59
|
+
#[error("join error: {0}")]
|
|
60
|
+
JoinError(#[from] tokio::task::JoinError),
|
|
61
|
+
|
|
62
|
+
#[error("invalid slab: {0}")]
|
|
63
|
+
InvalidSlab(String),
|
|
64
|
+
|
|
65
|
+
#[error("rhp4 error: {0}")]
|
|
66
|
+
Rhp4(#[from] crate::rhp4::Error),
|
|
67
|
+
|
|
68
|
+
#[error("custom error: {0}")]
|
|
69
|
+
Custom(String),
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
pub struct DownloadOptions {
|
|
73
|
+
/// Maximum number of concurrent sector downloads.
|
|
74
|
+
pub max_inflight: usize,
|
|
75
|
+
pub offset: u64,
|
|
76
|
+
pub length: Option<u64>,
|
|
77
|
+
|
|
78
|
+
/// Optional channel to notify when each slab is downloaded.
|
|
79
|
+
/// This can be used to implement progress reporting.
|
|
80
|
+
pub slab_downloaded: Option<mpsc::UnboundedSender<()>>,
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
impl Default for DownloadOptions {
|
|
84
|
+
fn default() -> Self {
|
|
85
|
+
Self {
|
|
86
|
+
#[cfg(target_arch = "wasm32")]
|
|
87
|
+
max_inflight: 2, // Browsers can't handle many concurrent WebTransport connections (no connection pooling)
|
|
88
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
89
|
+
max_inflight: 20,
|
|
90
|
+
offset: 0,
|
|
91
|
+
length: None,
|
|
92
|
+
slab_downloaded: None,
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
#[derive(Clone)]
|
|
98
|
+
pub(crate) struct Downloader {
|
|
99
|
+
account_key: Arc<PrivateKey>,
|
|
100
|
+
hosts: Hosts,
|
|
101
|
+
transport: Arc<dyn RHP4Client>,
|
|
102
|
+
#[cfg(target_arch = "wasm32")]
|
|
103
|
+
default_max_inflight: usize,
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
struct SectorDownloadTask {
|
|
107
|
+
sector: Sector,
|
|
108
|
+
offset: u64,
|
|
109
|
+
length: u64,
|
|
110
|
+
index: usize,
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
impl Downloader {
|
|
114
|
+
// helper to pair a sector with its erasure-coded index.
|
|
115
|
+
// Required because [FuturesUnordered.push] does not
|
|
116
|
+
// preserve ordering and doesn't play nice with closures.
|
|
117
|
+
async fn try_download_sector(
|
|
118
|
+
_permit: OwnedSemaphorePermit,
|
|
119
|
+
transport: Arc<dyn RHP4Client>,
|
|
120
|
+
account_key: Arc<PrivateKey>,
|
|
121
|
+
task: SectorDownloadTask,
|
|
122
|
+
) -> Result<(usize, Vec<u8>), DownloadError> {
|
|
123
|
+
let data = transport
|
|
124
|
+
.read_sector(
|
|
125
|
+
task.sector.host_key,
|
|
126
|
+
&account_key,
|
|
127
|
+
task.sector.root,
|
|
128
|
+
task.offset as usize,
|
|
129
|
+
task.length as usize,
|
|
130
|
+
)
|
|
131
|
+
.await?;
|
|
132
|
+
Ok((task.index, data.to_vec()))
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
136
|
+
pub fn new(hosts: Hosts, transport: Arc<dyn RHP4Client>, account_key: Arc<PrivateKey>) -> Self {
|
|
137
|
+
Self {
|
|
138
|
+
account_key,
|
|
139
|
+
hosts,
|
|
140
|
+
transport,
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
#[cfg(target_arch = "wasm32")]
|
|
145
|
+
pub fn new(
|
|
146
|
+
hosts: Hosts,
|
|
147
|
+
transport: Arc<dyn RHP4Client>,
|
|
148
|
+
account_key: Arc<PrivateKey>,
|
|
149
|
+
default_max_inflight: usize,
|
|
150
|
+
) -> Self {
|
|
151
|
+
Self {
|
|
152
|
+
account_key,
|
|
153
|
+
hosts,
|
|
154
|
+
transport,
|
|
155
|
+
default_max_inflight,
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
/// Returns default download options with the configured max_inflight.
|
|
160
|
+
#[cfg(target_arch = "wasm32")]
|
|
161
|
+
pub fn default_options(&self) -> DownloadOptions {
|
|
162
|
+
DownloadOptions {
|
|
163
|
+
max_inflight: self.default_max_inflight,
|
|
164
|
+
..Default::default()
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
/// Downloads the shards of an erasure-coded slab.
|
|
169
|
+
/// Successful shards will be decrypted using the
|
|
170
|
+
/// encryption_key.
|
|
171
|
+
///
|
|
172
|
+
/// offset and limit are the byte range to download
|
|
173
|
+
/// from each sector.
|
|
174
|
+
async fn download_slab_shards(
|
|
175
|
+
&self,
|
|
176
|
+
encryption_key: &EncryptionKey,
|
|
177
|
+
sectors: &[Sector],
|
|
178
|
+
min_shards: u8,
|
|
179
|
+
offset: u64,
|
|
180
|
+
length: u64,
|
|
181
|
+
max_inflight: usize,
|
|
182
|
+
) -> Result<Vec<Option<Vec<u8>>>, DownloadError> {
|
|
183
|
+
if sectors.len() < min_shards as usize {
|
|
184
|
+
return Err(DownloadError::InvalidSlab(format!(
|
|
185
|
+
"not enough sectors: have {}, need at least {}",
|
|
186
|
+
sectors.len(),
|
|
187
|
+
min_shards
|
|
188
|
+
)));
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
let semaphore = Arc::new(Semaphore::new(max_inflight));
|
|
192
|
+
let mut sectors = sectors
|
|
193
|
+
.iter()
|
|
194
|
+
.enumerate()
|
|
195
|
+
.map(|(index, s)| SectorDownloadTask {
|
|
196
|
+
sector: s.clone(),
|
|
197
|
+
offset,
|
|
198
|
+
length,
|
|
199
|
+
index,
|
|
200
|
+
})
|
|
201
|
+
.collect::<Vec<_>>();
|
|
202
|
+
self.hosts
|
|
203
|
+
.prioritize(&mut sectors, |task| &task.sector.host_key);
|
|
204
|
+
let total_shards = sectors.len();
|
|
205
|
+
let mut sectors = VecDeque::from(sectors);
|
|
206
|
+
let mut download_tasks = JoinSet::new();
|
|
207
|
+
for _ in 0..min_shards {
|
|
208
|
+
match sectors.pop_front() {
|
|
209
|
+
Some(task) => {
|
|
210
|
+
let permit = semaphore.clone().acquire_owned().await?;
|
|
211
|
+
let transport = self.transport.clone();
|
|
212
|
+
let account_key = self.account_key.clone();
|
|
213
|
+
join_set_spawn!(download_tasks, Self::try_download_sector(
|
|
214
|
+
permit,
|
|
215
|
+
transport,
|
|
216
|
+
account_key,
|
|
217
|
+
task,
|
|
218
|
+
));
|
|
219
|
+
}
|
|
220
|
+
None => panic!("not enough sectors to satisfy min_shards"), // should be unreachable
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
let mut successful: u8 = 0;
|
|
225
|
+
let mut shards = vec![None; total_shards];
|
|
226
|
+
let mut total_failures: usize = 0;
|
|
227
|
+
const MAX_TOTAL_FAILURES: usize = 30; // Give up after 30 failed attempts total
|
|
228
|
+
|
|
229
|
+
loop {
|
|
230
|
+
tokio::select! {
|
|
231
|
+
biased;
|
|
232
|
+
Some(res) = download_tasks.join_next() => {
|
|
233
|
+
match res? { // safe because tasks are never cancelled
|
|
234
|
+
Ok((index, mut data)) => {
|
|
235
|
+
let encryption_key = encryption_key.clone();
|
|
236
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
237
|
+
let data = spawn_blocking(move || {
|
|
238
|
+
encrypt_shard(&encryption_key, index as u8, offset as usize, &mut data);
|
|
239
|
+
data
|
|
240
|
+
}).await?;
|
|
241
|
+
#[cfg(target_arch = "wasm32")]
|
|
242
|
+
let data = {
|
|
243
|
+
encrypt_shard(&encryption_key, index as u8, offset as usize, &mut data);
|
|
244
|
+
data
|
|
245
|
+
};
|
|
246
|
+
shards[index] = Some(data);
|
|
247
|
+
successful += 1;
|
|
248
|
+
if successful >= min_shards {
|
|
249
|
+
return Ok(shards);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
Err(e) => {
|
|
253
|
+
total_failures += 1;
|
|
254
|
+
debug!("sector download failed ({total_failures}/{MAX_TOTAL_FAILURES}): {:?}", e);
|
|
255
|
+
|
|
256
|
+
if total_failures >= MAX_TOTAL_FAILURES {
|
|
257
|
+
return Err(DownloadError::NotEnoughShards(successful, min_shards));
|
|
258
|
+
}
|
|
259
|
+
|
|
260
|
+
let rem = min_shards.saturating_sub(successful);
|
|
261
|
+
if rem == 0 {
|
|
262
|
+
return Ok(shards); // sanity check
|
|
263
|
+
} else if download_tasks.len() + sectors.len() < rem as usize {
|
|
264
|
+
return Err(DownloadError::NotEnoughShards(successful, min_shards));
|
|
265
|
+
} else if download_tasks.len() <= rem as usize && let Some(task) = sectors.pop_front() {
|
|
266
|
+
let transport = self.transport.clone();
|
|
267
|
+
let account_key = self.account_key.clone();
|
|
268
|
+
let permit = semaphore.clone().acquire_owned().await?;
|
|
269
|
+
// only spawn additional download tasks if there
|
|
270
|
+
// are not enough to satisfy the required number
|
|
271
|
+
// of shards. The sleep arm will handle slow
|
|
272
|
+
// hosts.
|
|
273
|
+
join_set_spawn!(download_tasks, Self::try_download_sector(
|
|
274
|
+
permit,
|
|
275
|
+
transport,
|
|
276
|
+
account_key,
|
|
277
|
+
task,
|
|
278
|
+
));
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
}
|
|
282
|
+
},
|
|
283
|
+
_ = sleep(Duration::from_secs(1)) => {
|
|
284
|
+
if let Ok(racer_permit) = semaphore.clone().try_acquire_owned()
|
|
285
|
+
&& let Some(task) = sectors.pop_front() {
|
|
286
|
+
let transport = self.transport.clone();
|
|
287
|
+
let account_key = self.account_key.clone();
|
|
288
|
+
join_set_spawn!(download_tasks, Self::try_download_sector(
|
|
289
|
+
racer_permit,
|
|
290
|
+
transport,
|
|
291
|
+
account_key,
|
|
292
|
+
task,
|
|
293
|
+
));
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
/// Downloads the provided slabs and writes the decrypted data to the
|
|
301
|
+
/// provided writer.
|
|
302
|
+
pub async fn download<W: AsyncWriteExt + Unpin>(
|
|
303
|
+
&self,
|
|
304
|
+
w: &mut W,
|
|
305
|
+
object: &Object,
|
|
306
|
+
options: DownloadOptions,
|
|
307
|
+
) -> Result<(), DownloadError> {
|
|
308
|
+
let mut w = object.writer(w, options.offset as usize);
|
|
309
|
+
let mut offset = options.offset;
|
|
310
|
+
let max_length = object.size();
|
|
311
|
+
let mut length = options.length.unwrap_or(max_length);
|
|
312
|
+
if offset > max_length || length == 0 {
|
|
313
|
+
return Ok(());
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
for slab in object.slabs() {
|
|
317
|
+
if length == 0 {
|
|
318
|
+
break;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
let slab_length = slab.length as u64;
|
|
322
|
+
if offset >= slab_length {
|
|
323
|
+
offset -= slab_length;
|
|
324
|
+
continue;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// adjust slab range based on offset and length
|
|
328
|
+
let slab_offset = slab.offset as u64 + offset;
|
|
329
|
+
let slab_length = (slab_length - offset).min(length);
|
|
330
|
+
offset = 0;
|
|
331
|
+
|
|
332
|
+
// compute the sector aligned region to download
|
|
333
|
+
let chunk_size = SEGMENT_SIZE as u64 * slab.min_shards as u64;
|
|
334
|
+
let start = (slab_offset / chunk_size) * SEGMENT_SIZE as u64;
|
|
335
|
+
let end = (slab_offset + slab_length).div_ceil(chunk_size) * SEGMENT_SIZE as u64;
|
|
336
|
+
let shard_offset = start;
|
|
337
|
+
let shard_length = end - start;
|
|
338
|
+
|
|
339
|
+
let mut shards = self
|
|
340
|
+
.download_slab_shards(
|
|
341
|
+
&slab.encryption_key,
|
|
342
|
+
&slab.sectors,
|
|
343
|
+
slab.min_shards,
|
|
344
|
+
shard_offset,
|
|
345
|
+
shard_length,
|
|
346
|
+
options.max_inflight,
|
|
347
|
+
)
|
|
348
|
+
.await?;
|
|
349
|
+
let data_shards = slab.min_shards as usize;
|
|
350
|
+
let parity_shards = slab.sectors.len() - slab.min_shards as usize;
|
|
351
|
+
#[cfg(not(target_arch = "wasm32"))]
|
|
352
|
+
let shards = spawn_blocking(move || -> Result<Vec<Option<Vec<u8>>>, DownloadError> {
|
|
353
|
+
let rs = ErasureCoder::new(data_shards, parity_shards)?;
|
|
354
|
+
rs.reconstruct_data_shards(&mut shards)?;
|
|
355
|
+
Ok(shards)
|
|
356
|
+
})
|
|
357
|
+
.await??;
|
|
358
|
+
#[cfg(target_arch = "wasm32")]
|
|
359
|
+
let shards = {
|
|
360
|
+
let rs = ErasureCoder::new(data_shards, parity_shards)?;
|
|
361
|
+
rs.reconstruct_data_shards(&mut shards)?;
|
|
362
|
+
shards
|
|
363
|
+
};
|
|
364
|
+
ErasureCoder::write_data_shards(
|
|
365
|
+
&mut w,
|
|
366
|
+
&shards[..data_shards],
|
|
367
|
+
slab_offset as usize % (SEGMENT_SIZE * slab.min_shards as usize),
|
|
368
|
+
slab_length as usize,
|
|
369
|
+
)
|
|
370
|
+
.await?;
|
|
371
|
+
length -= slab_length;
|
|
372
|
+
if let Some(ref tx) = options.slab_downloaded {
|
|
373
|
+
let _ = tx.send(());
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
w.flush().await?;
|
|
377
|
+
Ok(())
|
|
378
|
+
}
|
|
379
|
+
}
|