Files
rust-libp2p/examples/file-sharing/src/main.rs

150 lines
4.9 KiB
Rust
Raw Normal View History

// 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<dyn Error>> {
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<u8>,
#[clap(long)]
peer: Option<Multiaddr>,
#[clap(long)]
listen_address: Option<Multiaddr>,
#[clap(subcommand)]
argument: CliArgument,
}
#[derive(Debug, Parser)]
enum CliArgument {
Provide {
#[clap(long)]
path: PathBuf,
#[clap(long)]
name: String,
},
Get {
#[clap(long)]
name: String,
},
}