use crate::fsentry::{FilesystemEntry, FilesystemKind}; use std::ffi::OsStr; use std::os::unix::ffi::OsStrExt; //use crate::fsiter::FsIterator; use crate::chunkid::ChunkId; use rusqlite::{params, Connection, OpenFlags, Row, Transaction}; use std::os::unix::ffi::OsStringExt; use std::path::{Path, PathBuf}; /// A backup generation. pub struct Generation { conn: Connection, fileno: u64, } impl Generation { pub fn create

(filename: P) -> anyhow::Result where P: AsRef, { let flags = OpenFlags::SQLITE_OPEN_CREATE | OpenFlags::SQLITE_OPEN_READ_WRITE; let conn = Connection::open_with_flags(filename, flags)?; conn.execute( "CREATE TABLE files (fileno INTEGER PRIMARY KEY, path BLOB, kind INTEGER, len INTEGER)", params![], )?; conn.execute( "CREATE TABLE chunks (fileno INTEGER, chunkid TEXT)", params![], )?; conn.pragma_update(None, "journal_mode", &"WAL")?; Ok(Self { conn, fileno: 0 }) } pub fn open

(filename: P) -> anyhow::Result where P: AsRef, { let flags = OpenFlags::SQLITE_OPEN_READ_WRITE; let conn = Connection::open_with_flags(filename, flags)?; conn.pragma_update(None, "journal_mode", &"WAL")?; let fileno = find_max_fileno(&conn)?; Ok(Self { conn, fileno }) } pub fn insert(&mut self, e: FilesystemEntry, ids: &[ChunkId]) -> anyhow::Result<()> { let t = self.conn.transaction()?; self.fileno += 1; insert_one(&t, e, self.fileno, ids)?; t.commit()?; Ok(()) } pub fn insert_iter( &mut self, entries: impl Iterator)>>, ) -> anyhow::Result<()> { let t = self.conn.transaction()?; for r in entries { let (e, ids) = r?; self.fileno += 1; insert_one(&t, e, self.fileno, &ids[..])?; } t.commit()?; Ok(()) } pub fn file_count(&self) -> u64 { self.fileno } pub fn files(&self) -> anyhow::Result> { let mut stmt = self.conn.prepare("SELECT * FROM files")?; let iter = stmt.query_map(params![], |row| row_to_entry(row))?; let mut files: Vec<(u64, FilesystemEntry)> = vec![]; for x in iter { let (fileno, entry) = x?; files.push((fileno, entry)); } Ok(files) } pub fn chunkids(&self, fileno: u64) -> anyhow::Result> { let fileno = fileno as i64; let mut stmt = self .conn .prepare("SELECT chunkid FROM chunks WHERE fileno = ?1")?; let iter = stmt.query_map(params![fileno], |row| Ok(row.get(0)?))?; let mut ids: Vec = vec![]; for x in iter { let fileno: String = x?; ids.push(ChunkId::from(&fileno)); } Ok(ids) } } fn row_to_entry(row: &Row) -> rusqlite::Result<(u64, FilesystemEntry)> { let fileno: i64 = row.get(row.column_index("fileno")?)?; let fileno = fileno as u64; let path: Vec = row.get(row.column_index("path")?)?; let path: &OsStr = OsStrExt::from_bytes(&path); let path: PathBuf = PathBuf::from(path); let kind = row.get(row.column_index("kind")?)?; let kind = FilesystemKind::from_code(kind).unwrap(); let entry = match kind { FilesystemKind::Regular => FilesystemEntry::regular(path, 0), FilesystemKind::Directory => FilesystemEntry::directory(path), }; Ok((fileno, entry)) } 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 (fileno, path, kind, len) VALUES (?1, ?2, ?3, ?4)", params![fileno, path, kind, len], )?; for id in ids { t.execute( "INSERT INTO chunks (fileno, chunkid) VALUES (?1, ?2)", params![fileno, id], )?; } Ok(()) } fn find_max_fileno(conn: &Connection) -> anyhow::Result { let mut stmt = conn.prepare("SELECT fileno FROM files ORDER BY fileno")?; let mut rows = stmt.query(params![])?; let mut fileno: i64 = 0; while let Some(row) = rows.next()? { let x = row.get(0)?; if x > fileno { fileno = x; } } Ok(fileno as u64) } #[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::create(&filename).unwrap(); // _gen is dropped here; the connection is close; the file // should not be removed. } assert!(filename.exists()); } }