diff options
Diffstat (limited to 'src/bin/obnam-restore.rs')
-rw-r--r-- | src/bin/obnam-restore.rs | 119 |
1 files changed, 119 insertions, 0 deletions
diff --git a/src/bin/obnam-restore.rs b/src/bin/obnam-restore.rs new file mode 100644 index 0000000..2014ef8 --- /dev/null +++ b/src/bin/obnam-restore.rs @@ -0,0 +1,119 @@ +// Fetch a backup generation's chunks, write to stdout. + +use log::{debug, info, trace}; +use obnam::chunk::{DataChunk, GenerationChunk}; +use obnam::chunkid::ChunkId; +//use obnam::chunkmeta::ChunkMeta; +use serde::Deserialize; +use std::io::{stdout, Write}; +use std::path::{Path, PathBuf}; +use structopt::StructOpt; + +#[derive(Debug, thiserror::Error)] +enum ClientError { + #[error("Server does not have generation {0}")] + GenerationNotFound(String), + + #[error("Server does not have chunk {0}")] + ChunkNotFound(String), +} + +#[derive(Debug, StructOpt)] +#[structopt(name = "obnam-backup", about = "Simplistic backup client")] +struct Opt { + #[structopt(parse(from_os_str))] + config: PathBuf, + + #[structopt()] + gen_id: String, +} + +fn main() -> anyhow::Result<()> { + pretty_env_logger::init(); + + let opt = Opt::from_args(); + let config = Config::read_config(&opt.config).unwrap(); + + info!("obnam-restore starts up"); + info!("opt: {:?}", opt); + info!("config: {:?}", config); + + let client = reqwest::blocking::Client::builder() + .danger_accept_invalid_certs(true) + .build()?; + let mut stdout = stdout(); + + let gen = fetch_generation(&client, &config, &opt.gen_id)?; + debug!("gen: {:?}", gen); + for id in gen.chunk_ids() { + let chunk = fetch_chunk(&client, &config, id)?; + debug!("got chunk: {}", id); + stdout.write_all(chunk.data())?; + } + + Ok(()) +} + +#[derive(Debug, Deserialize, Clone)] +pub struct Config { + pub server_name: String, + pub server_port: u16, +} + +impl Config { + pub fn read_config(filename: &Path) -> anyhow::Result<Config> { + let config = std::fs::read_to_string(filename)?; + let config: Config = serde_yaml::from_str(&config)?; + Ok(config) + } +} + +fn fetch_generation( + client: &reqwest::blocking::Client, + config: &Config, + gen_id: &str, +) -> anyhow::Result<GenerationChunk> { + let url = format!( + "http://{}:{}/chunks/{}", + config.server_name, config.server_port, gen_id, + ); + + trace!("fetch_generation: url={:?}", url); + let req = client.get(&url).build()?; + + let res = client.execute(req)?; + debug!("fetch_generation: status={}", res.status()); + if res.status() != 200 { + debug!("fetch_generation: error from server"); + return Err(ClientError::GenerationNotFound(gen_id.to_string()).into()); + } + + let text = res.text()?; + debug!("fetch_generation: text={:?}", text); + let gen = serde_json::from_str(&text)?; + Ok(gen) +} + +fn fetch_chunk( + client: &reqwest::blocking::Client, + config: &Config, + chunk_id: &ChunkId, +) -> anyhow::Result<DataChunk> { + let url = format!( + "http://{}:{}/chunks/{}", + config.server_name, config.server_port, chunk_id, + ); + + trace!("fetch_chunk: url={:?}", url); + let req = client.get(&url).build()?; + + let res = client.execute(req)?; + debug!("fetch_chunk: status={}", res.status()); + if res.status() != 200 { + debug!("fetch_chunk: error from server"); + return Err(ClientError::ChunkNotFound(chunk_id.to_string()).into()); + } + + let body = res.bytes()?; + Ok(DataChunk::new(body.to_vec())) +} |