summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
authorLars Wirzenius <liw@liw.fi>2020-11-08 12:16:36 +0200
committerLars Wirzenius <liw@liw.fi>2020-11-08 18:50:32 +0200
commit5cd1ea00ad5d60d5f54a23002bca6e8b40b9cd64 (patch)
tree5ed61745fc8189a12fdf6c2fd04b380293c78367 /src
parentec07ffe546300ca594a95851e8828987d68e4841 (diff)
downloadobnam2-5cd1ea00ad5d60d5f54a23002bca6e8b40b9cd64.tar.gz
feat: add Generation to represent data in a backup generation
The data is stored in an SQLite database file.
Diffstat (limited to 'src')
-rw-r--r--src/generation.rs94
1 files changed, 94 insertions, 0 deletions
diff --git a/src/generation.rs b/src/generation.rs
new file mode 100644
index 0000000..2cae478
--- /dev/null
+++ b/src/generation.rs
@@ -0,0 +1,94 @@
+use crate::fsentry::FilesystemEntry;
+//use crate::fsiter::FsIterator;
+use crate::chunkid::ChunkId;
+use rusqlite::{params, Connection, OpenFlags, Transaction};
+use std::os::unix::ffi::OsStringExt;
+use std::path::Path;
+
+/// A backup generation.
+pub struct Generation {
+ conn: Connection,
+ fileno: u64,
+}
+
+impl Generation {
+ pub fn new<P>(filename: P) -> anyhow::Result<Self>
+ where
+ P: AsRef<Path>,
+ {
+ let flags = OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE;
+ let conn = Connection::open_with_flags(filename, flags)?;
+ conn.execute(
+ "CREATE TABLE files (fileid INTEGER PRIMARY KEY, path BLOB, kind INTEGER, len INTEGER)",
+ params![],
+ )?;
+ conn.execute(
+ "CREATE TABLE chunks (fileid INTEGER, chunkid TEXT)",
+ params![],
+ )?;
+ conn.pragma_update(None, "journal_mode", &"WAL")?;
+ Ok(Self { conn, fileno: 0 })
+ }
+
+ pub fn insert(&mut self, e: FilesystemEntry, ids: &[ChunkId]) -> anyhow::Result<()> {
+ let t = self.conn.transaction()?;
+ insert_one(&t, e, self.fileno, ids)?;
+ self.fileno += 1;
+ t.commit()?;
+ Ok(())
+ }
+
+ pub fn insert_iter<'a>(
+ &mut self,
+ entries: impl Iterator<Item = anyhow::Result<(FilesystemEntry, Vec<ChunkId>)>>,
+ ) -> anyhow::Result<()> {
+ let t = self.conn.transaction()?;
+ for r in entries {
+ let (e, ids) = r?;
+ insert_one(&t, e, self.fileno, &ids[..])?;
+ self.fileno += 1;
+ }
+ t.commit()?;
+ Ok(())
+ }
+}
+
+fn insert_one(
+ t: &Transaction,
+ e: FilesystemEntry,
+ fileno: u64,
+ ids: &[ChunkId],
+) -> anyhow::Result<()> {
+ let path = e.path().as_os_str().to_os_string().into_vec();
+ let kind = e.kind().as_code();
+ let len = e.len() as i64;
+ let fileno = fileno as i64;
+ t.execute(
+ "INSERT INTO files (fileid, path, kind, len) VALUES (?1, ?2, ?3, ?4)",
+ params![fileno, path, kind, len],
+ )?;
+ for id in ids {
+ t.execute(
+ "INSERT INTO chunks (fileid, chunkid) VALUES (?1, ?2)",
+ params![fileno, id],
+ )?;
+ }
+ Ok(())
+}
+
+#[cfg(test)]
+mod test {
+ use super::Generation;
+ use tempfile::NamedTempFile;
+
+ #[test]
+ fn empty() {
+ let filename = NamedTempFile::new().unwrap().path().to_path_buf();
+ {
+ let mut _gen = Generation::new(&filename).unwrap();
+ // _gen is dropped here; the connection is close; the file
+ // should not be removed.
+ }
+ assert!(filename.exists());
+ }
+}