use std::thread::sleep;
use std::time::Duration;
use abscissa_core::{
config::Override, status_err, Application, Command, FrameworkError, Runnable, Shutdown,
};
use anyhow::Result;
use clap::Parser;
use gethostname::gethostname;
use log::{info, warn};
use tungstenite::{connect, Message};
use url::Url;
use rustic_core::{repofile::SnapshotFile, PathList, Repository};
use crate::{
config::RusticSchedulerConfig,
message::{BackupMessage, BackupResultMessage, HandshakeMessage},
prelude::RUSTIC_SCHEDULER_APP,
};
#[derive(Command, Debug, Parser)]
pub struct ClientCmd {
#[clap(short)]
name: Option<String>,
#[clap(long)]
url: Url,
}
impl Override<RusticSchedulerConfig> for ClientCmd {
fn override_config(
&self,
config: RusticSchedulerConfig,
) -> std::result::Result<RusticSchedulerConfig, FrameworkError> {
Ok(config)
}
}
impl Runnable for ClientCmd {
fn run(&self) {
let res = || -> Result<()> {
let name = self
.name
.clone()
.unwrap_or_else(|| gethostname().to_string_lossy().to_string());
loop {
if let Err(err) = connect_client(self.url.clone(), name.clone()) {
eprintln!("{err}");
warn!("error {err}, retrying...");
sleep(Duration::from_secs(5));
}
}
};
if let Err(err) = res() {
status_err!("{}", err);
RUSTIC_SCHEDULER_APP.shutdown(Shutdown::Crash);
};
}
}
fn connect_client(server: Url, name: String) -> Result<()> {
let (mut socket, _) = connect(server.as_str())?;
info!("Connected to the server");
println!("Connected to the server");
let handshake_msg = HandshakeMessage { client: name };
let handshake_msg = serde_json::to_string(&handshake_msg)?;
socket.send(handshake_msg.into())?;
loop {
let msg = socket.read()?;
match msg {
Message::Ping(..) => socket.send(Message::Pong(Vec::new()))?,
_ => {
let msg = msg.into_data();
let backup_msg: BackupMessage = serde_json::from_slice(&msg)?;
let snap_msg = match do_backup(backup_msg) {
Ok(snap) => {
println!("{snap:?}");
BackupResultMessage::Ok {
snapshot: Box::new(snap),
}
}
Err(err) => BackupResultMessage::Error {
message: err.to_string(),
},
};
let snap_msg = serde_json::to_string(&snap_msg)?;
socket.send(snap_msg.into())?;
}
}
}
}
fn do_backup(message: BackupMessage) -> Result<SnapshotFile> {
let backends = message.repo_opts.be.to_backends()?;
let repo_opts = message.repo_opts.repo;
let repo = Repository::new(&repo_opts, &backends)?
.open()?
.to_indexed_ids()?;
let source = PathList::from_string(&message.source)?.sanitize()?;
let snap = message.snapshot_opts.to_snapshot()?;
let snap = repo.backup(&message.backup_opts, &source, snap)?;
Ok(snap)
}