use futures::pin_mut;
use futures::stream::StreamExt; use ipfs::{Error, Ipfs, IpfsOptions, IpfsPath, MultiaddrWithPeerId, TestTypes, UninitializedIpfs};
use std::env;
use std::process::exit;
use tokio::io::AsyncWriteExt;
#[tokio::main]
async fn main() {
tracing_subscriber::fmt::init();
let (bootstrappers, path, target) = match parse_options() {
Ok(Some(tuple)) => tuple,
Ok(None) => {
eprintln!(
"Usage: fetch_and_cat [--default-bootstrappers] <IPFS_PATH | CID> [MULTIADDR]"
);
eprintln!();
eprintln!(
"Example will try to find the file by the given IPFS_PATH and print its contents to stdout."
);
eprintln!();
eprintln!("The example has three modes in the order of precedence:");
eprintln!(
"1. When --default-bootstrappers is given, use default bootstrappers to find the content"
);
eprintln!(
"2. When IPFS_PATH and MULTIADDR are given, connect to MULTIADDR to get the file"
);
eprintln!(
"3. When only IPFS_PATH is given, wait to be connected to by another ipfs node"
);
exit(0);
}
Err(e) => {
eprintln!("Invalid argument: {:?}", e);
exit(1);
}
};
let mut opts = IpfsOptions::inmemory_with_generated_keys();
opts.mdns = false;
let (ipfs, fut): (Ipfs<TestTypes>, _) = UninitializedIpfs::new(opts).start().await.unwrap();
tokio::task::spawn(fut);
if bootstrappers == BootstrapperOption::RestoreDefault {
ipfs.restore_bootstrappers().await.unwrap();
} else if let Some(target) = target {
ipfs.connect(target).await.unwrap();
} else {
let (_, addresses) = ipfs.identity().await.unwrap();
assert!(!addresses.is_empty(), "Zero listening addresses");
eprintln!("Please connect an ipfs node having {} to:\n", path);
for address in addresses {
eprintln!(" - {}", address);
}
eprintln!();
}
let stream = ipfs.cat_unixfs(path, None).await.unwrap_or_else(|e| {
eprintln!("Error: {}", e);
exit(1);
});
pin_mut!(stream);
let mut stdout = tokio::io::stdout();
loop {
match stream.next().await {
Some(Ok(bytes)) => {
stdout.write_all(&bytes).await.unwrap();
}
Some(Err(e)) => {
eprintln!("Error: {}", e);
exit(1);
}
None => break,
}
}
}
#[derive(PartialEq)]
enum BootstrapperOption {
RestoreDefault,
ConnectionsOnly,
}
fn parse_options(
) -> Result<Option<(BootstrapperOption, IpfsPath, Option<MultiaddrWithPeerId>)>, Error> {
let mut args = env::args().skip(1).peekable();
let mut bootstrappers = BootstrapperOption::ConnectionsOnly;
while let Some(option) = args.peek() {
if !option.starts_with("--") {
break;
}
let option = args.next().expect("already checked when peeking");
if option == "--default-bootstrappers" {
bootstrappers = BootstrapperOption::RestoreDefault;
} else {
return Err(anyhow::format_err!("unknown option: {}", option));
}
}
let path = if let Some(path) = args.next() {
path.parse::<IpfsPath>()
.map_err(|e| e.context(format!("failed to parse {:?} as IpfsPath", path)))?
} else {
return Ok(None);
};
let target = if let Some(multiaddr) = args.next() {
let ma = multiaddr.parse::<MultiaddrWithPeerId>().map_err(|e| {
Error::new(e).context(format!(
"failed to parse {:?} as MultiaddrWithPeerId",
multiaddr
))
})?;
Some(ma)
} else {
None
};
Ok(Some((bootstrappers, path, target)))
}