summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2020-12-30 11:57:08 +0000
committerLars Wirzenius <liw@liw.fi>2020-12-30 11:57:08 +0000
commit6b9553945683e2b01404673e37e8b951b32a993e (patch)
tree99bb10c1cfc99bded3b7187f18541c9e7c676ead
parentbbde1bfd90edaa49463d7c3950ddcaa834e9ce02 (diff)
parentc492bc19aaf404ab4115b8d7f06eff81a6480b4e (diff)
downloadobnam2-6b9553945683e2b01404673e37e8b951b32a993e.tar.gz
Merge branch 'list-gen-timestamps' into 'main'
add timestamps to generations See merge request larswirzenius/obnam!47
-rw-r--r--Cargo.toml1
-rw-r--r--src/client.rs17
-rw-r--r--src/cmd/backup.rs4
-rw-r--r--src/cmd/list.rs6
-rw-r--r--src/cmd/restore.rs8
-rw-r--r--src/generation.rs40
6 files changed, 60 insertions, 16 deletions
diff --git a/Cargo.toml b/Cargo.toml
index a8e404b..e8bbd91 100644
--- a/Cargo.toml
+++ b/Cargo.toml
@@ -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()), &current_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
+ }
+}