1pub mod items;
2pub mod kvp;
3mod migrations;
4pub mod pane;
5pub mod workspace;
6
7use std::fs;
8use std::path::{Path, PathBuf};
9use std::sync::Arc;
10
11use anyhow::Result;
12use log::error;
13use parking_lot::Mutex;
14use rusqlite::{backup, Connection};
15
16use migrations::MIGRATIONS;
17pub use workspace::*;
18
19#[derive(Clone)]
20pub enum Db {
21 Real(Arc<RealDb>),
22 Null,
23}
24
25pub struct RealDb {
26 connection: Mutex<Connection>,
27 path: Option<PathBuf>,
28}
29
30impl Db {
31 /// Open or create a database at the given directory path.
32 pub fn open(db_dir: &Path, channel: &'static str) -> Self {
33 // Use 0 for now. Will implement incrementing and clearing of old db files soon TM
34 let current_db_dir = db_dir.join(Path::new(&format!("0-{}", channel)));
35 fs::create_dir_all(¤t_db_dir)
36 .expect("Should be able to create the database directory");
37 let db_path = current_db_dir.join(Path::new("db.sqlite"));
38
39 Connection::open(db_path)
40 .map_err(Into::into)
41 .and_then(|connection| Self::initialize(connection))
42 .map(|connection| {
43 Db::Real(Arc::new(RealDb {
44 connection,
45 path: Some(db_dir.to_path_buf()),
46 }))
47 })
48 .unwrap_or_else(|e| {
49 error!(
50 "Connecting to file backed db failed. Reverting to null db. {}",
51 e
52 );
53 Self::Null
54 })
55 }
56
57 fn initialize(mut conn: Connection) -> Result<Mutex<Connection>> {
58 MIGRATIONS.to_latest(&mut conn)?;
59
60 conn.pragma_update(None, "journal_mode", "WAL")?;
61 conn.pragma_update(None, "synchronous", "NORMAL")?;
62 conn.pragma_update(None, "foreign_keys", true)?;
63 conn.pragma_update(None, "case_sensitive_like", true)?;
64
65 Ok(Mutex::new(conn))
66 }
67
68 pub fn persisting(&self) -> bool {
69 self.real().and_then(|db| db.path.as_ref()).is_some()
70 }
71
72 pub fn real(&self) -> Option<&RealDb> {
73 match self {
74 Db::Real(db) => Some(&db),
75 _ => None,
76 }
77 }
78
79 /// Open a in memory database for testing and as a fallback.
80 pub fn open_in_memory() -> Self {
81 Connection::open_in_memory()
82 .map_err(Into::into)
83 .and_then(|connection| Self::initialize(connection))
84 .map(|connection| {
85 Db::Real(Arc::new(RealDb {
86 connection,
87 path: None,
88 }))
89 })
90 .unwrap_or_else(|e| {
91 error!(
92 "Connecting to in memory db failed. Reverting to null db. {}",
93 e
94 );
95 Self::Null
96 })
97 }
98
99 pub fn write_to<P: AsRef<Path>>(&self, dest: P) -> Result<()> {
100 self.real()
101 .map(|db| {
102 if db.path.is_some() {
103 panic!("DB already exists");
104 }
105
106 let lock = db.connection.lock();
107 let mut dst = Connection::open(dest)?;
108 let backup = backup::Backup::new(&lock, &mut dst)?;
109 backup.step(-1)?;
110
111 Ok(())
112 })
113 .unwrap_or(Ok(()))
114 }
115}
116
117impl Drop for Db {
118 fn drop(&mut self) {
119 match self {
120 Db::Real(real_db) => {
121 let lock = real_db.connection.lock();
122
123 let _ = lock.pragma_update(None, "analysis_limit", "500");
124 let _ = lock.pragma_update(None, "optimize", "");
125 }
126 Db::Null => {}
127 }
128 }
129}
130
131#[cfg(test)]
132mod tests {
133 use crate::migrations::MIGRATIONS;
134
135 #[test]
136 fn test_migrations() {
137 assert!(MIGRATIONS.validate().is_ok());
138 }
139}