diff options
Diffstat (limited to 'src/bin/obnam-backup.rs')
-rw-r--r-- | src/bin/obnam-backup.rs | 100 |
1 files changed, 82 insertions, 18 deletions
diff --git a/src/bin/obnam-backup.rs b/src/bin/obnam-backup.rs index d2de289..9370611 100644 --- a/src/bin/obnam-backup.rs +++ b/src/bin/obnam-backup.rs @@ -1,7 +1,9 @@ // Read stdin, split into chunks, upload new chunks to chunk server. use indicatif::{ProgressBar, ProgressStyle}; -use obnam::chunk::DataChunk; +use log::{debug, error, info, trace}; +use obnam::chunk::{DataChunk, GenerationChunk}; +use obnam::chunkid::ChunkId; use obnam::chunkmeta::ChunkMeta; use serde::Deserialize; use sha2::{Digest, Sha256}; @@ -13,6 +15,12 @@ use structopt::StructOpt; const BUFFER_SIZE: usize = 1024 * 1024; +#[derive(Debug, thiserror::Error)] +enum ClientError { + #[error("Server successful response to creating chunk lacked chunk id")] + NoCreatedChunkId, +} + #[derive(Debug, StructOpt)] #[structopt(name = "obnam-backup", about = "Simplistic backup client")] struct Opt { @@ -21,43 +29,70 @@ struct Opt { } fn main() -> anyhow::Result<()> { + pretty_env_logger::init(); + let opt = Opt::from_args(); let config = Config::read_config(&opt.config).unwrap(); + // let pb = ProgressBar::new_spinner(); let pb = ProgressBar::new_spinner(); pb.set_style( ProgressStyle::default_bar() .template("backing up:\n{bytes} ({bytes_per_sec}) {elapsed} {msg} {spinner}"), ); - println!("config: {:?}", config); + info!("obnam-backup starts up"); + info!("config: {:?}", config); let client = reqwest::blocking::Client::builder() .danger_accept_invalid_certs(true) .build()?; + let mut chunk_ids = vec![]; + let mut total_bytes = 0; + let mut new_chunks = 0; + let mut dup_chunks = 0; + let mut new_bytes = 0; + let mut dup_bytes = 0; + let stdin = std::io::stdin(); let mut stdin = BufReader::new(stdin); - let mut dup = 0; loop { match read_chunk(&mut stdin)? { None => break, Some((meta, chunk)) => { let n = chunk.data().len() as u64; - if !has_chunk(&client, &config, &meta)? { - pb.inc(n); - upload_chunk(&client, &config, meta, chunk)?; + debug!("read {} bytes", n); + total_bytes += n; + pb.inc(n); + if let Some(chunk_id) = has_chunk(&client, &config, &meta)? { + debug!("dup chunk: {}", chunk_id); + chunk_ids.push(chunk_id); + dup_chunks += 1; + dup_bytes += n; } else { - dup += n; + let chunk_id = upload_chunk(&client, &config, meta, chunk)?; + debug!("new chunk: {}", chunk_id); + chunk_ids.push(chunk_id); + new_chunks += 1; + new_bytes += n; } } } } + + let gen = GenerationChunk::new(chunk_ids); + let gen_id = upload_gen(&client, &config, &gen)?; + pb.finish(); - println!( - "read total {} bytes from stdin ({} dup)", - pb.position(), - dup - ); + info!("read total {} bytes from stdin", total_bytes); + info!("duplicate bytes: {}", dup_bytes); + info!("duplicate chunks: {}", dup_chunks); + info!("new bytes: {}", new_bytes); + info!("new chunks: {}", new_chunks); + info!("total chunks: {}", gen.len()); + info!("generation id: {}", gen_id); + info!("obnam-backup finished OK"); + println!("backup OK: generation id: {}", gen_id); Ok(()) } @@ -110,42 +145,71 @@ fn upload_chunk( config: &Config, meta: ChunkMeta, chunk: DataChunk, -) -> anyhow::Result<()> { +) -> anyhow::Result<ChunkId> { let url = format!( "http://{}:{}/chunks", config.server_name, config.server_port ); - client + let res = client .post(&url) .header("chunk-meta", meta.to_json()) .body(chunk.data().to_vec()) .send()?; - Ok(()) + debug!("upload_chunk: res={:?}", res); + let res: HashMap<String, String> = res.json()?; + let chunk_id = if let Some(chunk_id) = res.get("chunk_id") { + debug!("upload_chunk: id={}", chunk_id); + chunk_id.parse().unwrap() + } else { + return Err(ClientError::NoCreatedChunkId.into()); + }; + Ok(chunk_id) +} + +fn upload_gen( + client: &reqwest::blocking::Client, + config: &Config, + gen: &GenerationChunk, +) -> anyhow::Result<ChunkId> { + let meta = ChunkMeta::new_generation("metasha", "ended-sometime"); + let chunk = gen.to_data_chunk()?; + upload_chunk(client, config, meta, chunk) } fn has_chunk( client: &reqwest::blocking::Client, config: &Config, meta: &ChunkMeta, -) -> anyhow::Result<bool> { +) -> anyhow::Result<Option<ChunkId>> { let url = format!( "http://{}:{}/chunks", config.server_name, config.server_port, ); + trace!("has_chunk: url={:?}", url); let req = client .get(&url) .query(&[("sha256", meta.sha256())]) .build()?; let res = client.execute(req)?; + debug!("has_chunk: status={}", res.status()); let has = if res.status() != 200 { - false + debug!("has_chunk: error from server"); + None } else { let text = res.text()?; + debug!("has_chunk: text={:?}", text); let hits: HashMap<String, ChunkMeta> = serde_json::from_str(&text)?; - !hits.is_empty() + debug!("has_chunk: hits={:?}", hits); + let mut iter = hits.iter(); + if let Some((chunk_id, _)) = iter.next() { + debug!("has_chunk: chunk_id={:?}", chunk_id); + Some(chunk_id.into()) + } else { + None + } }; Ok(has) |