use std::path::{Path, PathBuf};
use anyhow::{ensure, Result};
use clap::{Parser, Subcommand};
use iroh::{
address_lookup::MdnsAddressLookup, protocol::Router, Endpoint, PublicKey, RelayMode, SecretKey,
};
use iroh_blobs::{store::mem::MemStore, BlobsProtocol, Hash};
mod common;
use common::{get_or_generate_secret_key, setup_logging};
#[derive(Debug, Parser)]
#[command(version, about)]
pub struct Cli {
#[clap(subcommand)]
command: Commands,
}
#[derive(Subcommand, Clone, Debug)]
pub enum Commands {
Accept {
path: PathBuf,
},
Connect {
endpoint_id: PublicKey,
hash: Hash,
#[clap(long, short)]
out: Option<PathBuf>,
},
}
async fn accept(path: &Path) -> Result<()> {
if !path.is_file() {
println!("Content must be a file.");
return Ok(());
}
let key = get_or_generate_secret_key()?;
println!("Starting iroh node with mdns address lookup...");
let endpoint = Endpoint::empty_builder(RelayMode::Default)
.secret_key(key)
.address_lookup(MdnsAddressLookup::builder())
.relay_mode(RelayMode::Disabled)
.bind()
.await?;
let builder = Router::builder(endpoint.clone());
let store = MemStore::new();
let blobs = BlobsProtocol::new(&store, None);
let builder = builder.accept(iroh_blobs::ALPN, blobs.clone());
let node = builder.spawn();
if !path.is_file() {
println!("Content must be a file.");
node.shutdown().await?;
return Ok(());
}
let absolute = path.canonicalize()?;
println!("Adding {} as {}...", path.display(), absolute.display());
let tag = store.add_path(absolute).await?;
println!(
"To fetch the blob:\n\tcargo run --example mdns-address_lookup --features examples -- connect {} {} -o [FILE_PATH]",
node.endpoint().id(),
tag.hash
);
tokio::signal::ctrl_c().await?;
node.shutdown().await?;
Ok(())
}
async fn connect(node_id: PublicKey, hash: Hash, out: Option<PathBuf>) -> Result<()> {
let key = SecretKey::generate(&mut rand::rng());
let address_lookup = MdnsAddressLookup::builder();
println!("Starting iroh node with mdns address_lookup...");
let endpoint = Endpoint::empty_builder(RelayMode::Disabled)
.secret_key(key)
.address_lookup(address_lookup)
.bind()
.await?;
let store = MemStore::new();
println!("NodeID: {}", endpoint.id());
let conn = endpoint.connect(node_id, iroh_blobs::ALPN).await?;
let stats = store.remote().fetch(conn, hash).await?;
println!(
"Fetched {} bytes for hash {}",
stats.payload_bytes_read, hash
);
if let Some(path) = out {
let absolute = std::env::current_dir()?.join(&path);
ensure!(!absolute.is_dir(), "output must not be a directory");
println!(
"exporting {hash} to {} -> {}",
path.display(),
absolute.display()
);
let size = store.export(hash, absolute).await?;
println!("Exported {size} bytes");
}
endpoint.close().await;
store.shutdown().await?;
Ok(())
}
#[tokio::main]
async fn main() -> anyhow::Result<()> {
setup_logging();
let cli = Cli::parse();
match &cli.command {
Commands::Accept { path } => {
accept(path).await?;
}
Commands::Connect {
endpoint_id,
hash,
out,
} => {
connect(*endpoint_id, *hash, out.clone()).await?;
}
}
Ok(())
}