summaryrefslogtreecommitdiff
path: root/src/bin/obnam-backup.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/bin/obnam-backup.rs')
-rw-r--r--src/bin/obnam-backup.rs100
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)