small_bin/
database.rs

1use crate::*;
2use anyhow::Result;
3use rusqlite::{Connection, params};
4use serde::{Deserialize, Serialize};
5use std::{
6    path::Path,
7    sync::{Arc, Mutex},
8};
9
10
11#[derive(Debug, Clone, Serialize, Deserialize)]
12pub struct History {
13    pub content: String,
14    pub timestamp: i64,
15    pub file: String,
16    pub uuid: String,
17}
18
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct QueueItem {
21    pub local_file: String,
22    pub remote_file: String,
23    pub uuid: String,
24}
25
26#[derive(Debug)]
27pub struct Database {
28    conn: Arc<Mutex<Connection>>,
29}
30
31impl Database {
32    pub fn new<P: AsRef<Path>>(db_path: P) -> Result<Self> {
33        // Ensure parent directory exists
34        if let Some(parent) = db_path.as_ref().parent() {
35            std::fs::create_dir_all(parent)?;
36        }
37
38        let conn = Connection::open(db_path)?;
39        let db = Database {
40            conn: Arc::new(Mutex::new(conn)),
41        };
42        db.init_schema()?;
43        Ok(db)
44    }
45
46
47    fn init_schema(&self) -> Result<()> {
48        let conn = self.conn.lock().unwrap();
49
50        conn.execute(
51            "CREATE TABLE IF NOT EXISTS history (
52                id INTEGER PRIMARY KEY AUTOINCREMENT,
53                content TEXT NOT NULL,
54                timestamp INTEGER NOT NULL,
55                file TEXT NOT NULL,
56                uuid TEXT NOT NULL UNIQUE
57            )",
58            [],
59        )?;
60
61        conn.execute(
62            "CREATE INDEX IF NOT EXISTS idx_history_timestamp ON history(timestamp)",
63            [],
64        )?;
65
66        conn.execute(
67            "CREATE TABLE IF NOT EXISTS queue (
68                id INTEGER PRIMARY KEY AUTOINCREMENT,
69                local_file TEXT NOT NULL,
70                remote_file TEXT NOT NULL,
71                uuid TEXT NOT NULL UNIQUE
72            )",
73            [],
74        )?;
75
76        Ok(())
77    }
78
79
80    pub fn add_to_queue(&self, item: &QueueItem) -> Result<()> {
81        let conn = self.conn.lock().unwrap();
82        conn.execute(
83            "INSERT OR IGNORE INTO queue (local_file, remote_file, uuid) VALUES (?1, ?2, ?3)",
84            params![&item.local_file, &item.remote_file, &item.uuid],
85        )?;
86        Ok(())
87    }
88
89
90    pub fn get_queue(&self) -> Result<Vec<QueueItem>> {
91        let conn = self.conn.lock().unwrap();
92        let mut stmt = conn.prepare("SELECT local_file, remote_file, uuid FROM queue")?;
93        let items = stmt
94            .query_map([], |row| {
95                Ok(QueueItem {
96                    local_file: row.get(0)?,
97                    remote_file: row.get(1)?,
98                    uuid: row.get(2)?,
99                })
100            })?
101            .collect::<Result<Vec<_>, _>>()?;
102        Ok(items)
103    }
104
105
106    pub fn remove_from_queue(&self, uuid: &str) -> Result<()> {
107        let conn = self.conn.lock().unwrap();
108        conn.execute("DELETE FROM queue WHERE uuid = ?1", params![uuid])?;
109        Ok(())
110    }
111
112
113    pub fn add_history(&self, history: &History) -> Result<()> {
114        let conn = self.conn.lock().unwrap();
115        conn.execute(
116            "INSERT OR IGNORE INTO history (content, timestamp, file, uuid) VALUES (?1, ?2, ?3, ?4)",
117            params![&history.content, &history.timestamp, &history.file, &history.uuid],
118        )?;
119        Ok(())
120    }
121
122
123    pub fn get_history(&self, limit: Option<usize>) -> Result<Vec<History>> {
124        let conn = self.conn.lock().unwrap();
125        let query = if let Some(lim) = limit {
126            format!(
127                "SELECT content, timestamp, file, uuid FROM history ORDER BY timestamp DESC LIMIT {}",
128                lim
129            )
130        } else {
131            "SELECT content, timestamp, file, uuid FROM history ORDER BY timestamp DESC"
132                .to_string()
133        };
134
135        let mut stmt = conn.prepare(&query)?;
136        let items = stmt
137            .query_map([], |row| {
138                Ok(History {
139                    content: row.get(0)?,
140                    timestamp: row.get(1)?,
141                    file: row.get(2)?,
142                    uuid: row.get(3)?,
143                })
144            })?
145            .collect::<Result<Vec<_>, _>>()?;
146        Ok(items)
147    }
148
149
150    pub fn dump_to_file<P: AsRef<Path>>(&self, path: P) -> Result<()> {
151        let conn = self.conn.lock().unwrap();
152        let mut backup_conn = Connection::open(&path)?;
153
154        let backup = rusqlite::backup::Backup::new(&conn, &mut backup_conn)?;
155        backup.run_to_completion(5, std::time::Duration::from_millis(250), None)?;
156
157        info!("Database dumped to: {:?}", path.as_ref());
158        Ok(())
159    }
160}