diff --git a/src/app/browser/window/header/bar/menu.rs b/src/app/browser/window/header/bar/menu.rs index 34f86beb..f5ce57b9 100644 --- a/src/app/browser/window/header/bar/menu.rs +++ b/src/app/browser/window/header/bar/menu.rs @@ -207,13 +207,13 @@ impl Menu for MenuButton { move |_| { // Bookmarks main_bookmarks.remove_all(); - for request in profile.bookmark.recent() { - let menu_item = gio::MenuItem::new(Some(&ellipsize(&request, LABEL_MAX_LENGTH)), None); + for bookmark in profile.bookmark.recent() { + let menu_item = gio::MenuItem::new(Some(&ellipsize(&bookmark.request, LABEL_MAX_LENGTH)), None); menu_item.set_action_and_target_value(Some(&format!( "{}.{}", window_action.id, window_action.load.simple_action.name() - )), Some(&request.to_variant())); + )), Some(&bookmark.request.to_variant())); main_bookmarks.append_item(&menu_item); } // @TODO `menu_item` diff --git a/src/app/browser/window/tab/item/page/navigation/bookmark.rs b/src/app/browser/window/tab/item/page/navigation/bookmark.rs index 834e71ad..37838b30 100644 --- a/src/app/browser/window/tab/item/page/navigation/bookmark.rs +++ b/src/app/browser/window/tab/item/page/navigation/bookmark.rs @@ -48,7 +48,7 @@ impl Bookmark for Button { } fn update(&self, profile: &Profile, request: &Entry) { - let has_bookmark = profile.bookmark.get(&request.text()).is_some(); + let has_bookmark = profile.bookmark.contains_request(&request.text()); self.set_icon_name(icon_name(has_bookmark)); self.set_tooltip_text(Some(tooltip_text(has_bookmark))); } diff --git a/src/profile/bookmark.rs b/src/profile/bookmark.rs index bcfd5676..6878ff71 100644 --- a/src/profile/bookmark.rs +++ b/src/profile/bookmark.rs @@ -1,16 +1,18 @@ mod database; +mod item; mod memory; use anyhow::Result; use database::Database; use gtk::glib::DateTime; +use item::Item; use memory::Memory; use sqlite::{Connection, Transaction}; -use std::{rc::Rc, sync::RwLock}; +use std::{cell::RefCell, rc::Rc, sync::RwLock}; pub struct Bookmark { - database: Database, // permanent storage - memory: Memory, // fast search index + database: Database, // permanent storage + memory: RefCell, // fast search index } impl Bookmark { @@ -20,11 +22,17 @@ impl Bookmark { pub fn build(connection: &Rc>, profile_id: &Rc) -> Result { // Init children components let database = Database::new(connection, profile_id); - let memory = Memory::new(); + let memory = RefCell::new(Memory::new()); // Build initial index - for record in database.records(None)? { - memory.add(record.request, record.id)?; + { + let mut memory = memory.borrow_mut(); + for record in database.records(None)? { + memory.add(Item { + id: record.id, + request: record.request, + }); + } } // Return new `Self` @@ -33,25 +41,20 @@ impl Bookmark { // Actions - /// Get record `id` by `request` from memory index - pub fn get(&self, request: &str) -> Option { - self.memory.get(request) - } - /// Toggle bookmark in `database` and `memory` index /// * return `true` on bookmark create, `false` on delete pub fn toggle(&self, request: &str) -> Result { - Ok(match self.get(request) { - Some(id) => { - self.database.delete(id)?; - self.memory.delete(request)?; + let mut memory = self.memory.borrow_mut(); + Ok(match memory.delete_by_request(request) { + Some(record) => { + self.database.delete(record.id)?; false } None => { - self.memory.add( - request.into(), - self.database.add(DateTime::now_local()?, request)?, - )?; + memory.add(Item { + id: self.database.add(DateTime::now_local()?, request)?, + request: request.into(), + }); true } }) @@ -59,9 +62,14 @@ impl Bookmark { // Getters - /// Get recent requests vector from `memory`, sorted by `ID` DESC - pub fn recent(&self) -> Vec { - self.memory.recent() + /// Check `request` exists in the memory index + pub fn contains_request(&self, request: &str) -> bool { + self.memory.borrow_mut().contains_request(request) + } + + /// Get recent Items vector from `memory`, sorted by `ID` DESC + pub fn recent(&self) -> Vec { + self.memory.borrow().recent() } } diff --git a/src/profile/bookmark/database.rs b/src/profile/bookmark/database.rs index e23e18ec..9efe65bf 100644 --- a/src/profile/bookmark/database.rs +++ b/src/profile/bookmark/database.rs @@ -1,15 +1,9 @@ +use super::Item; use anyhow::Result; use gtk::glib::DateTime; use sqlite::{Connection, Transaction}; use std::{rc::Rc, sync::RwLock}; -pub struct Table { - pub id: i64, - //pub profile_id: i64, - //pub time: DateTime, - pub request: String, -} - pub struct Database { connection: Rc>, profile_id: Rc, // multi-profile relationship @@ -29,7 +23,7 @@ impl Database { // Getters /// Get bookmark records from database with optional filter by `request` - pub fn records(&self, request: Option<&str>) -> Result> { + pub fn records(&self, request: Option<&str>) -> Result> { let readable = self.connection.read().unwrap(); // @TODO let tx = readable.unchecked_transaction()?; select(&tx, *self.profile_id, request) @@ -86,7 +80,7 @@ pub fn insert(tx: &Transaction, profile_id: i64, time: DateTime, request: &str) Ok(tx.last_insert_rowid()) } -pub fn select(tx: &Transaction, profile_id: i64, request: Option<&str>) -> Result> { +pub fn select(tx: &Transaction, profile_id: i64, request: Option<&str>) -> Result> { let mut stmt = tx.prepare( "SELECT `id`, `profile_id`, `time`, `request` FROM `profile_bookmark` @@ -94,7 +88,7 @@ pub fn select(tx: &Transaction, profile_id: i64, request: Option<&str>) -> Resul )?; let result = stmt.query_map((profile_id, request.unwrap_or("%")), |row| { - Ok(Table { + Ok(Item { id: row.get(0)?, //profile_id: row.get(1)?, //time: DateTime::from_unix_local(row.get(2)?).unwrap(), diff --git a/src/profile/bookmark/item.rs b/src/profile/bookmark/item.rs new file mode 100644 index 00000000..477be8b2 --- /dev/null +++ b/src/profile/bookmark/item.rs @@ -0,0 +1,5 @@ +#[derive(Clone)] +pub struct Item { + pub id: i64, + pub request: String, +} diff --git a/src/profile/bookmark/memory.rs b/src/profile/bookmark/memory.rs index 60cd2399..a0fb4747 100644 --- a/src/profile/bookmark/memory.rs +++ b/src/profile/bookmark/memory.rs @@ -1,10 +1,56 @@ -use anyhow::Result; +use super::Item; use itertools::Itertools; -use std::{cell::RefCell, collections::HashMap}; /// Reduce disk usage by cache Bookmarks index in memory -pub struct Memory { - index: RefCell>, +pub struct Memory(Vec); + +impl Memory { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + Self(Vec::new()) + } + + // Actions + + /// Add new item + pub fn add(&mut self, item: Item) { + self.0.push(item) + } + + /// Delete record from index by `request` + pub fn delete_by_request(&mut self, request: &str) -> Option { + for (i, item) in self.0.iter().enumerate() { + if item.request == request { + return Some(self.0.remove(i)); + } + } + None + } + + /// Check `request` exists in the memory index + pub fn contains_request(&self, request: &str) -> bool { + for item in self.0.iter() { + if item.request == request { + return true; + } + } + false + } + + /// Get recent Items vector sorted by `ID` DESC + pub fn recent(&self) -> Vec { + let mut recent: Vec = Vec::new(); + for item in self + .0 + .iter() + .sorted_by(|a, b| Ord::cmp(&b.request, &a.request)) + { + recent.push(item.clone()) + } + recent + } } impl Default for Memory { @@ -12,62 +58,3 @@ impl Default for Memory { Self::new() } } - -impl Memory { - // Constructors - - /// Create new `Self` - pub fn new() -> Self { - Self { - index: RefCell::new(HashMap::new()), - } - } - - // Actions - - /// Add new record with `request` as key and `id` as value - /// * validate record with same key does not exist yet - pub fn add(&self, request: String, id: i64) -> Result<()> { - // Borrow shared index access - let mut index = self.index.borrow_mut(); - - // Prevent existing key overwrite - if index.contains_key(&request) { - panic!() // unexpected - } - - // Slot should be free, let check it twice - match index.insert(request, id) { - Some(_) => panic!(), // unexpected - None => Ok(()), - } - } - - /// Delete record from index by `request` - /// * validate record key is exist - pub fn delete(&self, request: &str) -> Result<()> { - match self.index.borrow_mut().remove(request) { - Some(_) => Ok(()), - None => panic!(), // unexpected - } - } - - /// Get `id` by `request` from memory index - pub fn get(&self, request: &str) -> Option { - self.index.borrow().get(request).copied() - } - - /// Get recent requests vector sorted by `ID` DESC - pub fn recent(&self) -> Vec { - let mut recent: Vec = Vec::new(); - for (request, _) in self - .index - .borrow() - .iter() - .sorted_by(|a, b| Ord::cmp(&b.1, &a.1)) - { - recent.push(request.to_string()) - } - recent - } -}