use clap::{Parser, Subcommand};
use log::info;
use std::path::PathBuf;
const DEFAULT_ADDRESS: &str = "127.0.0.1:8080";
const DEFAULT_PASSWORD: &str = "123456";
#[derive(Parser)]
#[command(name = "deon_protocol")]
#[command(version)]
#[command(about = "Secure file transfer with sane defaults", long_about = None)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Send {
file: PathBuf,
#[arg(default_value = DEFAULT_ADDRESS)]
address: String,
#[arg(short, long)]
password: Option<String>,
},
#[command(alias = "recv")]
Receive {
#[arg(short, long, default_value_t = 8080)]
port: u16,
#[arg(short, long, default_value = ".")]
out: PathBuf,
#[arg(short, long)]
password: Option<String>,
},
#[command(alias = "ssend")]
StreamSend {
input: PathBuf,
#[arg(default_value = DEFAULT_ADDRESS)]
address: String,
#[arg(short, long)]
name: Option<String>,
#[arg(short = 't', long)]
content_type: Option<String>,
#[arg(short, long)]
password: Option<String>,
},
#[command(alias = "srecv")]
StreamReceive {
#[arg(short, long, default_value_t = 8080)]
port: u16,
output: PathBuf,
#[arg(short, long)]
password: Option<String>,
},
}
fn resolve_password(password: Option<String>) -> String {
password
.or_else(|| std::env::var("DEON_PASSWORD").ok())
.filter(|value| !value.trim().is_empty())
.unwrap_or_else(|| DEFAULT_PASSWORD.to_string())
}
#[tokio::main]
async fn main() -> Result<(), Box<dyn std::error::Error>> {
env_logger::Builder::from_env(env_logger::Env::default().default_filter_or("info")).init();
let cli = Cli::parse();
match cli.command {
Commands::Send {
file,
address,
password,
} => {
let password = resolve_password(password);
info!("Sending '{}' to {}", file.display(), address);
deon_protocol::send_file(&file, &address, &password).await?;
info!("Transfer completed successfully");
}
Commands::Receive {
port,
out,
password,
} => {
let password = resolve_password(password);
info!("Waiting for file on port {}...", port);
let saved_path = deon_protocol::receive_file(port, &password, &out).await?;
info!("File saved at {}", saved_path.display());
}
Commands::StreamSend {
input,
address,
name,
content_type,
password,
} => {
let password = resolve_password(password);
let mut file = tokio::fs::File::open(&input).await?;
let total_size = file.metadata().await?.len();
let stream_name = name.or_else(|| {
input
.file_name()
.and_then(|value| value.to_str())
.map(|value| value.to_string())
}).unwrap_or_else(|| "stream.bin".to_string());
info!(
"Streaming '{}' ({} bytes) to {}",
stream_name,
total_size,
address
);
let sent = deon_protocol::send_stream_with_metadata(
&stream_name,
Some(total_size),
content_type.as_deref(),
&mut file,
&address,
&password,
).await?;
info!("Stream sent successfully ({} bytes)", sent);
}
Commands::StreamReceive {
port,
output,
password,
} => {
let password = resolve_password(password);
info!("Waiting for stream on port {}...", port);
let receipt = deon_protocol::receive_stream_to_path(port, &password, &output).await?;
info!(
"Stream '{}' saved at {} ({} bytes)",
receipt.name,
output.display(),
receipt.bytes_received
);
}
}
Ok(())
}