use super::{item::Event, Item}; use anyhow::Result; use gtk::glib::DateTime; use sqlite::{Connection, Transaction}; use std::{rc::Rc, sync::RwLock}; pub struct Database { connection: Rc>, profile_id: i64, } impl Database { // Constructors /// Create new `Self` pub fn build(connection: &Rc>, profile_id: i64) -> Self { Self { connection: connection.clone(), profile_id, } } // Getters /// Get history records from database with optional filter by `request` pub fn records(&self, request: Option<&str>, title: Option<&str>) -> Result> { let readable = self.connection.read().unwrap(); // @TODO let tx = readable.unchecked_transaction()?; select(&tx, self.profile_id, request, title) } // Actions /// Create new history record in database /// * return last insert ID on success pub fn add(&self, item: &Item) -> Result { let mut writable = self.connection.write().unwrap(); // @TODO let tx = writable.transaction()?; let id = insert(&tx, self.profile_id, item)?; tx.commit()?; Ok(id) } pub fn update(&self, item: &Item) -> Result { let mut writable = self.connection.write().unwrap(); // @TODO let tx = writable.transaction()?; let affected = update(&tx, self.profile_id, item)?; tx.commit()?; Ok(affected) } } // Low-level DB API pub fn init(tx: &Transaction) -> Result { Ok(tx.execute( "CREATE TABLE IF NOT EXISTS `profile_history` ( `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `profile_id` INTEGER NOT NULL, `opened_time` INTEGER NOT NULL, `opened_count` INTEGER NOT NULL, `closed_time` INTEGER NULL, `closed_count` INTEGER NULL, `request` TEXT NOT NULL, `title` TEXT NULL, FOREIGN KEY (`profile_id`) REFERENCES `profile` (`id`), UNIQUE (`profile_id`, `request`) )", [], )?) } pub fn insert(tx: &Transaction, profile_id: i64, item: &Item) -> Result { tx.execute( "INSERT INTO `profile_history` ( `profile_id`, `opened_time`, `opened_count`, `closed_time`, `closed_count`, `request`, `title` ) VALUES (?, ?, ?, ?, ?, ?, ?)", ( profile_id, item.opened.time.to_unix(), item.opened.count as i64, item.closed.as_ref().map(|closed| closed.time.to_unix()), item.closed.as_ref().map(|closed| closed.count as i64), item.request.as_str(), item.title.as_deref(), ), )?; Ok(tx.last_insert_rowid()) } pub fn update(tx: &Transaction, profile_id: i64, item: &Item) -> Result { Ok(tx.execute( "UPDATE `profile_history` SET `opened_time` = ?, `opened_count` = ?, `closed_time` = ?, `closed_count` = ?, `request` = ?, `title` = ? WHERE `id` = ? AND `profile_id` = ?", ( item.opened.time.to_unix(), item.opened.count as i64, item.closed.as_ref().map(|closed| closed.time.to_unix()), item.closed.as_ref().map(|closed| closed.count as i64), item.request.as_str(), item.title.as_deref(), item.id.unwrap(), profile_id, ), )?) } pub fn select( tx: &Transaction, profile_id: i64, request: Option<&str>, title: Option<&str>, ) -> Result> { let mut stmt = tx.prepare( "SELECT `id`, `profile_id`, `opened_time`, `opened_count`, `closed_time`, `closed_count`, `request`, `title` FROM `profile_history` WHERE `profile_id` = ? AND (`request` LIKE ? OR `title` LIKE ?)", )?; let result = stmt.query_map( (profile_id, request.unwrap_or("%"), title.unwrap_or("%")), |row| { Ok(Item { id: row.get(0)?, //profile_id: row.get(1)?, opened: Event { time: DateTime::from_unix_local(row.get(2)?).unwrap(), count: row.get(3)?, }, closed: closed(row.get(4)?, row.get(5)?), request: row.get::<_, String>(6)?.into(), title: row.get::<_, Option>(7)?.map(|s| s.into()), is_saved: true, }) }, )?; let mut items = Vec::new(); for record in result { let item = record?; items.push(item); } Ok(items) } // Tools fn closed(time: Option, count: Option) -> Option { if let Some(t) = time { if let Some(c) = count { return Some(Event { time: DateTime::from_unix_local(t).unwrap(), count: c as usize, }); } panic!() } None }