veloren/network/examples/fileshare/main.rs

203 lines
7.7 KiB
Rust
Raw Normal View History

#![feature(async_closure, exclusive_range_pattern)]
//!run with
//! (cd network/examples/fileshare && RUST_BACKTRACE=1 cargo run
//! --profile=release -Z unstable-options -- --trace=info --port 15006)
//! (cd network/examples/fileshare && RUST_BACKTRACE=1 cargo run
//! --profile=release -Z unstable-options -- --trace=info --port 15007) ```
2022-04-02 13:44:07 +00:00
use clap::{Command, Arg};
use std::{path::PathBuf, sync::Arc, thread, time::Duration};
use tokio::{io, io::AsyncBufReadExt, runtime::Runtime, sync::mpsc};
use tracing::*;
use tracing_subscriber::EnvFilter;
use veloren_network::{ConnectAddr, ListenAddr};
mod commands;
mod server;
use commands::{FileInfo, LocalCommand};
use server::Server;
fn main() {
2022-04-02 13:44:07 +00:00
let matches = Command::new("File Server")
.version("0.1.0")
.author("Marcel Märtens <marcel.cochem@googlemail.com>")
.about("example file server implemented with veloren-network")
.arg(
2022-04-02 13:44:07 +00:00
Arg::new("port")
.short('p')
.long("port")
.takes_value(true)
.default_value("15006")
.help("port to listen on"),
)
.arg(
2022-04-02 13:44:07 +00:00
Arg::new("trace")
.short('t')
.long("trace")
.takes_value(true)
.default_value("warn")
.possible_values(&["trace", "debug", "info", "warn", "error"])
.help("set trace level, not this has a performance impact!"),
)
.get_matches();
let trace = matches.value_of("trace").unwrap();
let filter = EnvFilter::from_default_env()
.add_directive(trace.parse().unwrap())
.add_directive("fileshare::server=trace".parse().unwrap())
.add_directive("fileshare::commands=trace".parse().unwrap());
tracing_subscriber::FmtSubscriber::builder()
.with_max_level(Level::TRACE)
.with_env_filter(filter)
.init();
let port: u16 = matches.value_of("port").unwrap().parse().unwrap();
let address = ListenAddr::Tcp(format!("{}:{}", "127.0.0.1", port).parse().unwrap());
let runtime = Arc::new(Runtime::new().unwrap());
let (server, cmd_sender) = Server::new(Arc::clone(&runtime));
runtime.spawn(server.run(address));
thread::sleep(Duration::from_millis(50)); //just for trace
runtime.block_on(client(cmd_sender));
}
2022-04-02 13:44:07 +00:00
fn file_exists(file: &str) -> Result<(), String> {
let file: std::path::PathBuf = shellexpand::tilde(file).parse().unwrap();
if file.exists() {
Ok(())
} else {
Err("File does not exist".to_string())
}
}
2022-04-02 13:44:07 +00:00
fn get_options<'a>() -> Command<'a> {
Command::new("")
.no_binary_name(true)
.subcommand_required(true)
.arg_required_else_help(true)
.color(clap::ColorChoice::Auto)
.subcommand(Command::new("quit").about("closes program"))
.subcommand(Command::new("disconnect").about("stop connections to all servers"))
.subcommand(Command::new("t").about("quick test by connecting to 127.0.0.1:1231"))
.subcommand(
2022-04-02 13:44:07 +00:00
Command::new("connect")
.about("opens a connection to another instance of this fileserver network")
2022-04-02 13:44:07 +00:00
.no_binary_name(true)
.arg(
2022-04-02 13:44:07 +00:00
Arg::new("ip:port")
.help("ip and port to connect to, example '127.0.0.1:1231'")
.required(true)
.validator(|ipport| match ipport.parse::<std::net::SocketAddr>() {
Ok(_) => Ok(()),
Err(e) => Err(format!("must be valid Ip:Port combination {:?}", e)),
}),
),
)
2022-04-02 13:44:07 +00:00
.subcommand(Command::new("list").about("lists all available files on the network"))
.subcommand(
2022-04-02 13:44:07 +00:00
Command::new("serve")
.about("make file available on the network")
.arg(
2022-04-02 13:44:07 +00:00
Arg::new("file")
.help("file to serve")
.required(true)
.validator(file_exists),
),
)
.subcommand(
2022-04-02 13:44:07 +00:00
Command::new("get")
.about(
"downloads file with the id from the `list` command. Optionally provide a \
storage path, if none is provided it will be saved in the current directory \
with the remote filename",
)
.arg(
2022-04-02 13:44:07 +00:00
Arg::new("id")
.help("id to download. get the id from the `list` command")
.required(true)
.validator(|id| match id.parse::<u32>() {
Ok(_) => Ok(()),
Err(e) => Err(format!("must be a number {:?}", e)),
}),
)
2022-04-02 13:44:07 +00:00
.arg(Arg::new("file").help("local path to store the file to")),
)
}
async fn client(cmd_sender: mpsc::UnboundedSender<LocalCommand>) {
use std::io::Write;
loop {
let mut line = String::new();
let mut input_lines = io::BufReader::new(io::stdin());
print!("==> ");
std::io::stdout().flush().unwrap();
input_lines.read_line(&mut line).await.unwrap();
2022-04-02 13:44:07 +00:00
let matches = match get_options().try_get_matches_from(line.split_whitespace()) {
Err(e) => {
2022-04-02 13:44:07 +00:00
println!("{}", e);
continue;
},
Ok(matches) => matches,
};
match matches.subcommand() {
2022-04-02 13:44:07 +00:00
None => {
println!("unknown subcommand");
break;
},
Some(("quit", _)) => {
cmd_sender.send(LocalCommand::Shutdown).unwrap();
println!("goodbye");
break;
},
2022-04-02 13:44:07 +00:00
Some(("disconnect", _)) => {
cmd_sender.send(LocalCommand::Disconnect).unwrap();
},
2022-04-02 13:44:07 +00:00
Some(("connect", connect_matches)) => {
let socketaddr = connect_matches
.value_of("ip:port")
.unwrap()
.parse()
.unwrap();
cmd_sender
.send(LocalCommand::Connect(ConnectAddr::Tcp(socketaddr)))
.unwrap();
},
2022-04-02 13:44:07 +00:00
Some(("t", _)) => {
cmd_sender
.send(LocalCommand::Connect(ConnectAddr::Tcp(
"127.0.0.1:1231".parse().unwrap(),
)))
.unwrap();
},
2022-04-02 13:44:07 +00:00
Some(("serve", serve_matches)) => {
let path = shellexpand::tilde(serve_matches.value_of("file").unwrap());
let path: PathBuf = path.parse().unwrap();
if let Some(fileinfo) = FileInfo::new(&path).await {
cmd_sender.send(LocalCommand::Serve(fileinfo)).unwrap();
}
},
2022-04-02 13:44:07 +00:00
Some(("list", _)) => {
cmd_sender.send(LocalCommand::List).unwrap();
},
2022-04-02 13:44:07 +00:00
Some(("get", get_matches)) => {
let id: u32 = get_matches.value_of("id").unwrap().parse().unwrap();
let file = get_matches.value_of("file");
cmd_sender
.send(LocalCommand::Get(id, file.map(|s| s.to_string())))
.unwrap();
},
2022-04-02 13:44:07 +00:00
Some((_, _)) => {
unreachable!("this subcommand isn't yet handled");
},
}
// this 100 ms is because i am super lazy, and i want to appear the logs before
// the next '==>' appears...
thread::sleep(Duration::from_millis(100));
println!();
}
thread::sleep(Duration::from_millis(30)); // TODO: still needed for correct shutdown
}