Skip to main content

huddle_core/storage/
mod.rs

1pub mod keychain;
2pub mod repo;
3pub mod schema;
4
5use rusqlite::Connection;
6use std::path::Path;
7use std::sync::{Arc, Mutex};
8
9use crate::error::Result;
10
11pub type Db = Arc<Mutex<Connection>>;
12
13/// Open the DB. If `master_key` is `Some`, SQLCipher is unlocked with
14/// `PRAGMA key`; otherwise the DB is opened unencrypted (the Phase 1
15/// path, kept for tests and `--no-master-passphrase` runs).
16pub fn open_db(path: &Path, master_key: Option<&[u8; 32]>) -> Result<Db> {
17    let conn = Connection::open(path)?;
18    if let Some(key) = master_key {
19        let pragma = format!("PRAGMA key = \"x'{}'\";", hex::encode(key));
20        conn.execute_batch(&pragma)?;
21    }
22    conn.execute_batch("PRAGMA journal_mode=WAL; PRAGMA foreign_keys=ON;")?;
23    run_migrations(&conn)?;
24    Ok(Arc::new(Mutex::new(conn)))
25}
26
27pub fn open_db_in_memory() -> Result<Db> {
28    let conn = Connection::open_in_memory()?;
29    conn.execute_batch("PRAGMA foreign_keys=ON;")?;
30    run_migrations(&conn)?;
31    Ok(Arc::new(Mutex::new(conn)))
32}
33
34fn run_migrations(conn: &Connection) -> Result<()> {
35    for migration in schema::MIGRATIONS {
36        if let Err(e) = conn.execute_batch(migration) {
37            let msg = e.to_string().to_lowercase();
38            // Tolerate idempotent additive migrations on existing DBs
39            // (ALTER TABLE ... ADD COLUMN runs every launch but errors
40            // on the second run; CREATE INDEX IF NOT EXISTS already
41            // handles itself).
42            if msg.contains("duplicate column")
43                || msg.contains("already exists")
44                || msg.contains("duplicate column name")
45            {
46                continue;
47            }
48            return Err(e.into());
49        }
50    }
51    Ok(())
52}