diff --git a/Cargo.lock b/Cargo.lock index 05939a5259b..efb909bfa49 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2621,12 +2621,14 @@ dependencies = [ "indicatif", "iroh", "iroh-base", + "iroh-blobs", "iroh-docs", "iroh-gossip", "iroh-io", "iroh-metrics", "iroh-net", "iroh-quinn", + "iroh-relay", "iroh-router", "iroh-test", "nested_enum_utils", diff --git a/iroh/Cargo.toml b/iroh/Cargo.toml index 04a49cee21b..e10bb092565 100644 --- a/iroh/Cargo.toml +++ b/iroh/Cargo.toml @@ -57,23 +57,25 @@ tempfile = "3.4" tokio = { version = "1", features = ["io-util", "rt"] } tokio-util = { version = "0.7", features = ["codec", "io-util", "io", "time"] } tracing = "0.1" +iroh-relay = { version = "0.28", path = "../iroh-relay" } +ref-cast = "1.0.23" # Examples clap = { version = "4", features = ["derive"], optional = true } indicatif = { version = "0.17", features = ["tokio"], optional = true } -ref-cast = "1.0.23" console = { version = "0.15.5", optional = true } +iroh-blobs = { version = "0.28", optional = true, features = ["rpc"] } # Documentation tests url = { version = "2.5.0", features = ["serde"] } serde-error = "0.1.3" [features] -default = ["metrics", "fs-store"] +default = ["metrics", "fs-store", "examples"] metrics = ["iroh-metrics"] fs-store = [] test = [] -examples = ["dep:clap", "dep:indicatif"] +examples = ["dep:clap", "dep:indicatif", "dep:iroh-blobs"] discovery-local-network = [ "iroh-net/discovery-local-network", "examples", @@ -106,22 +108,14 @@ rustdoc-args = ["--cfg", "iroh_docsrs"] [[example]] name = "hello-world-provide" +required-features = ["examples"] [[example]] name = "hello-world-fetch" - -[[example]] -name = "collection-provide" - -[[example]] -name = "collection-fetch" - -[[example]] -name = "rpc" required-features = ["examples"] [[example]] -name = "client" +name = "rpc" required-features = ["examples"] [[example]] diff --git a/iroh/examples/client.rs b/iroh/examples/client.rs deleted file mode 100644 index 4d2338012aa..00000000000 --- a/iroh/examples/client.rs +++ /dev/null @@ -1,43 +0,0 @@ -//! This example shows the shortest path to working with documents in iroh. This example creates a -//! document and sets an entry with key: "hello", value: "world". The document is completely local. -//! -//! The iroh node that creates the document is backed by an in-memory database and a random node ID -//! -//! run this example from the project root: -//! $ cargo run --features=examples --example client -use indicatif::HumanBytes; -use iroh::{base::base32, client::docs::Entry, docs::store::Query, node::Node}; -use tokio_stream::StreamExt; - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - let node = Node::memory().enable_docs().spawn().await?; - - // Could also use `node` directly, as it derefs to the client. - let client = node.client(); - - let blobs = client.blobs(); - let doc = client.docs().create().await?; - let author = client.authors().default().await?; - - doc.set_bytes(author, "hello", "world").await?; - - let mut stream = doc.get_many(Query::all()).await?; - while let Some(entry) = stream.try_next().await? { - println!("entry {}", fmt_entry(&entry)); - let content = blobs.read_to_bytes(entry.content_hash()).await?; - println!(" content {}", std::str::from_utf8(&content)?) - } - - Ok(()) -} - -fn fmt_entry(entry: &Entry) -> String { - let id = entry.id(); - let key = std::str::from_utf8(id.key()).unwrap_or(""); - let author = id.author().fmt_short(); - let hash = entry.content_hash(); - let hash = base32::fmt_short(hash.as_bytes()); - let len = HumanBytes(entry.content_len()); - format!("@{author}: {key} = {hash} ({len})",) -} diff --git a/iroh/examples/collection-fetch.rs b/iroh/examples/collection-fetch.rs deleted file mode 100644 index 98712a07f69..00000000000 --- a/iroh/examples/collection-fetch.rs +++ /dev/null @@ -1,94 +0,0 @@ -//! An example that fetches an iroh collection and prints the contents. -//! Will only work with collections that contain text, and is meant as a companion to the and `collection-provide` example. -//! -//! This is using an in memory database and a random node id. -//! Run the `collection-provide` example, which will give you instructions on how to run this example. -use std::{env, str::FromStr}; - -use anyhow::{bail, ensure, Context, Result}; -use iroh::{base::ticket::BlobTicket, blobs::BlobFormat}; -use tracing_subscriber::{prelude::*, EnvFilter}; - -// set the RUST_LOG env var to one of {debug,info,warn} to see logging info -pub fn setup_logging() { - tracing_subscriber::registry() - .with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr)) - .with(EnvFilter::from_default_env()) - .try_init() - .ok(); -} - -#[tokio::main] -async fn main() -> Result<()> { - setup_logging(); - println!("\ncollection fetch example!"); - // get the ticket - let args: Vec = env::args().collect(); - - if args.len() != 2 { - bail!("expected one argument [BLOB_TICKET]\n\nGet a ticket by running the follow command in a separate terminal:\n\n`cargo run --example collection-provide`"); - } - - // deserialize ticket string into a ticket - let ticket = - BlobTicket::from_str(&args[1]).context("failed parsing blob ticket\n\nGet a ticket by running the follow command in a separate terminal:\n\n`cargo run --example collection-provide`")?; - - // create a new node - let node = iroh::node::Node::memory().spawn().await?; - - println!("fetching hash: {}", ticket.hash()); - println!("node id: {}", node.node_id()); - println!("node listening addresses:"); - let addrs = node.net().node_addr().await?; - for addr in addrs.direct_addresses() { - println!("\t{:?}", addr); - } - println!( - "node relay server url: {:?}", - node.home_relay() - .expect("a default relay url should be provided") - .to_string() - ); - - // Get the content we have just fetched from the iroh database. - ensure!( - ticket.format() == BlobFormat::HashSeq, - "'collection' example expects to fetch a collection, but the ticket indicates a single blob." - ); - - // `download` returns a stream of `DownloadProgress` events. You can iterate through these updates to get progress - // on the state of your download. - let download_stream = node - .blobs() - .download_hash_seq(ticket.hash(), ticket.node_addr().clone()) - .await?; - - // You can also just `await` the stream, which poll the `DownloadProgress` stream for you. - let outcome = download_stream.await.context("unable to download hash")?; - - println!( - "\ndownloaded {} bytes from node {}", - outcome.downloaded_size, - ticket.node_addr().node_id - ); - - // If the `BlobFormat` is `HashSeq`, then we can assume for the example (and for any `HashSeq` that is derived from any iroh API), that it can be parsed as a `Collection` - // A `Collection` is a special `HashSeq`, where we preserve the names of any blobs added to the collection. (We do this by designating the first entry in the `Collection` as meta data.) - // To get the content of the collection, we first get the collection from the database using the `blobs` API - let collection = node - .blobs() - .get_collection(ticket.hash()) - .await - .context("expect hash with `BlobFormat::HashSeq` to be a collection")?; - - // Then we iterate through the collection, which gives us the name and hash of each entry in the collection. - for (name, hash) in collection.iter() { - println!("\nname: {name}, hash: {hash}"); - // Use the hash of the blob to get the content. - let content = node.blobs().read_to_bytes(*hash).await?; - let s = std::str::from_utf8(&content).context("unable to parse blob as as utf-8 string")?; - println!("{s}"); - } - - Ok(()) -} diff --git a/iroh/examples/collection-provide.rs b/iroh/examples/collection-provide.rs deleted file mode 100644 index 06d9f6dcfee..00000000000 --- a/iroh/examples/collection-provide.rs +++ /dev/null @@ -1,73 +0,0 @@ -//! An example that serves an iroh collection from memory. -//! -//! Since this is using the default iroh collection format, it can be downloaded -//! recursively using the iroh CLI. -//! -//! This is using an in memory database and a random node id. -//! run this example from the project root: -//! $ cargo run --example collection-provide -use iroh::blobs::{format::collection::Collection, util::SetTagOption, BlobFormat}; -use iroh_base::{node_addr::AddrInfoOptions, ticket::BlobTicket}; -use tracing_subscriber::{prelude::*, EnvFilter}; - -// set the RUST_LOG env var to one of {debug,info,warn} to see logging info -pub fn setup_logging() { - tracing_subscriber::registry() - .with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr)) - .with(EnvFilter::from_default_env()) - .try_init() - .ok(); -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - setup_logging(); - println!("\ncollection provide example!"); - - // create a new node - let node = iroh::node::Node::memory().spawn().await?; - - // Add two blobs - let blob1 = node.blobs().add_bytes("the first blob of bytes").await?; - let blob2 = node.blobs().add_bytes("the second blob of bytes").await?; - - // Create blobs from the data - let collection: Collection = [("blob1", blob1.hash), ("blob2", blob2.hash)] - .into_iter() - .collect(); - - // Create a collection - let (hash, _) = node - .blobs() - .create_collection(collection, SetTagOption::Auto, Default::default()) - .await?; - - // create a ticket - // tickets wrap all details needed to get a collection - let mut addr = node.net().node_addr().await?; - addr.apply_options(AddrInfoOptions::RelayAndAddresses); - let ticket = BlobTicket::new(addr, hash, BlobFormat::HashSeq)?; - - // print some info about the node - println!("serving hash: {}", ticket.hash()); - println!("node id: {}", ticket.node_addr().node_id); - println!("node listening addresses:"); - for addr in ticket.node_addr().direct_addresses() { - println!("\t{:?}", addr); - } - println!( - "node relay server url: {:?}", - ticket - .node_addr() - .relay_url() - .expect("a default relay url should be provided") - .to_string() - ); - // print the ticket, containing all the above information - println!("\nin another terminal, run:"); - println!("\tcargo run --example collection-fetch {}", ticket); - // block until SIGINT is received (ctrl+c) - tokio::signal::ctrl_c().await?; - node.shutdown().await?; - Ok(()) -} diff --git a/iroh/examples/custom-protocol.rs b/iroh/examples/custom-protocol.rs index b6bdd56d387..92130a4dffa 100644 --- a/iroh/examples/custom-protocol.rs +++ b/iroh/examples/custom-protocol.rs @@ -44,14 +44,13 @@ use anyhow::Result; use clap::Parser; use futures_lite::future::Boxed as BoxedFuture; use iroh::{ - blobs::Hash, - client::blobs, net::{ endpoint::{get_remote_node_id, Connecting}, Endpoint, NodeId, }, router::ProtocolHandler, }; +use iroh_base::hash::Hash; use tracing_subscriber::{prelude::*, EnvFilter}; #[derive(Debug, Parser)] diff --git a/iroh/examples/hammer.rs b/iroh/examples/hammer.rs deleted file mode 100644 index 64d1f7b1720..00000000000 --- a/iroh/examples/hammer.rs +++ /dev/null @@ -1,106 +0,0 @@ -//! The smallest possible example to spin up a node and serve a single blob. -//! -//! This is using an in memory database and a random node id. -//! run this example from the project root: -//! $ cargo run --example hello-world-provide -use std::str::FromStr; - -use anyhow::Context; -use iroh_base::{node_addr::AddrInfoOptions, ticket::BlobTicket}; -use iroh_net::{relay::RelayUrl, RelayMap, RelayMode}; -use tracing_subscriber::{prelude::*, EnvFilter}; - -// set the RUST_LOG env var to one of {debug,info,warn} to see logging info -pub fn setup_logging() { - tracing_subscriber::registry() - .with(tracing_subscriber::fmt::layer().with_writer(std::io::stderr)) - .with(EnvFilter::from_default_env()) - .try_init() - .ok(); -} - -#[tokio::main] -async fn main() -> anyhow::Result<()> { - setup_logging(); - println!("Hammer time!"); - - // get iterations from command line - let args: Vec = std::env::args().collect(); - let iterations = if args.len() == 2 { - args[1] - .parse::() - .context("failed to parse iterations")? - } else { - 10 - }; - - for i in 0..iterations { - // create a new node - println!("node: {}", i); - let relay_url = RelayUrl::from_str("http://localhost:3340").unwrap(); - let relay_map = RelayMap::from_url(relay_url.clone()); - tokio::task::spawn(async move { - let node = iroh::node::Node::memory() - .relay_mode(RelayMode::Custom(relay_map.clone())) - .spawn() - .await - .unwrap(); - - // add some data and remember the hash - let res = node.blobs().add_bytes("Hello, world!").await.unwrap(); - - // create a ticket - let mut addr = node.net().node_addr().await.unwrap(); - addr.apply_options(AddrInfoOptions::RelayAndAddresses); - let ticket = BlobTicket::new(addr, res.hash, res.format).unwrap(); - - tokio::task::spawn(async move { - let client_node = iroh::node::Node::memory() - .relay_mode(RelayMode::Custom(relay_map.clone())) - .spawn() - .await - .unwrap(); - - // `download` returns a stream of `DownloadProgress` events. You can iterate through these updates to get progress - // on the state of your download. - let download_stream = client_node - .blobs() - .download(ticket.hash(), ticket.node_addr().clone()) - .await - .unwrap(); - - // You can also just `await` the stream, which will poll the `DownloadProgress` stream for you. - let outcome = download_stream - .await - .context("unable to download hash") - .unwrap(); - - println!( - "\ndownloaded {} bytes from node {}", - outcome.downloaded_size, - ticket.node_addr().node_id - ); - - // Get the content we have just fetched from the iroh database. - - let bytes = client_node - .blobs() - .read_to_bytes(ticket.hash()) - .await - .unwrap(); - let s = std::str::from_utf8(&bytes) - .context("unable to parse blob as as utf-8 string") - .unwrap(); - println!("content: {}", s); - - tokio::time::sleep(std::time::Duration::from_secs(1)).await; - }); - - tokio::time::sleep(std::time::Duration::from_secs(5)).await; - node.shutdown().await.unwrap(); - }); - tokio::time::sleep(std::time::Duration::from_millis(5)).await; - } - tokio::signal::ctrl_c().await?; - Ok(()) -} diff --git a/iroh/examples/hello-world-fetch.rs b/iroh/examples/hello-world-fetch.rs index 06a62bbf239..7b09d340430 100644 --- a/iroh/examples/hello-world-fetch.rs +++ b/iroh/examples/hello-world-fetch.rs @@ -3,10 +3,13 @@ //! //! This is using an in memory database and a random node id. //! Run the `provide` example, which will give you instructions on how to run this example. -use std::{env, str::FromStr}; +use std::{env, str::FromStr, sync::Arc}; use anyhow::{bail, ensure, Context, Result}; -use iroh::{base::ticket::BlobTicket, blobs::BlobFormat}; +use iroh::base::ticket::BlobTicket; +use iroh_blobs::{ + downloader::Downloader, net_protocol::Blobs, util::local_pool::LocalPool, BlobFormat, +}; use tracing_subscriber::{prelude::*, EnvFilter}; // set the RUST_LOG env var to one of {debug,info,warn} to see logging info @@ -34,7 +37,20 @@ async fn main() -> Result<()> { BlobTicket::from_str(&args[1]).context("failed parsing blob ticket\n\nGet a ticket by running the follow command in a separate terminal:\n\n`cargo run --example hello-world-provide`")?; // create a new node - let node = iroh::node::Node::memory().spawn().await?; + let builder = iroh::node::Node::memory().build().await?; + let local_pool = LocalPool::default(); + let store = iroh_blobs::store::mem::Store::new(); + let downloader = Downloader::new(store.clone(), builder.endpoint(), local_pool.handle()); + let blobs = Blobs::new_with_events( + store, + local_pool.handle().clone(), + Default::default(), + downloader, + builder.endpoint(), + ); + let blobs_client = blobs.client(); + builder.accept(iroh_blobs::protocol::ALPN.to_vec(), Arc::new(blobs)); + let node = builder.spawn().await?; println!("fetching hash: {}", ticket.hash()); println!("node id: {}", node.node_id()); @@ -58,8 +74,7 @@ async fn main() -> Result<()> { // `download` returns a stream of `DownloadProgress` events. You can iterate through these updates to get progress // on the state of your download. - let download_stream = node - .blobs() + let download_stream = blobs_client .download(ticket.hash(), ticket.node_addr().clone()) .await?;