pub mod keychain;
pub mod repo;
pub mod schema;
use rusqlite::Connection;
use std::path::Path;
use std::sync::{Arc, Mutex};
use crate::error::{HuddleError, Result};
pub type Db = Arc<Mutex<Connection>>;
pub fn open_db(path: &Path, master_key: Option<&[u8; 32]>) -> Result<Db> {
let conn = Connection::open(path)?;
if let Some(key) = master_key {
let pragma = format!("PRAGMA key = \"x'{}'\";", hex::encode(key));
conn.execute_batch(&pragma)?;
if let Err(e) = conn.query_row("SELECT count(*) FROM sqlite_master", [], |r| {
r.get::<_, i64>(0)
}) {
return Err(HuddleError::Session(format!(
"wrong master passphrase, or DB file corrupt: {e}"
)));
}
}
conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
run_migrations(&conn)?;
Ok(Arc::new(Mutex::new(conn)))
}
pub fn open_db_in_memory() -> Result<Db> {
let conn = Connection::open_in_memory()?;
conn.execute_batch("PRAGMA foreign_keys=ON;")?;
run_migrations(&conn)?;
Ok(Arc::new(Mutex::new(conn)))
}
fn run_migrations(conn: &Connection) -> Result<()> {
let applied: i64 = conn.query_row("PRAGMA user_version", [], |row| row.get(0))?;
for (idx, migration) in schema::MIGRATIONS.iter().enumerate() {
if (idx as i64) < applied {
continue;
}
let target = (idx + 1) as i64;
let batch = format!(
"BEGIN; {migration}; PRAGMA user_version = {target}; COMMIT;",
migration = migration,
target = target
);
if let Err(e) = conn.execute_batch(&batch) {
let _ = conn.execute_batch("ROLLBACK;");
return Err(HuddleError::Other(format!(
"migration {idx} failed: {e}"
)));
}
}
Ok(())
}