From 268af308305e1d24e0dd7347746ae291c5c4cd71 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sat, 8 Mar 2025 21:02:31 +0200 Subject: [PATCH] reorganize history memory model --- src/app/browser/window/action/bookmark.rs | 5 - src/app/browser/window/action/history_back.rs | 5 - .../browser/window/action/history_forward.rs | 5 - src/app/browser/window/action/home.rs | 5 - src/app/browser/window/header/bar/menu.rs | 22 +++-- src/app/browser/window/tab.rs | 13 +-- src/app/browser/window/tab/item/client.rs | 28 ++---- src/app/browser/window/tab/item/page.rs | 6 +- .../window/tab/item/page/navigation.rs | 4 - src/profile/history.rs | 37 ++++++- src/profile/history/item.rs | 40 ++++++++ src/profile/history/memory.rs | 96 +++++++++++++++---- src/profile/history/memory/request.rs | 64 ------------- src/profile/history/memory/tab.rs | 60 ------------ 14 files changed, 172 insertions(+), 218 deletions(-) create mode 100644 src/profile/history/item.rs delete mode 100644 src/profile/history/memory/request.rs delete mode 100644 src/profile/history/memory/tab.rs diff --git a/src/app/browser/window/action/bookmark.rs b/src/app/browser/window/action/bookmark.rs index f7eea930..fc0ccb0a 100644 --- a/src/app/browser/window/action/bookmark.rs +++ b/src/app/browser/window/action/bookmark.rs @@ -36,11 +36,6 @@ impl Bookmark { // Actions - /// Emit [activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal - pub fn activate(&self) { - self.simple_action.activate(None); - } - /// Change action [state](https://docs.gtk.org/gio/method.SimpleAction.set_state.html) /// * set `DEFAULT_STATE` on `None` pub fn change_state(&self, state: Option) { diff --git a/src/app/browser/window/action/history_back.rs b/src/app/browser/window/action/history_back.rs index b8300a0f..e6b7338e 100644 --- a/src/app/browser/window/action/history_back.rs +++ b/src/app/browser/window/action/history_back.rs @@ -36,11 +36,6 @@ impl HistoryBack { // Actions - /// Emit [activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal - pub fn activate(&self) { - self.simple_action.activate(None); - } - /// Change action [state](https://docs.gtk.org/gio/method.SimpleAction.set_state.html) /// * set `DEFAULT_STATE` on `None` pub fn change_state(&self, state: Option) { diff --git a/src/app/browser/window/action/history_forward.rs b/src/app/browser/window/action/history_forward.rs index 2117d64d..65705b62 100644 --- a/src/app/browser/window/action/history_forward.rs +++ b/src/app/browser/window/action/history_forward.rs @@ -36,11 +36,6 @@ impl HistoryForward { // Actions - /// Emit [activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal - pub fn activate(&self) { - self.simple_action.activate(None); - } - /// Change action [state](https://docs.gtk.org/gio/method.SimpleAction.set_state.html) /// * set `DEFAULT_STATE` on `None` pub fn change_state(&self, state: Option) { diff --git a/src/app/browser/window/action/home.rs b/src/app/browser/window/action/home.rs index 1647e08c..9a3c6dfa 100644 --- a/src/app/browser/window/action/home.rs +++ b/src/app/browser/window/action/home.rs @@ -36,11 +36,6 @@ impl Home { // Actions - /// Emit [activate](https://docs.gtk.org/gio/signal.SimpleAction.activate.html) signal - pub fn activate(&self) { - self.simple_action.activate(None); - } - /// Change action [state](https://docs.gtk.org/gio/method.SimpleAction.set_state.html) /// * set `DEFAULT_STATE` on `None` pub fn change_state(&self, state: Option) { diff --git a/src/app/browser/window/header/bar/menu.rs b/src/app/browser/window/header/bar/menu.rs index f5ce57b9..292e0dff 100644 --- a/src/app/browser/window/header/bar/menu.rs +++ b/src/app/browser/window/header/bar/menu.rs @@ -1,7 +1,7 @@ use super::{BrowserAction, Profile, WindowAction}; use gtk::{ gio::{self}, - glib::{GString, Uri}, + glib::{GString, Uri, UriFlags}, prelude::{ActionExt, ToVariant}, Align, MenuButton, }; @@ -220,14 +220,13 @@ impl Menu for MenuButton { // Recently closed history main_history_tab.remove_all(); - for item in profile.history.memory.tab.recent() { - let item_request = item.page.navigation.request(); // @TODO restore entire `Item` - let menu_item = gio::MenuItem::new(Some(&ellipsize(&item_request, LABEL_MAX_LENGTH)), None); + for history in profile.history.recently_closed(None) { + let menu_item = gio::MenuItem::new(Some(&ellipsize(&history.request, LABEL_MAX_LENGTH)), None); menu_item.set_action_and_target_value(Some(&format!( "{}.{}", window_action.id, window_action.load.simple_action.name() - )), Some(&item_request.to_variant())); + )), Some(&history.request.to_variant())); main_history_tab.append_item(&menu_item); } // @TODO `menu_item` @@ -238,11 +237,14 @@ impl Menu for MenuButton { main_history_request.remove_all(); let mut list: IndexMap> = IndexMap::new(); - for uri in profile.history.memory.request.recent() { - list.entry(match uri.host() { - Some(host) => host, - None => uri.to_str(), - }).or_default().push(uri); + for history in profile.history.recently_opened(None) { + match Uri::parse(&history.request, UriFlags::NONE) { + Ok(uri) => list.entry(match uri.host() { + Some(host) => host, + None => uri.to_str(), + }).or_default().push(uri), + Err(_) => continue // @TODO + } } for (group, items) in list { diff --git a/src/app/browser/window/tab.rs b/src/app/browser/window/tab.rs index 636ffa7e..c753651c 100644 --- a/src/app/browser/window/tab.rs +++ b/src/app/browser/window/tab.rs @@ -8,12 +8,7 @@ use crate::Profile; use action::Action; use adw::{TabPage, TabView}; use anyhow::Result; -use gtk::{ - gio::Icon, - glib::{DateTime, Propagation}, - prelude::ActionExt, - Box, Orientation, -}; +use gtk::{gio::Icon, glib::Propagation, prelude::ActionExt, Box, Orientation}; pub use item::Item; use menu::Menu; use sourceview::prelude::IsA; @@ -86,11 +81,7 @@ impl Tab { // keep removed `Item` reference in the memory (to reopen from the main menu) // * skip item with blank request if !item.page.navigation.request().is_empty() { - profile - .history - .memory - .tab - .add(item, DateTime::now_local().unwrap().to_unix()); + profile.history.close(&item.page.navigation.request()); } } // reassign global actions to active tab diff --git a/src/app/browser/window/tab/item/client.rs b/src/app/browser/window/tab/item/client.rs index bb8da9d7..684ec128 100644 --- a/src/app/browser/window/tab/item/client.rs +++ b/src/app/browser/window/tab/item/client.rs @@ -63,13 +63,13 @@ impl Client { Ok(uri) => match uri.scheme().as_str() { "file" => { if is_snap_history { - snap_history(&page, Some(&uri)); + snap_history(&page); } driver.file.handle(uri, feature, cancellable) } "gemini" | "titan" => { if is_snap_history { - snap_history(&page, Some(&uri)); + snap_history(&page); } driver.gemini.handle(uri, feature, cancellable) } @@ -201,23 +201,9 @@ fn search(profile: &Profile, query: &str) -> Uri { } /// Make new history record in related components -/// * optional [Uri](https://docs.gtk.org/glib/struct.Uri.html) reference wanted only for performance reasons, to not parse it twice -fn snap_history(page: &Page, uri: Option<&Uri>) { - let request = page.navigation.request(); - - // Add new record into the global memory index (used in global menu) - // * if the `Uri` is `None`, try parse it from `request` - match uri { - Some(uri) => page.profile.history.memory.request.set(uri.clone()), - None => { - // this case especially useful for some routes that contain redirects - // maybe some parental optimization wanted @TODO - if let Some(uri) = page.navigation.uri() { - page.profile.history.memory.request.set(uri); - } - } - } - - // Add new record into the page navigation history - page.item_action.history.add(request, true) +fn snap_history(page: &Page) { + page.item_action + .history + .add(page.navigation.request(), true); + page.profile.history.open(page.navigation.request()) } diff --git a/src/app/browser/window/tab/item/page.rs b/src/app/browser/window/tab/item/page.rs index 5fd40e28..5118ac7b 100644 --- a/src/app/browser/window/tab/item/page.rs +++ b/src/app/browser/window/tab/item/page.rs @@ -121,10 +121,8 @@ impl Page { self.set_needs_attention(record.is_needs_attention); // Restore child components self.navigation.restore(transaction, &record.id)?; - // Make initial page history snap using `navigation` values restored - if let Some(uri) = self.navigation.uri() { - self.profile.history.memory.request.set(uri); - } + // Make initial page history snap + self.profile.history.open(self.navigation.request()); } Ok(()) } diff --git a/src/app/browser/window/tab/item/page/navigation.rs b/src/app/browser/window/tab/item/page/navigation.rs index 963f77e2..84f2d2e7 100644 --- a/src/app/browser/window/tab/item/page/navigation.rs +++ b/src/app/browser/window/tab/item/page/navigation.rs @@ -137,10 +137,6 @@ impl Navigation { self.request.text() } - pub fn uri(&self) -> Option { - self.request.uri() - } - pub fn home(&self) -> Option { self.request.home() } diff --git a/src/profile/history.rs b/src/profile/history.rs index c289dedc..e4dbe840 100644 --- a/src/profile/history.rs +++ b/src/profile/history.rs @@ -1,13 +1,15 @@ // mod database; +mod item; mod memory; +use gtk::glib::GString; +use item::Item; use memory::Memory; - use sqlite::Connection; -use std::{rc::Rc, sync::RwLock}; +use std::{cell::RefCell, rc::Rc, sync::RwLock}; pub struct History { - pub memory: Rc, // fast search index + memory: RefCell, // fast search index } impl History { @@ -16,9 +18,36 @@ impl History { /// Create new `Self` pub fn build(_connection: &Rc>, _profile_id: &Rc) -> Self { // Init children components - let memory = Rc::new(Memory::new()); + let memory = RefCell::new(Memory::new()); // Return new `Self` Self { memory } } + + // Actions + + /// Create new history record + pub fn open(&self, request: GString) { + let mut memory = self.memory.borrow_mut(); + if !memory.open(&request) { + memory.add(Item::create(0, request)) // @TODO + } + } + + /// Close existing history record + pub fn close(&self, request: &str) { + self.memory.borrow_mut().close(request) + } + + // Getters + + /// Get recently `opened` Items vector from the memory index, sorted by ASC + pub fn recently_opened(&self, limit: Option) -> Vec { + self.memory.borrow().recently_opened(limit) + } + + /// Get recently `closed` Items vector from the memory index, sorted by ASC + pub fn recently_closed(&self, limit: Option) -> Vec { + self.memory.borrow().recently_closed(limit) + } } diff --git a/src/profile/history/item.rs b/src/profile/history/item.rs new file mode 100644 index 00000000..f0d0d09a --- /dev/null +++ b/src/profile/history/item.rs @@ -0,0 +1,40 @@ +use gtk::glib::{DateTime, GString}; + +#[derive(Clone)] +pub struct Item { + pub id: i64, + pub request: GString, + pub created: DateTime, + pub opened: DateTime, + pub closed: Option, +} + +impl Item { + // Constructors + + pub fn create(id: i64, request: GString) -> Self { + Self { + id, + request, + created: now(), + opened: now(), + closed: None, + } + } + + // Actions + + pub fn open(&mut self) { + self.opened = now() + } + + pub fn close(&mut self) { + self.closed = Some(now()) + } +} + +// Tools + +fn now() -> DateTime { + DateTime::now_local().unwrap() +} diff --git a/src/profile/history/memory.rs b/src/profile/history/memory.rs index 934c6019..590282af 100644 --- a/src/profile/history/memory.rs +++ b/src/profile/history/memory.rs @@ -1,13 +1,81 @@ -mod request; -mod tab; - -use request::Request; -use tab::Tab; +use super::Item; +use itertools::Itertools; /// Reduce disk usage by cache Bookmarks index in memory -pub struct Memory { - pub request: Request, - pub tab: Tab, +pub struct Memory(Vec); + +impl Memory { + // Constructors + + /// Create new `Self` + pub fn new() -> Self { + Self(Vec::new()) + } + + // Actions + + pub fn add(&mut self, item: Item) { + self.0.push(item) + } + + /// Update `opened` time for given `request` + /// * return `false` if the `request` not found in memory index + pub fn open(&mut self, request: &str) -> bool { + for record in &mut self.0 { + if record.request == request { + record.open(); + return true; + } + } + false + } + + /// Update `closed` time for given `request` + pub fn close(&mut self, request: &str) { + for record in &mut self.0 { + if record.request == request { + record.close(); + return; + } + } + } + + // Getters + + /// Get recent Items vector sorted by `closed` ASC + pub fn recently_closed(&self, limit: Option) -> Vec { + let mut recent: Vec = Vec::new(); + for (i, item) in self + .0 + .iter() + .filter(|x| x.closed.is_some()) + .sorted_by(|a, b| Ord::cmp(&a.closed, &b.closed)) + .enumerate() + { + if limit.is_some_and(|l| i > l) { + break; + } + recent.push(item.clone()) + } + recent + } + + /// Get recent Items vector sorted by `opened` ASC + pub fn recently_opened(&self, limit: Option) -> Vec { + let mut recent: Vec = Vec::new(); + for (i, item) in self + .0 + .iter() + .sorted_by(|a, b| Ord::cmp(&a.opened, &b.opened)) + .enumerate() + { + if limit.is_some_and(|l| i > l) { + break; + } + recent.push(item.clone()) + } + recent + } } impl Default for Memory { @@ -15,15 +83,3 @@ impl Default for Memory { Self::new() } } - -impl Memory { - // Constructors - - /// Create new `Self` - pub fn new() -> Self { - Self { - request: Request::new(), - tab: Tab::new(), - } - } -} diff --git a/src/profile/history/memory/request.rs b/src/profile/history/memory/request.rs deleted file mode 100644 index 726b3b94..00000000 --- a/src/profile/history/memory/request.rs +++ /dev/null @@ -1,64 +0,0 @@ -use gtk::glib::{DateTime, GString, Uri}; -use itertools::Itertools; -use std::{cell::RefCell, collections::HashMap}; - -pub struct Value { - pub unix_timestamp: i64, - pub uri: Uri, -} - -/// Recent request history -pub struct Request { - index: RefCell>, -} - -impl Default for Request { - fn default() -> Self { - Self::new() - } -} - -impl Request { - // Constructors - - /// Create new `Self` - pub fn new() -> Self { - Self { - index: RefCell::new(HashMap::new()), - } - } - - // Actions - - /// Add new record with `request` as key and `unix_timestamp` as value - /// * replace with new value if `request` already exists - pub fn set(&self, uri: Uri) { - self.index.borrow_mut().insert( - uri.to_str(), - Value { - unix_timestamp: DateTime::now_local().unwrap().to_unix(), - uri, - }, - ); - } - - /// Get recent records vector - /// * sorted by `unix_timestamp` DESC - pub fn recent(&self) -> Vec { - let mut recent: Vec = Vec::new(); - for (_, value) in self - .index - .borrow() - .iter() - .sorted_by(|a, b| Ord::cmp(&b.1.unix_timestamp, &a.1.unix_timestamp)) - { - recent.push(value.uri.clone()) - } - recent - } - - /// Get records total - pub fn total(&self) -> usize { - self.index.borrow().len() - } -} diff --git a/src/profile/history/memory/tab.rs b/src/profile/history/memory/tab.rs deleted file mode 100644 index ad30ff56..00000000 --- a/src/profile/history/memory/tab.rs +++ /dev/null @@ -1,60 +0,0 @@ -use crate::app::browser::window::tab::Item; -use itertools::Itertools; -use std::{cell::RefCell, rc::Rc}; - -pub struct Record { - pub item: Rc, - pub unix_timestamp: i64, -} - -/// Recently closed tabs index -pub struct Tab { - index: RefCell>, -} - -impl Default for Tab { - fn default() -> Self { - Self::new() - } -} - -impl Tab { - // Constructors - - /// Create new `Self` - pub fn new() -> Self { - Self { - index: RefCell::new(Vec::new()), - } - } - - // Actions - - /// Add new record - /// * replace with new one if the record already exist - pub fn add(&self, item: Rc, unix_timestamp: i64) { - self.index.borrow_mut().push(Record { - item, - unix_timestamp, - }); - } - - /// Get recent `Item` vector sorted by `unix_timestamp` DESC - pub fn recent(&self) -> Vec> { - let mut recent: Vec> = Vec::new(); - for record in self - .index - .borrow() - .iter() - .sorted_by(|a, b| Ord::cmp(&b.unix_timestamp, &a.unix_timestamp)) - { - recent.push(record.item.clone()) - } - recent - } - - /// Get records total - pub fn total(&self) -> usize { - self.index.borrow().len() - } -}