Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 80 additions & 2 deletions src/core/protocol/catalog.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ use iroh::{
protocol::{AcceptError, ProtocolHandler},
Endpoint, EndpointId,
};
use iroh_blobs::{store::fs::FsStore, BlobFormat, Hash};
use iroh_blobs::{
api::blobs::BlobStatus, format::collection::Collection, store::fs::FsStore, BlobFormat, Hash,
};
use iroh_rings::{Permission, Registry};

use crate::core::{GrantStore, Privilege, ShareTicket};
Expand Down Expand Up @@ -67,6 +69,10 @@ pub struct CatalogEntry {
pub name: String,
/// Transfer ticket that can be passed to the local node to download this blob.
pub ticket: ShareTicket,
/// Number of files in the collection; `None` for raw blobs or when unavailable.
pub file_count: Option<usize>,
/// Total content size in bytes; `None` when the information is unavailable.
pub total_size: Option<u64>,
}

/// iroh [`ProtocolHandler`] for the catalog protocol.
Expand Down Expand Up @@ -152,7 +158,45 @@ impl<R: Registry + Clone + Send + Sync + 'static> CatalogHandler<R> {
let ticket =
ShareTicket::from_format(addr.clone(), info.hash, info.format, Some(name.clone()));
let ticket_uri = ticket.to_uri()?;
write_entry(send, info.hash, info.format, &name, &ticket_uri).await?;

let (file_count, total_size) = match info.format {
BlobFormat::Raw => {
let size = match self.store.blobs().status(info.hash).await {
Ok(BlobStatus::Complete { size }) => Some(size),
_ => None,
};
(None, size)
}
BlobFormat::HashSeq => match Collection::load(info.hash, &*self.store).await {
Ok(collection) => {
let fc = collection.iter().count();
let mut sum: u64 = 0;
let mut complete = true;
for (_, fhash) in collection.iter() {
match self.store.blobs().status(*fhash).await {
Ok(BlobStatus::Complete { size }) => sum += size,
_ => {
complete = false;
break;
}
}
}
(Some(fc), if complete { Some(sum) } else { None })
}
Err(_) => (None, None),
},
};

write_entry(
send,
info.hash,
info.format,
&name,
&ticket_uri,
file_count,
total_size,
)
.await?;
}

send.finish()?;
Expand Down Expand Up @@ -219,11 +263,31 @@ pub(crate) async fn decode_entries(
let ticket_uri = String::from_utf8(ticket_bytes).context("entry ticket is not UTF-8")?;
let ticket = ShareTicket::from_uri(&ticket_uri)?;

let mut size_buf = [0u8; 8];
recv.read_exact(&mut size_buf)
.await
.context("reading total_size")?;
let total_size = match u64::from_le_bytes(size_buf) {
u64::MAX => None,
v => Some(v),
};

let mut count_buf = [0u8; 4];
recv.read_exact(&mut count_buf)
.await
.context("reading file_count")?;
let file_count = match u32::from_le_bytes(count_buf) {
u32::MAX => None,
v => Some(v as usize),
};

entries.push(CatalogEntry {
hash,
format,
name,
ticket,
file_count,
total_size,
});
}
Ok(entries)
Expand All @@ -235,6 +299,8 @@ async fn write_entry(
format: BlobFormat,
name: &str,
ticket_uri: &str,
file_count: Option<usize>,
total_size: Option<u64>,
) -> Result<()> {
send.write_all(hash.as_bytes())
.await
Expand All @@ -252,6 +318,18 @@ async fn write_entry(
write_length_prefixed(send, ticket_uri.as_bytes())
.await
.context("writing ticket")?;
// u64::MAX sentinel = unknown size; u32::MAX sentinel = not applicable / unknown count.
send.write_all(&total_size.unwrap_or(u64::MAX).to_le_bytes())
.await
.context("writing total_size")?;
send.write_all(
&file_count
.map(|c| c as u32)
.unwrap_or(u32::MAX)
.to_le_bytes(),
)
.await
.context("writing file_count")?;
Ok(())
}

Expand Down
17 changes: 2 additions & 15 deletions src/daemon/server/handlers/blob.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ use uuid::Uuid;
use crate::core::Node;
use crate::daemon::protocol::Event;

use crate::util::format_size;

use super::{format_ring, resolve_target, send};

pub(crate) async fn handle_import<R: Registry + Clone + Send + Sync + 'static>(
Expand Down Expand Up @@ -205,21 +207,6 @@ pub(crate) async fn handle_blob_list<R: Registry + Clone + Send + Sync + 'static
Ok(())
}

