file_sharing_example/
main.rs

1// Copyright 2021 Protocol Labs.
2//
3// Permission is hereby granted, free of charge, to any person obtaining a
4// copy of this software and associated documentation files (the "Software"),
5// to deal in the Software without restriction, including without limitation
6// the rights to use, copy, modify, merge, publish, distribute, sublicense,
7// and/or sell copies of the Software, and to permit persons to whom the
8// Software is furnished to do so, subject to the following conditions:
9//
10// The above copyright notice and this permission notice shall be included in
11// all copies or substantial portions of the Software.
12//
13// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
14// OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
18// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
19// DEALINGS IN THE SOFTWARE.
20
21#![doc = include_str!("../README.md")]
22
23mod network;
24
25use std::{error::Error, io::Write, path::PathBuf};
26
27use clap::Parser;
28use futures::{prelude::*, StreamExt};
29use libp2p::{core::Multiaddr, multiaddr::Protocol};
30use tokio::task::spawn;
31use tracing_subscriber::EnvFilter;
32
33#[tokio::main]
34async fn main() -> Result<(), Box<dyn Error>> {
35    let _ = tracing_subscriber::fmt()
36        .with_env_filter(EnvFilter::from_default_env())
37        .try_init();
38
39    let opt = Opt::parse();
40
41    let (mut network_client, mut network_events, network_event_loop) =
42        network::new(opt.secret_key_seed).await?;
43
44    // Spawn the network task for it to run in the background.
45    spawn(network_event_loop.run());
46
47    // In case a listen address was provided use it, otherwise listen on any
48    // address.
49    match opt.listen_address {
50        Some(addr) => network_client
51            .start_listening(addr)
52            .await
53            .expect("Listening not to fail."),
54        None => network_client
55            .start_listening("/ip4/0.0.0.0/tcp/0".parse()?)
56            .await
57            .expect("Listening not to fail."),
58    };
59
60    // In case the user provided an address of a peer on the CLI, dial it.
61    if let Some(addr) = opt.peer {
62        let Some(Protocol::P2p(peer_id)) = addr.iter().last() else {
63            return Err("Expect peer multiaddr to contain peer ID.".into());
64        };
65        network_client
66            .dial(peer_id, addr)
67            .await
68            .expect("Dial to succeed");
69    }
70
71    match opt.argument {
72        // Providing a file.
73        CliArgument::Provide { path, name } => {
74            // Advertise oneself as a provider of the file on the DHT.
75            network_client.start_providing(name.clone()).await;
76
77            loop {
78                match network_events.next().await {
79                    // Reply with the content of the file on incoming requests.
80                    Some(network::Event::InboundRequest { request, channel }) => {
81                        if request == name {
82                            network_client
83                                .respond_file(std::fs::read(&path)?, channel)
84                                .await;
85                        }
86                    }
87                    e => todo!("{:?}", e),
88                }
89            }
90        }
91        // Locating and getting a file.
92        CliArgument::Get { name } => {
93            // Locate all nodes providing the file.
94            let providers = network_client.get_providers(name.clone()).await;
95            if providers.is_empty() {
96                return Err(format!("Could not find provider for file {name}.").into());
97            }
98
99            // Request the content of the file from each node.
100            let requests = providers.into_iter().map(|p| {
101                let mut network_client = network_client.clone();
102                let name = name.clone();
103                async move { network_client.request_file(p, name).await }.boxed()
104            });
105
106            // Await the requests, ignore the remaining once a single one succeeds.
107            let file_content = futures::future::select_ok(requests)
108                .await
109                .map_err(|_| "None of the providers returned file.")?
110                .0;
111
112            std::io::stdout().write_all(&file_content)?;
113        }
114    }
115
116    Ok(())
117}
118
119#[derive(Parser, Debug)]
120#[command(name = "libp2p file sharing example")]
121struct Opt {
122    /// Fixed value to generate deterministic peer ID.
123    #[arg(long)]
124    secret_key_seed: Option<u8>,
125
126    #[arg(long)]
127    peer: Option<Multiaddr>,
128
129    #[arg(long)]
130    listen_address: Option<Multiaddr>,
131
132    #[command(subcommand)]
133    argument: CliArgument,
134}
135
136#[derive(Debug, Parser)]
137enum CliArgument {
138    Provide {
139        #[arg(long)]
140        path: PathBuf,
141        #[arg(long)]
142        name: String,
143    },
144    Get {
145        #[arg(long)]
146        name: String,
147    },
148}