diff options
author | Lars Wirzenius <liw@liw.fi> | 2020-12-30 11:57:08 +0000 |
---|---|---|
committer | Lars Wirzenius <liw@liw.fi> | 2020-12-30 11:57:08 +0000 |
commit | 6b9553945683e2b01404673e37e8b951b32a993e (patch) | |
tree | 99bb10c1cfc99bded3b7187f18541c9e7c676ead | |
parent | bbde1bfd90edaa49463d7c3950ddcaa834e9ce02 (diff) | |
parent | c492bc19aaf404ab4115b8d7f06eff81a6480b4e (diff) | |
download | obnam2-6b9553945683e2b01404673e37e8b951b32a993e.tar.gz |
Merge branch 'list-gen-timestamps' into 'main'
add timestamps to generations
See merge request larswirzenius/obnam!47
-rw-r--r-- | Cargo.toml | 1 | ||||
-rw-r--r-- | src/client.rs | 17 | ||||
-rw-r--r-- | src/cmd/backup.rs | 4 | ||||
-rw-r--r-- | src/cmd/list.rs | 6 | ||||
-rw-r--r-- | src/cmd/restore.rs | 8 | ||||
-rw-r--r-- | src/generation.rs | 40 |
6 files changed, 60 insertions, 16 deletions
@@ -7,6 +7,7 @@ edition = "2018" [dependencies] anyhow = "1" bytes = "0.5" +chrono = "0.4" indicatif = "0.15" libc = "0.2" log = "0.4" diff --git a/src/client.rs b/src/client.rs index 3149ec5..adca373 100644 --- a/src/client.rs +++ b/src/client.rs @@ -5,6 +5,8 @@ use crate::chunker::Chunker; use crate::chunkid::ChunkId; use crate::chunkmeta::ChunkMeta; use crate::fsentry::{FilesystemEntry, FilesystemKind}; +use crate::generation::FinishedGeneration; +use chrono::{DateTime, Local}; use log::{debug, error, info, trace}; use reqwest::blocking::Client; use serde::Deserialize; @@ -73,7 +75,7 @@ impl BackupClient { let ids = self.read_file(filename.to_path_buf(), size)?; let gen = GenerationChunk::new(ids); let data = gen.to_data_chunk()?; - let meta = ChunkMeta::new_generation(&sha256(data.data()), "timestamp"); + let meta = ChunkMeta::new_generation(&sha256(data.data()), ¤t_timestamp()); let gen_id = self.upload_gen_chunk(meta, gen)?; Ok(gen_id) } @@ -179,7 +181,7 @@ impl BackupClient { Ok(chunk_ids) } - pub fn list_generations(&self) -> anyhow::Result<Vec<ChunkId>> { + pub fn list_generations(&self) -> anyhow::Result<Vec<FinishedGeneration>> { let url = format!("{}?generation=true", &self.chunks_url()); trace!("list_generations: url={:?}", url); let req = self.client.get(&url).build()?; @@ -189,7 +191,11 @@ impl BackupClient { debug!("list_generationgs: body={:?}", body); let map: HashMap<String, ChunkMeta> = serde_yaml::from_slice(&body)?; debug!("list_generations: map={:?}", map); - Ok(map.keys().into_iter().map(|key| key.into()).collect()) + let finished = map + .iter() + .map(|(id, meta)| FinishedGeneration::new(id, meta.ended().map_or("", |s| s))) + .collect(); + Ok(finished) } pub fn fetch_chunk(&self, chunk_id: &ChunkId) -> anyhow::Result<DataChunk> { @@ -222,3 +228,8 @@ impl BackupClient { Ok(gen) } } + +fn current_timestamp() -> String { + let now: DateTime<Local> = Local::now(); + format!("{}", now.format("%Y-%m-%d %H:%M:%S %z")) +} diff --git a/src/cmd/backup.rs b/src/cmd/backup.rs index 2a294f5..c9b7fc5 100644 --- a/src/cmd/backup.rs +++ b/src/cmd/backup.rs @@ -1,6 +1,6 @@ use crate::client::{BackupClient, ClientConfig}; use crate::fsiter::FsIterator; -use crate::generation::Generation; +use crate::generation::NascentGeneration; use indicatif::{ProgressBar, ProgressStyle}; use log::info; use tempfile::NamedTempFile; @@ -22,7 +22,7 @@ pub fn backup(config: &ClientConfig, buffer_size: usize) -> anyhow::Result<()> { // Create the SQLite database using the named temporary file. // The fetching is in its own block so that the file handles // get closed and data flushed to disk. - let mut gen = Generation::create(&dbname)?; + let mut gen = NascentGeneration::create(&dbname)?; let progress = create_progress_bar(GUESS_FILE_COUNT, true); progress.enable_steady_tick(100); gen.insert_iter(FsIterator::new(&config.root).map(|entry| { diff --git a/src/cmd/list.rs b/src/cmd/list.rs index 6c48244..70aa0a7 100644 --- a/src/cmd/list.rs +++ b/src/cmd/list.rs @@ -3,8 +3,10 @@ use crate::client::{BackupClient, ClientConfig}; pub fn list(config: &ClientConfig) -> anyhow::Result<()> { let client = BackupClient::new(&config.server_url)?; - for gen_id in client.list_generations()? { - println!("{}", gen_id); + let mut generations = client.list_generations()?; + generations.sort_by_cached_key(|gen| gen.ended().to_string()); + for finished in generations { + println!("{} {}", finished.id(), finished.ended()); } Ok(()) diff --git a/src/cmd/restore.rs b/src/cmd/restore.rs index da654fd..dd7ed41 100644 --- a/src/cmd/restore.rs +++ b/src/cmd/restore.rs @@ -1,7 +1,7 @@ use crate::client::BackupClient; use crate::client::ClientConfig; use crate::fsentry::{FilesystemEntry, FilesystemKind}; -use crate::generation::Generation; +use crate::generation::NascentGeneration; use indicatif::{ProgressBar, ProgressStyle}; use libc::{fchmod, futimens, timespec}; use log::{debug, error, info}; @@ -37,7 +37,7 @@ pub fn restore(config: &ClientConfig, gen_id: &str, to: &Path) -> anyhow::Result } info!("downloaded generation to {}", dbname.display()); - let gen = Generation::open(&dbname)?; + let gen = NascentGeneration::open(&dbname)?; info!("restore file count: {}", gen.file_count()); let progress = create_progress_bar(gen.file_count(), true); for (fileid, entry) in gen.files()? { @@ -74,7 +74,7 @@ struct Opt { fn restore_generation( client: &BackupClient, - gen: &Generation, + gen: &NascentGeneration, fileid: u64, entry: &FilesystemEntry, to: &Path, @@ -123,7 +123,7 @@ fn restored_path(entry: &FilesystemEntry, to: &Path) -> anyhow::Result<PathBuf> fn restore_regular( client: &BackupClient, - gen: &Generation, + gen: &NascentGeneration, path: &Path, fileid: u64, entry: &FilesystemEntry, diff --git a/src/generation.rs b/src/generation.rs index 875221f..dc9bf0c 100644 --- a/src/generation.rs +++ b/src/generation.rs @@ -3,13 +3,17 @@ use crate::fsentry::FilesystemEntry; use rusqlite::{params, Connection, OpenFlags, Row, Transaction}; use std::path::Path; -/// A backup generation. -pub struct Generation { +/// A nascent backup generation. +/// +/// A nascent generation is one that is being prepared. It isn't +/// finished yet, and it's not actually on the server, yet, or not +/// completely. It can't be restored. +pub struct NascentGeneration { conn: Connection, fileno: u64, } -impl Generation { +impl NascentGeneration { pub fn create<P>(filename: P) -> anyhow::Result<Self> where P: AsRef<Path>, @@ -136,17 +140,43 @@ fn find_max_fileno(conn: &Connection) -> anyhow::Result<u64> { #[cfg(test)] mod test { - use super::Generation; + use super::NascentGeneration; use tempfile::NamedTempFile; #[test] fn empty() { let filename = NamedTempFile::new().unwrap().path().to_path_buf(); { - let mut _gen = Generation::create(&filename).unwrap(); + let mut _gen = NascentGeneration::create(&filename).unwrap(); // _gen is dropped here; the connection is close; the file // should not be removed. } assert!(filename.exists()); } } + +/// A finished generation. +/// +/// A generation is finished when it's on the server. It can be restored. +pub struct FinishedGeneration { + id: ChunkId, + ended: String, +} + +impl FinishedGeneration { + pub fn new(id: &str, ended: &str) -> Self { + let id = id.parse().unwrap(); // this never fails + Self { + id, + ended: ended.to_string(), + } + } + + pub fn id(&self) -> ChunkId { + self.id.clone() + } + + pub fn ended(&self) -> &str { + &self.ended + } +} |