mirror of
https://github.com/fluencelabs/rust-libp2p
synced 2025-06-21 13:51:33 +00:00
refactor: move examples to common location
Refactor examples into separate binary crates. Fixes https://github.com/libp2p/rust-libp2p/issues/3111. Pull-Request: #3509.
This commit is contained in:
206
examples/file-sharing/src/main.rs
Normal file
206
examples/file-sharing/src/main.rs
Normal file
@ -0,0 +1,206 @@
|
||||
// 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.
|
||||
|
||||
//! # File sharing example
|
||||
//!
|
||||
//! Basic file sharing application with peers either providing or locating and
|
||||
//! getting files by name.
|
||||
//!
|
||||
//! While obviously showcasing how to build a basic file sharing application,
|
||||
//! the actual goal of this example is **to show how to integrate rust-libp2p
|
||||
//! into a larger application**.
|
||||
//!
|
||||
//! ## Sample plot
|
||||
//!
|
||||
//! Assuming there are 3 nodes, A, B and C. A and B each provide a file while C
|
||||
//! retrieves a file.
|
||||
//!
|
||||
//! Provider nodes A and B each provide a file, file FA and FB respectively.
|
||||
//! They do so by advertising themselves as a provider for their file on a DHT
|
||||
//! via [`libp2p-kad`]. The two, among other nodes of the network, are
|
||||
//! interconnected via the DHT.
|
||||
//!
|
||||
//! Node C can locate the providers for file FA or FB on the DHT via
|
||||
//! [`libp2p-kad`] without being connected to the specific node providing the
|
||||
//! file, but any node of the DHT. Node C then connects to the corresponding
|
||||
//! node and requests the file content of the file via
|
||||
//! [`libp2p-request-response`].
|
||||
//!
|
||||
//! ## Architectural properties
|
||||
//!
|
||||
//! - Clean clonable async/await interface ([`Client`](network::Client)) to interact with the
|
||||
//! network layer.
|
||||
//!
|
||||
//! - Single task driving the network layer, no locks required.
|
||||
//!
|
||||
//! ## Usage
|
||||
//!
|
||||
//! A two node setup with one node providing the file and one node requesting the file.
|
||||
//!
|
||||
//! 1. Run command below in one terminal.
|
||||
//!
|
||||
//! ```sh
|
||||
//! cargo run -- --listen-address /ip4/127.0.0.1/tcp/40837 \
|
||||
//! --secret-key-seed 1 \
|
||||
//! provide \
|
||||
//! --path <path-to-your-file> \
|
||||
//! --name <name-for-others-to-find-your-file>
|
||||
//! ```
|
||||
//!
|
||||
//! 2. Run command below in another terminal.
|
||||
//!
|
||||
//! ```sh
|
||||
//! cargo run -- --peer /ip4/127.0.0.1/tcp/40837/p2p/12D3KooWPjceQrSwdWXPyLLeABRXmuqt69Rg3sBYbU1Nft9HyQ6X \
|
||||
//! get \
|
||||
//! --name <name-for-others-to-find-your-file>
|
||||
//! ```
|
||||
//!
|
||||
//! Note: The client does not need to be directly connected to the providing
|
||||
//! peer, as long as both are connected to some node on the same DHT.
|
||||
mod network;
|
||||
|
||||
use async_std::task::spawn;
|
||||
use clap::Parser;
|
||||
|
||||
use futures::prelude::*;
|
||||
use futures::StreamExt;
|
||||
use libp2p::{
|
||||
core::{Multiaddr, PeerId},
|
||||
multiaddr::Protocol,
|
||||
};
|
||||
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,
|
||||
},
|
||||
}
|
Reference in New Issue
Block a user