summaryrefslogtreecommitdiff
path: root/src/cmd
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2021-01-04 13:30:21 +0200
committerLars Wirzenius <liw@liw.fi>2021-01-04 15:02:33 +0200
commitc11b8bb76c50cff6aa481f17907ae7200ac55c01 (patch)
treef275d30b15891a71f11d8012f0573268310567bf /src/cmd
parentf73b2a919dc2fe2d92eef6df9b7cab25af083fb3 (diff)
downloadobnam2-c11b8bb76c50cff6aa481f17907ae7200ac55c01.tar.gz
feat! record whether file was backed up and why, in a generation
This changes SQL schema.
Diffstat (limited to 'src/cmd')
-rw-r--r--src/cmd/backup.rs74
-rw-r--r--src/cmd/list_files.rs42
-rw-r--r--src/cmd/mod.rs5
-rw-r--r--src/cmd/restore.rs4
4 files changed, 105 insertions, 20 deletions
diff --git a/src/cmd/backup.rs b/src/cmd/backup.rs
index 4d13fe7..1521cab 100644
--- a/src/cmd/backup.rs
+++ b/src/cmd/backup.rs
@@ -4,6 +4,9 @@ use crate::fsiter::FsIterator;
use crate::generation::{LocalGeneration, NascentGeneration};
use indicatif::{ProgressBar, ProgressStyle};
use log::{debug, info};
+use rusqlite::types::ToSqlOutput;
+use rusqlite::ToSql;
+use std::fmt;
use tempfile::NamedTempFile;
pub fn backup(config: &ClientConfig, buffer_size: usize) -> anyhow::Result<()> {
@@ -43,7 +46,9 @@ pub fn backup(config: &ClientConfig, buffer_size: usize) -> anyhow::Result<()> {
let path = &entry.pathbuf();
info!("backup: {}", path.display());
progress.set_message(&format!("{}", path.display()));
- client.upload_filesystem_entry(entry, buffer_size)
+ let (new_entry, ids) =
+ client.upload_filesystem_entry(entry, buffer_size)?;
+ Ok((new_entry, ids, Reason::IsNew))
}
}
}))?;
@@ -60,16 +65,22 @@ pub fn backup(config: &ClientConfig, buffer_size: usize) -> anyhow::Result<()> {
let path = &entry.pathbuf();
info!("backup: {}", path.display());
progress.set_message(&format!("{}", path.display()));
- if needs_backup(&old, &entry) {
- client.upload_filesystem_entry(entry, buffer_size)
- } else {
- let fileno = old.get_fileno(&entry.pathbuf())?;
- let ids = if let Some(fileno) = fileno {
- old.chunkids(fileno)?
- } else {
- vec![]
- };
- Ok((entry.clone(), ids))
+ let reason = needs_backup(&old, &entry);
+ match reason {
+ Reason::IsNew | Reason::Changed | Reason::Error => {
+ let (new_entry, ids) =
+ client.upload_filesystem_entry(entry, buffer_size)?;
+ Ok((new_entry, ids, reason))
+ }
+ Reason::Unchanged => {
+ let fileno = old.get_fileno(&entry.pathbuf())?;
+ let ids = if let Some(fileno) = fileno {
+ old.chunkids(fileno)?
+ } else {
+ vec![]
+ };
+ Ok((entry.clone(), ids, Reason::Unchanged))
+ }
}
}
}
@@ -85,13 +96,42 @@ pub fn backup(config: &ClientConfig, buffer_size: usize) -> anyhow::Result<()> {
let gen_id = client.upload_generation(&newname, buffer_size)?;
println!("gen id: {}", gen_id);
- // Delete the temporary file.
+ // Delete the temporary file.q
std::fs::remove_file(&newname)?;
std::fs::remove_file(&oldname)?;
Ok(())
}
+#[derive(Debug)]
+pub enum Reason {
+ IsNew,
+ Changed,
+ Unchanged,
+ Error,
+}
+
+impl ToSql for Reason {
+ fn to_sql(&self) -> rusqlite::Result<ToSqlOutput> {
+ Ok(ToSqlOutput::Owned(rusqlite::types::Value::Text(format!(
+ "{}",
+ self
+ ))))
+ }
+}
+
+impl fmt::Display for Reason {
+ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
+ let reason = match self {
+ Reason::IsNew => "new",
+ Reason::Changed => "changed",
+ Reason::Unchanged => "unchanged",
+ Reason::Error => "error",
+ };
+ write!(f, "{}", reason)
+ }
+}
+
fn create_progress_bar(verbose: bool) -> ProgressBar {
let progress = if verbose {
ProgressBar::new(0)
@@ -109,7 +149,7 @@ fn create_progress_bar(verbose: bool) -> ProgressBar {
progress
}
-fn needs_backup(old: &LocalGeneration, new_entry: &FilesystemEntry) -> bool {
+fn needs_backup(old: &LocalGeneration, new_entry: &FilesystemEntry) -> Reason {
let new_name = new_entry.pathbuf();
match old.get_file(&new_name) {
// File is not in old generation.
@@ -118,17 +158,17 @@ fn needs_backup(old: &LocalGeneration, new_entry: &FilesystemEntry) -> bool {
"needs_backup: file is not in old generation, needs backup: {:?}",
new_name
);
- true
+ Reason::IsNew
}
// File is in old generation. Has its metadata changed?
Ok(Some(old_entry)) => {
if file_has_changed(&old_entry, new_entry) {
debug!("needs_backup: file has changed: {:?}", new_name);
- true
+ Reason::Changed
} else {
debug!("needs_backup: file has NOT changed: {:?}", new_name);
- false
+ Reason::Unchanged
}
}
@@ -139,7 +179,7 @@ fn needs_backup(old: &LocalGeneration, new_entry: &FilesystemEntry) -> bool {
"needs_backup: lookup in old generation returned error, ignored: {:?}: {}",
new_name, err
);
- true
+ Reason::Error
}
}
}
diff --git a/src/cmd/list_files.rs b/src/cmd/list_files.rs
new file mode 100644
index 0000000..aa4bed0
--- /dev/null
+++ b/src/cmd/list_files.rs
@@ -0,0 +1,42 @@
+use crate::client::BackupClient;
+use crate::client::ClientConfig;
+use crate::error::ObnamError;
+use crate::fsentry::{FilesystemEntry, FilesystemKind};
+use tempfile::NamedTempFile;
+
+pub fn list_files(config: &ClientConfig, gen_ref: &str) -> anyhow::Result<()> {
+ // Create a named temporary file. We don't meed the open file
+ // handle, so we discard that.
+ let dbname = {
+ let temp = NamedTempFile::new()?;
+ let (_, dbname) = temp.keep()?;
+ dbname
+ };
+
+ let client = BackupClient::new(&config.server_url)?;
+
+ let genlist = client.list_generations()?;
+ let gen_id: String = match genlist.resolve(gen_ref) {
+ None => return Err(ObnamError::UnknownGeneration(gen_ref.to_string()).into()),
+ Some(id) => id,
+ };
+
+ let gen = client.fetch_generation(&gen_id, &dbname)?;
+ for (_, entry, reason) in gen.files()? {
+ println!("{}", format_entry(&entry, &reason));
+ }
+
+ // Delete the temporary file.
+ std::fs::remove_file(&dbname)?;
+
+ Ok(())
+}
+
+fn format_entry(e: &FilesystemEntry, reason: &str) -> String {
+ let kind = match e.kind() {
+ FilesystemKind::Regular => "-",
+ FilesystemKind::Directory => "d",
+ FilesystemKind::Symlink => "l",
+ };
+ format!("{} {} ({})", kind, e.pathbuf().display(), reason)
+}
diff --git a/src/cmd/mod.rs b/src/cmd/mod.rs
index ca5ff42..2919d88 100644
--- a/src/cmd/mod.rs
+++ b/src/cmd/mod.rs
@@ -1,8 +1,11 @@
mod backup;
-pub use backup::backup;
+pub use backup::{backup, Reason};
mod list;
pub use list::list;
+mod list_files;
+pub use list_files::list_files;
+
pub mod restore;
pub use restore::restore;
diff --git a/src/cmd/restore.rs b/src/cmd/restore.rs
index 53e168a..0efdffb 100644
--- a/src/cmd/restore.rs
+++ b/src/cmd/restore.rs
@@ -35,10 +35,10 @@ pub fn restore(config: &ClientConfig, gen_ref: &str, to: &Path) -> anyhow::Resul
let gen = client.fetch_generation(&gen_id, &dbname)?;
info!("restore file count: {}", gen.file_count()?);
let progress = create_progress_bar(gen.file_count()?, true);
- for (fileid, entry) in gen.files()? {
+ for (fileid, entry, _) in gen.files()? {
restore_generation(&client, &gen, fileid, &entry, &to, &progress)?;
}
- for (_, entry) in gen.files()? {
+ for (_, entry, _) in gen.files()? {
if entry.is_dir() {
restore_directory_metadata(&entry, &to)?;
}