Skip to content

Commit a8015d5

Browse files
committed
Extend multiprovider example with persistent local storage
1 parent dbf20de commit a8015d5

File tree

3 files changed

+98
-70
lines changed

3 files changed

+98
-70
lines changed

examples/multiprovider.rs

Lines changed: 24 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,8 @@ use console::Term;
66
use iroh::{NodeId, SecretKey};
77
use iroh_blobs::{
88
downloader2::{
9-
DownloadRequest, Downloader, ObserveEvent, ObserveRequest, StaticContentDiscovery,
9+
print_bitmap, DownloadRequest, Downloader, ObserveEvent, ObserveRequest,
10+
StaticContentDiscovery,
1011
},
1112
store::Store,
1213
util::total_bytes,
@@ -30,7 +31,11 @@ struct DownloadArgs {
3031
#[clap(help = "hash to download")]
3132
hash: Hash,
3233

34+
#[clap(help = "providers to download from")]
3335
providers: Vec<NodeId>,
36+
37+
#[clap(long, help = "path to save to")]
38+
path: Option<PathBuf>,
3439
}
3540

3641
#[derive(Debug, Parser)]
@@ -139,77 +144,28 @@ impl BlobDownloadProgress {
139144
}
140145
}
141146

142-
fn bitmap(current: &[ChunkNum], requested: &[ChunkNum], n: usize) -> String {
143-
// If n is 0, return an empty string.
144-
if n == 0 {
145-
return String::new();
146-
}
147-
148-
// Determine the overall bitfield size.
149-
// Since the ranges are sorted, we take the last element as the total size.
150-
let total = if let Some(&last) = requested.last() {
151-
last.0
152-
} else {
153-
// If there are no ranges, we assume the bitfield is empty.
154-
0
155-
};
156-
157-
// If total is 0, output n spaces.
158-
if total == 0 {
159-
return " ".repeat(n);
160-
}
161-
162-
let mut result = String::with_capacity(n);
163-
164-
// For each of the n output buckets:
165-
for bucket in 0..n {
166-
// Calculate the bucket's start and end in the overall bitfield.
167-
let bucket_start = bucket as u64 * total / n as u64;
168-
let bucket_end = (bucket as u64 + 1) * total / n as u64;
169-
let bucket_size = bucket_end.saturating_sub(bucket_start);
170-
171-
// Sum the number of bits that are set in this bucket.
172-
let mut set_bits = 0u64;
173-
for pair in current.chunks_exact(2) {
174-
let start = pair[0];
175-
let end = pair[1];
176-
// Determine the overlap between the bucket and the current range.
177-
let overlap_start = start.0.max(bucket_start);
178-
let overlap_end = end.0.min(bucket_end);
179-
if overlap_start < overlap_end {
180-
set_bits += overlap_end - overlap_start;
181-
}
147+
async fn download(args: DownloadArgs) -> anyhow::Result<()> {
148+
match &args.path {
149+
Some(path) => {
150+
tokio::fs::create_dir_all(path).await?;
151+
let store = iroh_blobs::store::fs::Store::load(path).await?;
152+
// make sure we properly shut down the store on ctrl-c
153+
let res = tokio::select! {
154+
x = download_impl(args, store.clone()) => x,
155+
_ = tokio::signal::ctrl_c() => Ok(()),
156+
};
157+
store.shutdown().await;
158+
res
159+
}
160+
None => {
161+
let store = iroh_blobs::store::mem::Store::new();
162+
download_impl(args, store).await
182163
}
183-
184-
// Calculate the fraction of the bucket that is set.
185-
let fraction = if bucket_size > 0 {
186-
set_bits as f64 / bucket_size as f64
187-
} else {
188-
0.0
189-
};
190-
191-
// Map the fraction to a grayscale character.
192-
let ch = if fraction == 0.0 {
193-
' ' // completely empty
194-
} else if fraction == 1.0 {
195-
'█' // completely full
196-
} else if fraction < 0.25 {
197-
'░'
198-
} else if fraction < 0.5 {
199-
'▒'
200-
} else {
201-
'▓'
202-
};
203-
204-
result.push(ch);
205164
}
206-
207-
result
208165
}
209166

210-
async fn download(args: DownloadArgs) -> anyhow::Result<()> {
167+
async fn download_impl<S: Store>(args: DownloadArgs, store: S) -> anyhow::Result<()> {
211168
let endpoint = iroh::Endpoint::builder().discovery_n0().bind().await?;
212-
let store = iroh_blobs::store::mem::Store::new();
213169
let discovery = StaticContentDiscovery::new(Default::default(), args.providers);
214170
let downloader = Downloader::builder(endpoint, store)
215171
.discovery(discovery)
@@ -233,7 +189,7 @@ async fn download(args: DownloadArgs) -> anyhow::Result<()> {
233189
progress.update(chunk);
234190
let current = progress.current.boundaries();
235191
let requested = progress.request.ranges.boundaries();
236-
let bitmap = bitmap(current, requested, rows as usize);
192+
let bitmap = print_bitmap(current, requested, rows as usize);
237193
print!("\r{bitmap}");
238194
if progress.is_done() {
239195
println!();

src/downloader2.rs

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -436,6 +436,75 @@ impl<S: Store> BitfieldSubscription for SimpleBitfieldSubscription<S> {
436436
}
437437
}
438438

439+
/// Print a bitmap
440+
pub fn print_bitmap(current: &[ChunkNum], requested: &[ChunkNum], n: usize) -> String {
441+
// If n is 0, return an empty string.
442+
if n == 0 {
443+
return String::new();
444+
}
445+
446+
// Determine the overall bitfield size.
447+
// Since the ranges are sorted, we take the last element as the total size.
448+
let total = if let Some(&last) = requested.last() {
449+
last.0
450+
} else {
451+
// If there are no ranges, we assume the bitfield is empty.
452+
0
453+
};
454+
455+
// If total is 0, output n spaces.
456+
if total == 0 {
457+
return " ".repeat(n);
458+
}
459+
460+
let mut result = String::with_capacity(n);
461+
462+
// For each of the n output buckets:
463+
for bucket in 0..n {
464+
// Calculate the bucket's start and end in the overall bitfield.
465+
let bucket_start = bucket as u64 * total / n as u64;
466+
let bucket_end = (bucket as u64 + 1) * total / n as u64;
467+
let bucket_size = bucket_end.saturating_sub(bucket_start);
468+
469+
// Sum the number of bits that are set in this bucket.
470+
let mut set_bits = 0u64;
471+
for pair in current.chunks_exact(2) {
472+
let start = pair[0];
473+
let end = pair[1];
474+
// Determine the overlap between the bucket and the current range.
475+
let overlap_start = start.0.max(bucket_start);
476+
let overlap_end = end.0.min(bucket_end);
477+
if overlap_start < overlap_end {
478+
set_bits += overlap_end - overlap_start;
479+
}
480+
}
481+
482+
// Calculate the fraction of the bucket that is set.
483+
let fraction = if bucket_size > 0 {
484+
set_bits as f64 / bucket_size as f64
485+
} else {
486+
0.0
487+
};
488+
489+
// Map the fraction to a grayscale character.
490+
let ch = if fraction == 0.0 {
491+
' ' // completely empty
492+
} else if fraction == 1.0 {
493+
'█' // completely full
494+
} else if fraction < 0.25 {
495+
'░'
496+
} else if fraction < 0.5 {
497+
'▒'
498+
} else {
499+
'▓'
500+
};
501+
502+
result.push(ch);
503+
}
504+
505+
result
506+
}
507+
439508
#[cfg(test)]
440509
mod tests {
441510
#![allow(clippy::single_range_in_vec_init)]

src/downloader2/state.rs

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -262,7 +262,9 @@ impl DownloaderState {
262262
PeerBlobState::new(subscription_id),
263263
);
264264
}
265-
if let std::collections::btree_map::Entry::Vacant(e) = self.discovery.entry(request.hash) {
265+
if let std::collections::btree_map::Entry::Vacant(e) =
266+
self.discovery.entry(request.hash)
267+
{
266268
// start a discovery task
267269
let id = self.discovery_id_gen.next();
268270
evs.push(Event::StartDiscovery { hash, id });
@@ -825,7 +827,8 @@ impl Downloads {
825827
id: PeerDownloadId,
826828
) -> Option<(&DownloadId, &mut DownloadState)> {
827829
self.by_id
828-
.iter_mut().find(|(_, v)| v.peer_downloads.iter().any(|(_, state)| state.id == id))
830+
.iter_mut()
831+
.find(|(_, v)| v.peer_downloads.iter().any(|(_, state)| state.id == id))
829832
}
830833
}
831834

0 commit comments

Comments
 (0)