// Copyright 2021 Protocol Labs. // // Permission is hereby granted, free of charge, to any person obtaining a // copy of this software and associated documentation files (the "Software"), // to deal in the Software without restriction, including without limitation // the rights to use, copy, modify, merge, publish, distribute, sublicense, // and/or sell copies of the Software, and to permit persons to whom the // Software is furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS // OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER // DEALINGS IN THE SOFTWARE. #![doc = include_str!("../README.md")] mod network; use async_std::task::spawn; use clap::Parser; use futures::prelude::*; use futures::StreamExt; use libp2p::{core::Multiaddr, multiaddr::Protocol, PeerId}; use std::error::Error; use std::io::Write; use std::path::PathBuf; #[async_std::main] async fn main() -> Result<(), Box> { env_logger::init(); let opt = Opt::parse(); let (mut network_client, mut network_events, network_event_loop) = network::new(opt.secret_key_seed).await?; // Spawn the network task for it to run in the background. spawn(network_event_loop.run()); // In case a listen address was provided use it, otherwise listen on any // address. match opt.listen_address { Some(addr) => network_client .start_listening(addr) .await .expect("Listening not to fail."), None => network_client .start_listening("/ip4/0.0.0.0/tcp/0".parse()?) .await .expect("Listening not to fail."), }; // In case the user provided an address of a peer on the CLI, dial it. if let Some(addr) = opt.peer { let peer_id = match addr.iter().last() { Some(Protocol::P2p(hash)) => PeerId::from_multihash(hash).expect("Valid hash."), _ => return Err("Expect peer multiaddr to contain peer ID.".into()), }; network_client .dial(peer_id, addr) .await .expect("Dial to succeed"); } match opt.argument { // Providing a file. CliArgument::Provide { path, name } => { // Advertise oneself as a provider of the file on the DHT. network_client.start_providing(name.clone()).await; loop { match network_events.next().await { // Reply with the content of the file on incoming requests. Some(network::Event::InboundRequest { request, channel }) => { if request == name { network_client .respond_file(std::fs::read(&path)?, channel) .await; } } e => todo!("{:?}", e), } } } // Locating and getting a file. CliArgument::Get { name } => { // Locate all nodes providing the file. let providers = network_client.get_providers(name.clone()).await; if providers.is_empty() { return Err(format!("Could not find provider for file {name}.").into()); } // Request the content of the file from each node. let requests = providers.into_iter().map(|p| { let mut network_client = network_client.clone(); let name = name.clone(); async move { network_client.request_file(p, name).await }.boxed() }); // Await the requests, ignore the remaining once a single one succeeds. let file_content = futures::future::select_ok(requests) .await .map_err(|_| "None of the providers returned file.")? .0; std::io::stdout().write_all(&file_content)?; } } Ok(()) } #[derive(Parser, Debug)] #[clap(name = "libp2p file sharing example")] struct Opt { /// Fixed value to generate deterministic peer ID. #[clap(long)] secret_key_seed: Option, #[clap(long)] peer: Option, #[clap(long)] listen_address: Option, #[clap(subcommand)] argument: CliArgument, } #[derive(Debug, Parser)] enum CliArgument { Provide { #[clap(long)] path: PathBuf, #[clap(long)] name: String, }, Get { #[clap(long)] name: String, }, }