Skip to main content

time_rs/lib/
db.rs

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    /// Get the platform-appropriate database path
22    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        // Create the directory if it doesn't exist
29        fs::create_dir_all(&app_dir)?;
30
31        Ok(app_dir.join("timers.db"))
32    }
33
34    /// Create a new Db instance using the platform-appropriate path
35    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))) // Wrap the connection in an Arc<Mutex<Connection>>conn)
61    }
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(&timestamp).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}