fn format_size(bytes: u64) -> String {
const KIB: u64 = 1024;
const MIB: u64 = 1024 * KIB;
const GIB: u64 = 1024 * MIB;
if bytes >= GIB {
format!("{:.1} GiB", bytes as f64 / GIB as f64)
} else if bytes >= MIB {
format!("{:.1} MiB", bytes as f64 / MIB as f64)
} else if bytes >= KIB {
format!("{:.1} KiB", bytes as f64 / KIB as f64)
} else {
format!("{bytes} B")
}
}

pub(crate) async fn handle_blob_remove<R: Registry + Clone + Send + Sync + 'static>(
req_id: Uuid,
node: &Node<R>,
Expand Down
26 changes: 25 additions & 1 deletion src/daemon/server/handlers/remote.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ use uuid::Uuid;

use crate::core::Node;
use crate::daemon::protocol::Event;
use crate::util::parse_peer_id;
use crate::util::{format_size, parse_peer_id};

use super::send;

Expand All @@ -37,12 +37,33 @@ pub(crate) async fn handle_remote_blob_list<R: Registry + Clone + Send + Sync +
send(tx, Event::line(req_id, format!("{} blobs:", entries.len()))).await;
for entry in entries {
let ticket_str = entry.ticket.to_uri()?;

let kind_str = match entry.format {
iroh_blobs::BlobFormat::HashSeq => {
let count = entry
.file_count
.map(|n| format!("{n} files"))
.unwrap_or_else(|| "dir".into());
format!("dir, {count}")
}
_ => "file".into(),
};
let size_str = entry
.total_size
.map(format_size)
.unwrap_or_else(|| "?".into());

send(tx, Event::blank(req_id)).await;
send(
tx,
Event::line(req_id, format!(" {} ({})", entry.hash, entry.name)),
)
.await;
send(
tx,
Event::line(req_id, format!(" kind: {kind_str} ({size_str})")),
)
.await;
send(tx, Event::line(req_id, format!(" ticket: {ticket_str}"))).await;
send(
tx,
Expand All @@ -51,6 +72,9 @@ pub(crate) async fn handle_remote_blob_list<R: Registry + Clone + Send + Sync +
serde_json::json!({
"hash": entry.hash.to_string(),
"name": entry.name,
"kind": kind_str,
"file_count": entry.file_count,
"size_bytes": entry.total_size,
"ticket": ticket_str,
}),
),
Expand Down
16 changes: 16 additions & 0 deletions src/util.rs
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,22 @@ pub(crate) fn display_peer(peer: &EndpointId, store: &PeerStore) -> String {
format_peer_entry(peer, nick.as_deref())
}

/// Formats a byte count as a human-readable string (B / KiB / MiB / GiB).
pub(crate) fn format_size(bytes: u64) -> String {
const KIB: u64 = 1024;
const MIB: u64 = 1024 * KIB;
const GIB: u64 = 1024 * MIB;
if bytes >= GIB {
format!("{:.1} GiB", bytes as f64 / GIB as f64)
} else if bytes >= MIB {
format!("{:.1} MiB", bytes as f64 / MIB as f64)
} else if bytes >= KIB {
format!("{:.1} KiB", bytes as f64 / KIB as f64)
} else {
format!("{bytes} B")
}
}

/// Prints the ringdrop startup banner with version to stdout.
///
/// The giraffe mascot is rendered in yellow on the left; the "ringdrop" text
Expand Down
Loading