1
1
use std:: path:: PathBuf ;
2
2
3
- use iroh:: { protocol:: Router , Endpoint } ;
3
+ use clap:: Parser ;
4
+ use iroh:: { discovery:: pkarr:: PkarrResolver , protocol:: Router , Endpoint } ;
4
5
use iroh_blobs:: { store:: mem:: MemStore , ticket:: BlobTicket , BlobsProtocol } ;
6
+ mod common;
7
+ use common:: setup_logging;
8
+ #[ derive( Debug , Parser ) ]
9
+ #[ command( version, about) ]
10
+ pub struct Cli {
11
+ #[ clap( subcommand) ]
12
+ command : Command ,
13
+ }
5
14
6
- #[ tokio:: main]
7
- async fn main ( ) -> anyhow:: Result < ( ) > {
15
+ #[ derive( Parser , Debug ) ]
16
+ pub enum Command {
17
+ /// Send a file to the network
18
+ Send {
19
+ /// Path to the file to send
20
+ file : PathBuf ,
21
+ } ,
22
+ /// Receive a file from the network
23
+ Receive {
24
+ /// Ticket describing the content to fetch
25
+ ticket : BlobTicket ,
26
+ /// Path to save the received file
27
+ filename : PathBuf ,
28
+ } ,
29
+ }
30
+
31
+ async fn send ( filename : PathBuf ) -> anyhow:: Result < ( ) > {
8
32
// Create an endpoint, it allows creating and accepting
9
33
// connections in the iroh p2p world
10
34
let endpoint = Endpoint :: builder ( ) . discovery_n0 ( ) . bind ( ) . await ?;
@@ -14,80 +38,82 @@ async fn main() -> anyhow::Result<()> {
14
38
// Then we initialize a struct that can accept blobs requests over iroh connections
15
39
let blobs = BlobsProtocol :: new ( & store, endpoint. clone ( ) , None ) ;
16
40
17
- // Grab all passed in arguments, the first one is the binary itself, so we skip it.
18
- let args: Vec < String > = std:: env:: args ( ) . skip ( 1 ) . collect ( ) ;
19
- // Convert to &str, so we can pattern-match easily:
20
- let arg_refs: Vec < & str > = args. iter ( ) . map ( String :: as_str) . collect ( ) ;
41
+ let abs_path = std:: path:: absolute ( & filename) ?;
42
+ let tag = store. blobs ( ) . add_path ( abs_path) . await ?;
43
+
44
+ let node_id = endpoint. node_id ( ) ;
45
+ let ticket = BlobTicket :: new ( node_id. into ( ) , tag. hash , tag. format ) ;
21
46
22
- match arg_refs. as_slice ( ) {
23
- [ "send" , filename] => {
24
- let filename: PathBuf = filename. parse ( ) ?;
25
- let abs_path = std:: path:: absolute ( & filename) ?;
47
+ println ! ( "File hashed. Fetch this file by running:" ) ;
48
+ println ! (
49
+ "cargo run --example transfer -- receive {ticket} {}" ,
50
+ filename. display( )
51
+ ) ;
26
52
27
- println ! ( "Hashing file." ) ;
53
+ // For sending files we build a router that accepts blobs connections & routes them
54
+ // to the blobs protocol.
55
+ let router = Router :: builder ( endpoint)
56
+ . accept ( iroh_blobs:: ALPN , blobs)
57
+ . spawn ( ) ;
28
58
29
- // When we import a blob, we get back a "tag" that refers to said blob in the store
30
- // and allows us to control when/if it gets garbage-collected
31
- let tag = store. blobs ( ) . add_path ( abs_path) . await ?;
59
+ tokio:: signal:: ctrl_c ( ) . await ?;
32
60
33
- let node_id = endpoint. node_id ( ) ;
34
- let ticket = BlobTicket :: new ( node_id. into ( ) , tag. hash , tag. format ) ;
61
+ // Gracefully shut down the node
62
+ println ! ( "Shutting down." ) ;
63
+ router. shutdown ( ) . await ?;
64
+ Ok ( ( ) )
65
+ }
35
66
36
- println ! ( "File hashed. Fetch this file by running:" ) ;
37
- println ! (
38
- "cargo run --example transfer -- receive {ticket} {}" ,
39
- filename. display( )
40
- ) ;
67
+ async fn receive ( ticket : BlobTicket , filename : PathBuf ) -> anyhow:: Result < ( ) > {
68
+ // Create a store to download blobs into
69
+ let store = MemStore :: new ( ) ;
41
70
42
- // For sending files we build a router that accepts blobs connections & routes them
43
- // to the blobs protocol.
44
- let router = Router :: builder ( endpoint)
45
- . accept ( iroh_blobs:: ALPN , blobs)
46
- . spawn ( ) ;
71
+ // Create an endpoint, it allows creating and accepting
72
+ // connections in the iroh p2p world.
73
+ //
74
+ // Since we just want to receive files, we don't need a stable node address
75
+ // or to publish our discovery information.
76
+ let endpoint = Endpoint :: builder ( )
77
+ . discovery ( PkarrResolver :: n0_dns ( ) )
78
+ . bind ( )
79
+ . await ?;
47
80
48
- tokio:: signal:: ctrl_c ( ) . await ?;
81
+ // For receiving files, we create a "downloader" that allows us to fetch files
82
+ // from other nodes via iroh connections
83
+ let downloader = store. downloader ( & endpoint) ;
49
84
50
- // Gracefully shut down the node
51
- println ! ( "Shutting down." ) ;
52
- router. shutdown ( ) . await ?;
53
- }
54
- [ "receive" , ticket, filename] => {
55
- let filename: PathBuf = filename. parse ( ) ?;
56
- let abs_path = std:: path:: absolute ( filename) ?;
57
- let ticket: BlobTicket = ticket. parse ( ) ?;
85
+ println ! ( "Starting download." ) ;
58
86
59
- // For receiving files, we create a " downloader" that allows us to fetch files
60
- // from other nodes via iroh connections
61
- let downloader = store . downloader ( & endpoint ) ;
87
+ downloader
88
+ . download ( ticket . hash ( ) , [ ticket . node_addr ( ) . node_id ] )
89
+ . await ? ;
62
90
63
- println ! ( "Starting download." ) ;
91
+ println ! ( "Finished download." ) ;
92
+ println ! ( "Copying to destination." ) ;
64
93
65
- downloader
66
- . download ( ticket. hash ( ) , Some ( ticket. node_addr ( ) . node_id ) )
67
- . await ?;
94
+ store. export ( ticket. hash ( ) , filename) . await ?;
68
95
69
- println ! ( "Finished download." ) ;
70
- println ! ( "Copying to destination." ) ;
96
+ println ! ( "Finished copying." ) ;
71
97
72
- store. blobs ( ) . export ( ticket. hash ( ) , abs_path) . await ?;
98
+ // Gracefully shut down the endpoint and the store
99
+ println ! ( "Shutting down." ) ;
100
+ endpoint. close ( ) . await ;
101
+ store. shutdown ( ) . await ?;
102
+ Ok ( ( ) )
103
+ }
73
104
74
- println ! ( "Finished copying." ) ;
105
+ #[ tokio:: main]
106
+ async fn main ( ) -> anyhow:: Result < ( ) > {
107
+ setup_logging ( ) ;
108
+ let cli = Cli :: parse ( ) ;
75
109
76
- // Gracefully shut down the node
77
- println ! ( "Shutting down." ) ;
78
- endpoint . close ( ) . await ;
110
+ match cli . command {
111
+ Command :: Send { file } => {
112
+ send ( file ) . await ? ;
79
113
}
80
- _ => {
81
- println ! ( "Couldn't parse command line arguments: {args:?}" ) ;
82
- println ! ( "Usage:" ) ;
83
- println ! ( " # to send:" ) ;
84
- println ! ( " cargo run --example transfer -- send [FILE]" ) ;
85
- println ! ( " # this will print a ticket." ) ;
86
- println ! ( ) ;
87
- println ! ( " # to receive:" ) ;
88
- println ! ( " cargo run --example transfer -- receive [TICKET] [FILE]" ) ;
114
+ Command :: Receive { ticket, filename } => {
115
+ receive ( ticket, filename) . await ?;
89
116
}
90
117
}
91
-
92
118
Ok ( ( ) )
93
119
}
0 commit comments