#![feature(iterator_try_collect, never_type)] pub mod client; pub mod server; use anyhow::{anyhow, Result}; use clap::{Parser, Subcommand}; use client::Client; use log::info; use serde::Deserialize; use server::server; use std::{ fs::{read_to_string, File}, net::SocketAddr, os::unix::fs::MetadataExt, path::PathBuf, }; pub type Serial = u64; #[derive(Parser)] struct Args { config: PathBuf, #[clap(subcommand)] action: Action, } #[derive(Subcommand)] enum Action { Daemon, List { peer: Option, }, Download { path: PathBuf, /// Which peer to download from, any if not specified peer: Option, /// Serial of the backup to download, latest if not specified serial: Option, }, Upload { /// Path to backup file path: PathBuf, /// Which peer to upload to, all if not specified peer: Option, }, } #[derive(Deserialize)] pub struct Config { storage: StorageConfig, server: ServerConfig, peer: Vec, } #[derive(Deserialize)] pub struct PeerConfig { name: String, address: SocketAddr, shared_secret: String, } #[derive(Deserialize)] pub struct ServerConfig { address: String, } #[derive(Deserialize)] pub struct StorageConfig { root: PathBuf, size: u64, versions: usize, upload_cooldown: u64, download_cooldown: u64, upload_speed: usize, download_speed: usize, } fn main() -> Result<()> { env_logger::init_from_env("LOG"); let args = Args::parse(); let config = read_to_string(&args.config)?; let config = toml::from_str::(&config)?; match args.action { Action::Daemon => server(config.into())?, Action::List { peer } => { let peers = config.peer.iter().filter(|p| { if let Some(pn) = &peer { pn == &p.name } else { true } }); for p in peers { println!("peer {:?}", p.name); let mut client = Client::new(p.address, &p.shared_secret)?; for (mtime, size, serial) in client.list()? { println!("\tserial={serial} mtime={mtime} size={size}") } client.quit()?; } } Action::Download { path, serial, peer } => { let mut peers = config.peer.iter().filter(|p| { if let Some(pn) = &peer { pn == &p.name } else { true } }); let peer = peers.next().ok_or(anyhow!("no matching peer"))?; info!("connecting to {:?}", peer.name); let mut client = Client::new(peer.address, &peer.shared_secret)?; let file = File::create_new(&path)?; let serial = if let Some(serial) = serial { serial } else { client.list()?.last().ok_or(anyhow!("no backups stored"))?.2 }; println!("downloading serial={serial} from {}", peer.name); client.download(serial, file)?; info!("upload successful"); client.quit()?; println!("success") } Action::Upload { peer, path } => { let peers = config.peer.iter().filter(|p| { if let Some(pn) = &peer { pn == &p.name } else { true } }); for peer in peers { info!("connecting to {:?}", peer.name); println!("uploading to {}", peer.name); let mut client = Client::new(peer.address, &peer.shared_secret)?; let file = File::open(&path)?; let size = file.metadata()?.size(); client.upload(size, file)?; info!("upload successful"); client.quit()?; } println!("success") } } Ok(()) }