1use crate::lib::app::Timer;
2use chrono::{DateTime, Duration};
3use rusqlite::{Connection, params};
4use std::sync::{Arc, Mutex};
5use std::path::PathBuf;
6use std::fs;
7use dirs;
8
9#[derive(Debug)]
10pub struct Db {
11 conn: Arc<Mutex<Connection>>,
12}
13
14impl Db {
15 pub fn new(path: &str) -> Self {
16 Db {
17 conn: Db::init_db(path).expect("Unable to init db"),
18 }
19 }
20
21 pub fn get_database_path() -> Result<PathBuf, Box<dyn std::error::Error>> {
23 let data_dir = dirs::data_dir()
24 .ok_or("Unable to determine data directory")?;
25
26 let app_dir = data_dir.join("timers");
27
28 fs::create_dir_all(&app_dir)?;
30
31 Ok(app_dir.join("timers.db"))
32 }
33
34 pub fn new_with_default_path() -> Result<Self, Box<dyn std::error::Error>> {
36 let db_path = Self::get_database_path()?;
37 let path_str = db_path.to_str()
38 .ok_or("Database path contains invalid UTF-8")?;
39
40 Ok(Db {
41 conn: Db::init_db(path_str).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)?,
42 })
43 }
44
45 fn init_db(path: &str) -> Result<Arc<Mutex<Connection>>, rusqlite::Error> {
46 let conn = Connection::open(path)?;
47
48 conn.execute(
49 "CREATE TABLE IF NOT EXISTS timers (
50 id INTEGER PRIMARY KEY AUTOINCREMENT,
51 name TEXT NOT NULL,
52 description TEXT NOT NULL,
53 start_time DATETIME NOT NULL,
54 duration INTEGER NOT NULL,
55 running BOOLEAN NOT NULL
56 )",
57 [],
58 )?;
59
60 Ok(Arc::new(Mutex::new(conn))) }
62
63 pub fn add_timer_to_db(&self, timer: &mut Timer) -> Result<(), rusqlite::Error> {
64 let conn = self.conn.lock().expect("Unable to lock connection");
65 conn.execute(
66 "INSERT INTO timers (name, description, start_time, duration, running) VALUES (?, ?, ?, ?, ?)",
67 params![
68 timer.name,
69 timer.description,
70 timer.start_time.to_rfc3339(),
71 timer.duration.num_seconds(),
72 timer.running
73 ],
74 )?;
75
76 let id = conn.last_insert_rowid();
77 timer.id = id as usize;
78 Ok(())
79 }
80
81 pub fn get_timers_from_db(&self) -> Result<Vec<Timer>, rusqlite::Error> {
82 let conn = self.conn.lock().expect("Unable to lock connection");
83 let mut stmt = conn
84 .prepare("SELECT id, name, description, start_time, duration, running FROM timers")?;
85 let timers = stmt
86 .query_map(params![], |row| {
87 let timestamp: String = row.get(3)?;
88 let date = DateTime::parse_from_rfc3339(×tamp).unwrap().to_utc();
89 Ok(Timer {
90 id: row.get(0)?,
91 name: row.get(1)?,
92 description: row.get(2)?,
93 start_time: date,
94 duration: Duration::seconds(row.get(4)?),
95 running: row.get(5)?,
96 })
97 })?
98 .collect::<Result<Vec<Timer>, rusqlite::Error>>()?;
99 Ok(timers)
100 }
101
102 pub fn update_timers_in_db(&self, timers: &Vec<Timer>) -> Result<(), rusqlite::Error> {
103 let conn = self.conn.lock().expect("Unable to lock connection");
104 for timer in timers {
105 conn.execute(
106 "UPDATE timers SET duration = ?, running = ? WHERE id = ?",
107 params![timer.duration.num_seconds(), timer.running, timer.id],
108 )?;
109 }
110 Ok(())
111 }
112
113 pub fn delete_timer(&self, id: usize) -> Result<(), rusqlite::Error> {
114 let conn = self.conn.lock().expect("Unable to lock connection");
115 conn.execute("DELETE FROM timers WHERE id = ?", params![id])?;
116 Ok(())
117 }
118
119 pub fn edit_timer(
120 &self,
121 timer: &Timer,
122 name: &str,
123 description: &str,
124 ) -> Result<(), rusqlite::Error> {
125 let conn = self.conn.lock().expect("Unable to lock connection");
126 conn.execute(
127 "UPDATE timers SET name = ?, description = ? WHERE id = ?",
128 params![name, description, timer.id],
129 )?;
130 Ok(())
131 }
132}