mirror of
https://github.com/YGGverse/Yoda.git
synced 2025-02-03 23:14:13 +00:00
reorganize clone semantics, implement recently closed tabs history
This commit is contained in:
parent
3682b5bf3f
commit
ba68019614
@ -32,7 +32,7 @@ GTK 4 / Libadwaita client written in Rust
|
|||||||
* [x] Unsupported content type downloads
|
* [x] Unsupported content type downloads
|
||||||
* [ ] History
|
* [ ] History
|
||||||
* [ ] Browser window
|
* [ ] Browser window
|
||||||
* [ ] Recently closed
|
* [x] Recently closed
|
||||||
* [ ] Recently visited
|
* [ ] Recently visited
|
||||||
* [ ] Proxy
|
* [ ] Proxy
|
||||||
* [ ] Session
|
* [ ] Session
|
||||||
|
@ -8,8 +8,9 @@ use std::rc::Rc;
|
|||||||
|
|
||||||
// Config options
|
// Config options
|
||||||
|
|
||||||
const RECENT_BOOKMARKS: usize = 50;
|
|
||||||
const LABEL_MAX_LENGTH: usize = 32;
|
const LABEL_MAX_LENGTH: usize = 32;
|
||||||
|
const RECENT_BOOKMARKS: usize = 50;
|
||||||
|
const RECENTLY_CLOSED: usize = 50;
|
||||||
|
|
||||||
pub struct Menu {
|
pub struct Menu {
|
||||||
pub menu_button: MenuButton,
|
pub menu_button: MenuButton,
|
||||||
@ -131,6 +132,16 @@ impl Menu {
|
|||||||
|
|
||||||
main.append_submenu(Some("Bookmarks"), &main_bookmarks);
|
main.append_submenu(Some("Bookmarks"), &main_bookmarks);
|
||||||
|
|
||||||
|
// Main > History
|
||||||
|
let main_history = gio::Menu::new();
|
||||||
|
|
||||||
|
// Main > History > Recently closed
|
||||||
|
// * menu items dynamically generated using profile memory pool and `set_create_popup_func`
|
||||||
|
let main_history_closed = gio::Menu::new();
|
||||||
|
main_history.append_submenu(Some("Closed tabs"), &main_history_closed);
|
||||||
|
|
||||||
|
main.append_submenu(Some("History"), &main_history);
|
||||||
|
|
||||||
// Main > Tool
|
// Main > Tool
|
||||||
let main_tool = gio::Menu::new();
|
let main_tool = gio::Menu::new();
|
||||||
|
|
||||||
@ -176,6 +187,7 @@ impl Menu {
|
|||||||
let main_bookmarks = main_bookmarks.clone();
|
let main_bookmarks = main_bookmarks.clone();
|
||||||
let window_action = window_action.clone();
|
let window_action = window_action.clone();
|
||||||
move |_| {
|
move |_| {
|
||||||
|
// Bookmarks
|
||||||
main_bookmarks.remove_all();
|
main_bookmarks.remove_all();
|
||||||
for request in profile.bookmark.memory.recent(RECENT_BOOKMARKS) {
|
for request in profile.bookmark.memory.recent(RECENT_BOOKMARKS) {
|
||||||
let menu_item = gio::MenuItem::new(Some(&label(&request, LABEL_MAX_LENGTH)), None);
|
let menu_item = gio::MenuItem::new(Some(&label(&request, LABEL_MAX_LENGTH)), None);
|
||||||
@ -191,6 +203,19 @@ impl Menu {
|
|||||||
// if profile.bookmark.memory.total() > RECENT_BOOKMARKS {
|
// if profile.bookmark.memory.total() > RECENT_BOOKMARKS {
|
||||||
// @TODO
|
// @TODO
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
// History
|
||||||
|
main_history_closed.remove_all();
|
||||||
|
for request in profile.history.memory.closed.recent(RECENTLY_CLOSED) {
|
||||||
|
let menu_item = gio::MenuItem::new(Some(&label(&request, LABEL_MAX_LENGTH)), None);
|
||||||
|
menu_item.set_action_and_target_value(Some(&format!(
|
||||||
|
"{}.{}",
|
||||||
|
window_action.id,
|
||||||
|
window_action.open.simple_action.name()
|
||||||
|
)), Some(&request.to_variant()));
|
||||||
|
|
||||||
|
main_history_closed.append_item(&menu_item);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -15,8 +15,8 @@ use crate::app::browser::{
|
|||||||
};
|
};
|
||||||
use crate::Profile;
|
use crate::Profile;
|
||||||
use gtk::{
|
use gtk::{
|
||||||
glib::{GString, Propagation},
|
glib::{DateTime, GString, Propagation},
|
||||||
prelude::WidgetExt,
|
prelude::{EditableExt, WidgetExt},
|
||||||
};
|
};
|
||||||
use sqlite::Transaction;
|
use sqlite::Transaction;
|
||||||
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
use std::{cell::RefCell, collections::HashMap, rc::Rc};
|
||||||
@ -92,6 +92,7 @@ impl Tab {
|
|||||||
|
|
||||||
widget.tab_view.connect_close_page({
|
widget.tab_view.connect_close_page({
|
||||||
let index = index.clone();
|
let index = index.clone();
|
||||||
|
let profile = profile.clone();
|
||||||
move |_, item| {
|
move |_, item| {
|
||||||
// Get index ID by keyword saved
|
// Get index ID by keyword saved
|
||||||
match item.keyword() {
|
match item.keyword() {
|
||||||
@ -100,7 +101,14 @@ impl Tab {
|
|||||||
panic!("Tab index can not be empty!")
|
panic!("Tab index can not be empty!")
|
||||||
}
|
}
|
||||||
// Cleanup HashMap index
|
// Cleanup HashMap index
|
||||||
index.borrow_mut().remove(&id);
|
if let Some(item) = index.borrow_mut().remove(&id) {
|
||||||
|
// Add history record into profile memory pool
|
||||||
|
// * this action allows to recover recently closed tab (e.g. from the main menu)
|
||||||
|
profile.history.memory.closed.add(
|
||||||
|
item.page.navigation.request.widget.entry.text(),
|
||||||
|
DateTime::now_local().unwrap().to_unix(),
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
None => panic!("Undefined tab index!"),
|
None => panic!("Undefined tab index!"),
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,11 @@
|
|||||||
mod bookmark;
|
mod bookmark;
|
||||||
mod database;
|
mod database;
|
||||||
//mod history;
|
mod history;
|
||||||
mod identity;
|
mod identity;
|
||||||
|
|
||||||
use bookmark::Bookmark;
|
use bookmark::Bookmark;
|
||||||
use database::Database;
|
use database::Database;
|
||||||
|
use history::History;
|
||||||
use identity::Identity;
|
use identity::Identity;
|
||||||
|
|
||||||
use gtk::glib::{user_config_dir, DateTime};
|
use gtk::glib::{user_config_dir, DateTime};
|
||||||
@ -20,6 +21,7 @@ const DB_NAME: &str = "database.sqlite3";
|
|||||||
pub struct Profile {
|
pub struct Profile {
|
||||||
pub bookmark: Rc<Bookmark>,
|
pub bookmark: Rc<Bookmark>,
|
||||||
pub database: Rc<Database>,
|
pub database: Rc<Database>,
|
||||||
|
pub history: Rc<History>,
|
||||||
pub identity: Rc<Identity>,
|
pub identity: Rc<Identity>,
|
||||||
pub config_path: PathBuf,
|
pub config_path: PathBuf,
|
||||||
}
|
}
|
||||||
@ -87,7 +89,7 @@ impl Profile {
|
|||||||
} // unlock database
|
} // unlock database
|
||||||
|
|
||||||
// Init model
|
// Init model
|
||||||
let database = Rc::new(Database::new(connection.clone()));
|
let database = Rc::new(Database::build(&connection));
|
||||||
|
|
||||||
// Get active profile or create new one
|
// Get active profile or create new one
|
||||||
let profile_id = Rc::new(match database.active().unwrap() {
|
let profile_id = Rc::new(match database.active().unwrap() {
|
||||||
@ -98,11 +100,10 @@ impl Profile {
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
// Init bookmark component @TODO handle errors
|
// Init components
|
||||||
let bookmark = Rc::new(Bookmark::new(connection.clone(), profile_id.clone()));
|
let bookmark = Rc::new(Bookmark::build(&connection, &profile_id));
|
||||||
|
let history = Rc::new(History::build(&connection, &profile_id));
|
||||||
// Init identity component
|
let identity = Rc::new(match Identity::build(&connection, &profile_id) {
|
||||||
let identity = Rc::new(match Identity::new(connection, profile_id) {
|
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(e) => todo!("{:?}", e.to_string()),
|
Err(e) => todo!("{:?}", e.to_string()),
|
||||||
});
|
});
|
||||||
@ -110,8 +111,9 @@ impl Profile {
|
|||||||
// Result
|
// Result
|
||||||
Self {
|
Self {
|
||||||
bookmark,
|
bookmark,
|
||||||
identity,
|
|
||||||
database,
|
database,
|
||||||
|
history,
|
||||||
|
identity,
|
||||||
config_path,
|
config_path,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,7 +19,7 @@ impl Bookmark {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(connection: Rc<RwLock<Connection>>, profile_id: Rc<i64>) -> Self {
|
pub fn build(connection: &Rc<RwLock<Connection>>, profile_id: &Rc<i64>) -> Self {
|
||||||
// Init children components
|
// Init children components
|
||||||
let database = Rc::new(Database::new(connection, profile_id));
|
let database = Rc::new(Database::new(connection, profile_id));
|
||||||
let memory = Rc::new(Memory::new());
|
let memory = Rc::new(Memory::new());
|
||||||
|
@ -18,10 +18,10 @@ impl Database {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(connection: Rc<RwLock<Connection>>, profile_id: Rc<i64>) -> Self {
|
pub fn new(connection: &Rc<RwLock<Connection>>, profile_id: &Rc<i64>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
connection,
|
connection: connection.clone(),
|
||||||
profile_id,
|
profile_id: profile_id.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -17,8 +17,10 @@ impl Database {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(connection: Rc<RwLock<Connection>>) -> Self {
|
pub fn build(connection: &Rc<RwLock<Connection>>) -> Self {
|
||||||
Self { connection }
|
Self {
|
||||||
|
connection: connection.clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
|
@ -1,18 +1,24 @@
|
|||||||
mod database;
|
// mod database;
|
||||||
|
mod memory;
|
||||||
|
|
||||||
use sqlite::Transaction;
|
use memory::Memory;
|
||||||
|
|
||||||
// Tools
|
use sqlite::Connection;
|
||||||
|
use std::{rc::Rc, sync::RwLock};
|
||||||
|
|
||||||
pub fn migrate(tx: &Transaction) -> Result<(), String> {
|
pub struct History {
|
||||||
// Migrate self components
|
pub memory: Rc<Memory>, // fast search index
|
||||||
if let Err(e) = database::init(tx) {
|
}
|
||||||
return Err(e.to_string());
|
|
||||||
}
|
impl History {
|
||||||
|
// Constructors
|
||||||
// Delegate migration to childs
|
|
||||||
// nothing yet..
|
/// Create new `Self`
|
||||||
|
pub fn build(_connection: &Rc<RwLock<Connection>>, _profile_id: &Rc<i64>) -> Self {
|
||||||
// Success
|
// Init children components
|
||||||
Ok(())
|
let memory = Rc::new(Memory::new());
|
||||||
|
|
||||||
|
// Return new `Self`
|
||||||
|
Self { memory }
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -56,7 +56,7 @@ pub fn select(
|
|||||||
WHERE `profile_id` = ? AND `request` LIKE ?",
|
WHERE `profile_id` = ? AND `request` LIKE ?",
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let result = stmt.query_map((profile_id, request.unwrap_or("%")), |row| {
|
let result = stmt.query_map((profile_id, request.unwrap_or("%".to_string())), |row| {
|
||||||
Ok(Table {
|
Ok(Table {
|
||||||
id: row.get(0)?,
|
id: row.get(0)?,
|
||||||
profile_id: row.get(1)?,
|
profile_id: row.get(1)?,
|
||||||
|
24
src/profile/history/memory.rs
Normal file
24
src/profile/history/memory.rs
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
mod closed;
|
||||||
|
use closed::Closed;
|
||||||
|
|
||||||
|
/// Reduce disk usage by cache Bookmarks index in memory
|
||||||
|
pub struct Memory {
|
||||||
|
pub closed: Closed,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Memory {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Memory {
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
/// Create new `Self`
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
closed: Closed::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
src/profile/history/memory/closed.rs
Normal file
53
src/profile/history/memory/closed.rs
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
use gtk::glib::GString;
|
||||||
|
use itertools::Itertools;
|
||||||
|
use std::{cell::RefCell, collections::HashMap};
|
||||||
|
|
||||||
|
/// Reduce disk usage by cache Bookmarks index in memory
|
||||||
|
pub struct Closed {
|
||||||
|
index: RefCell<HashMap<GString, i64>>,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Default for Closed {
|
||||||
|
fn default() -> Self {
|
||||||
|
Self::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Closed {
|
||||||
|
// Constructors
|
||||||
|
|
||||||
|
/// Create new `Self`
|
||||||
|
pub fn new() -> Self {
|
||||||
|
Self {
|
||||||
|
index: RefCell::new(HashMap::new()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actions
|
||||||
|
|
||||||
|
/// Add new record
|
||||||
|
/// * replace with new one if the record already exist
|
||||||
|
pub fn add(&self, request: GString, unix_timestamp: i64) {
|
||||||
|
self.index.borrow_mut().insert(request, unix_timestamp);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get recent requests vector sorted by `ID` DESC
|
||||||
|
pub fn recent(&self, limit: usize) -> Vec<GString> {
|
||||||
|
let mut recent: Vec<GString> = Vec::new();
|
||||||
|
for (request, _) in self
|
||||||
|
.index
|
||||||
|
.borrow()
|
||||||
|
.iter()
|
||||||
|
.sorted_by(|a, b| Ord::cmp(&b.1, &a.1))
|
||||||
|
.take(limit)
|
||||||
|
{
|
||||||
|
recent.push(request.clone())
|
||||||
|
}
|
||||||
|
recent
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Get records total
|
||||||
|
pub fn total(&self) -> usize {
|
||||||
|
self.index.borrow().len()
|
||||||
|
}
|
||||||
|
}
|
@ -19,9 +19,9 @@ impl Identity {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(connection: Rc<RwLock<Connection>>, profile_id: Rc<i64>) -> Result<Self, Error> {
|
pub fn build(connection: &Rc<RwLock<Connection>>, profile_id: &Rc<i64>) -> Result<Self, Error> {
|
||||||
// Init identity database
|
// Init identity database
|
||||||
let database = Rc::new(Database::new(connection.clone()));
|
let database = Rc::new(Database::build(connection));
|
||||||
|
|
||||||
// Get active identity set for profile or create new one
|
// Get active identity set for profile or create new one
|
||||||
let profile_identity_id = Rc::new(match database.active() {
|
let profile_identity_id = Rc::new(match database.active() {
|
||||||
@ -36,7 +36,7 @@ impl Identity {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// Init gemini component
|
// Init gemini component
|
||||||
let gemini = Rc::new(match Gemini::new(connection, profile_identity_id) {
|
let gemini = Rc::new(match Gemini::build(connection, &profile_identity_id) {
|
||||||
Ok(result) => result,
|
Ok(result) => result,
|
||||||
Err(e) => return Err(Error::Gemini(e)),
|
Err(e) => return Err(Error::Gemini(e)),
|
||||||
});
|
});
|
||||||
|
@ -15,8 +15,10 @@ impl Database {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(connection: Rc<RwLock<Connection>>) -> Self {
|
pub fn build(connection: &Rc<RwLock<Connection>>) -> Self {
|
||||||
Self { connection }
|
Self {
|
||||||
|
connection: connection.clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
@ -37,7 +39,7 @@ impl Database {
|
|||||||
// Setters
|
// Setters
|
||||||
|
|
||||||
/// Create new record in `Self` database connected
|
/// Create new record in `Self` database connected
|
||||||
pub fn add(&self, profile_id: Rc<i64>, is_active: bool) -> Result<i64, Error> {
|
pub fn add(&self, profile_id: &Rc<i64>, is_active: bool) -> Result<i64, Error> {
|
||||||
// Begin new transaction
|
// Begin new transaction
|
||||||
let mut writable = self.connection.write().unwrap();
|
let mut writable = self.connection.write().unwrap();
|
||||||
let tx = writable.transaction()?;
|
let tx = writable.transaction()?;
|
||||||
@ -80,7 +82,7 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn insert(tx: &Transaction, profile_id: Rc<i64>, is_active: bool) -> Result<usize, Error> {
|
pub fn insert(tx: &Transaction, profile_id: &Rc<i64>, is_active: bool) -> Result<usize, Error> {
|
||||||
tx.execute(
|
tx.execute(
|
||||||
"INSERT INTO `profile_identity` (
|
"INSERT INTO `profile_identity` (
|
||||||
`profile_id`,
|
`profile_id`,
|
||||||
|
@ -29,16 +29,16 @@ impl Gemini {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(
|
pub fn build(
|
||||||
connection: Rc<RwLock<Connection>>,
|
connection: &Rc<RwLock<Connection>>,
|
||||||
profile_identity_id: Rc<i64>,
|
profile_identity_id: &Rc<i64>,
|
||||||
) -> Result<Self, Error> {
|
) -> Result<Self, Error> {
|
||||||
// Init components
|
// Init components
|
||||||
let auth = match Auth::new(connection.clone()) {
|
let auth = match Auth::new(connection) {
|
||||||
Ok(auth) => Rc::new(auth),
|
Ok(auth) => Rc::new(auth),
|
||||||
Err(e) => return Err(Error::Auth(e)),
|
Err(e) => return Err(Error::Auth(e)),
|
||||||
};
|
};
|
||||||
let database = Rc::new(Database::new(connection, profile_identity_id.clone()));
|
let database = Rc::new(Database::build(connection, profile_identity_id));
|
||||||
let memory = Rc::new(Memory::new());
|
let memory = Rc::new(Memory::new());
|
||||||
|
|
||||||
// Init `Self`
|
// Init `Self`
|
||||||
|
@ -21,7 +21,7 @@ impl Auth {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(connection: Rc<RwLock<Connection>>) -> Result<Self, Error> {
|
pub fn new(connection: &Rc<RwLock<Connection>>) -> Result<Self, Error> {
|
||||||
// Init `Self`
|
// Init `Self`
|
||||||
let this = Self {
|
let this = Self {
|
||||||
database: Rc::new(Database::new(connection)),
|
database: Rc::new(Database::new(connection)),
|
||||||
|
@ -16,8 +16,10 @@ impl Database {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(connection: Rc<RwLock<Connection>>) -> Self {
|
pub fn new(connection: &Rc<RwLock<Connection>>) -> Self {
|
||||||
Self { connection }
|
Self {
|
||||||
|
connection: connection.clone(),
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Actions
|
// Actions
|
||||||
|
@ -17,10 +17,10 @@ impl Database {
|
|||||||
// Constructors
|
// Constructors
|
||||||
|
|
||||||
/// Create new `Self`
|
/// Create new `Self`
|
||||||
pub fn new(connection: Rc<RwLock<Connection>>, profile_identity_id: Rc<i64>) -> Self {
|
pub fn build(connection: &Rc<RwLock<Connection>>, profile_identity_id: &Rc<i64>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
connection,
|
connection: connection.clone(),
|
||||||
profile_identity_id,
|
profile_identity_id: profile_identity_id.clone(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user