use anyhow crate, return id on insert

This commit is contained in:
yggverse 2025-03-07 18:14:37 +02:00
parent e859b97d79
commit 5effd63575
42 changed files with 496 additions and 1164 deletions

View file

@ -30,6 +30,7 @@ version = "0.9.1"
[dependencies] [dependencies]
ansi-parser = "0.9.1" ansi-parser = "0.9.1"
anyhow = "1.0.97"
ggemini = "0.17.0" ggemini = "0.17.0"
ggemtext = "0.3.1" ggemtext = "0.3.1"
indexmap = "2.7.0" indexmap = "2.7.0"

View file

@ -1,10 +1,10 @@
pub mod browser; pub mod browser;
mod database; mod database;
use browser::Browser;
use crate::profile::Profile; use crate::profile::Profile;
use adw::Application; use adw::Application;
use anyhow::Result;
use browser::Browser;
use gtk::{ use gtk::{
glib::ExitCode, glib::ExitCode,
prelude::{ActionExt, ApplicationExt, ApplicationExtManual, GtkApplicationExt}, prelude::{ActionExt, ApplicationExt, ApplicationExtManual, GtkApplicationExt},
@ -294,11 +294,9 @@ impl App {
} }
// Tools // Tools
fn migrate(tx: &Transaction) -> Result<(), String> { fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
browser::migrate(tx)?; browser::migrate(tx)?;

View file

@ -11,6 +11,7 @@ use window::Window;
use crate::Profile; use crate::Profile;
use adw::{prelude::AdwDialogExt, AboutDialog, Application}; use adw::{prelude::AdwDialogExt, AboutDialog, Application};
use anyhow::Result;
use gtk::{ use gtk::{
gio::{Cancellable, File}, gio::{Cancellable, File},
prelude::GtkWindowExt, prelude::GtkWindowExt,
@ -109,70 +110,43 @@ impl Browser {
// Actions // Actions
pub fn clean(&self, transaction: &Transaction, app_id: i64) -> Result<(), String> { pub fn clean(&self, transaction: &Transaction, app_id: i64) -> Result<()> {
match database::select(transaction, app_id) { for record in database::select(transaction, app_id)? {
Ok(records) => { database::delete(transaction, record.id)?;
for record in records { // Delegate clean action to childs
match database::delete(transaction, record.id) { self.window.clean(transaction, record.id)?;
Ok(_) => { self.widget.clean(transaction, record.id)?;
// Delegate clean action to childs /* @TODO
self.window.clean(transaction, record.id)?; self.header.clean(transaction, &record.id)?; */
self.widget.clean(transaction, record.id)?;
/* @TODO
self.header.clean(transaction, &record.id)?; */
}
Err(e) => return Err(e.to_string()),
}
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
pub fn restore(&self, transaction: &Transaction, app_id: i64) -> Result<(), String> { pub fn restore(&self, transaction: &Transaction, app_id: i64) -> Result<()> {
match database::select(transaction, app_id) { for record in database::select(transaction, app_id)? {
Ok(records) => { // Delegate restore action to childs
for record in records { self.widget.restore(transaction, record.id)?;
// Delegate restore action to childs self.window.restore(transaction, record.id)?;
self.widget.restore(transaction, record.id)?; /* @TODO
self.window.restore(transaction, record.id)?; self.header.restore(transaction, &record.id)?; */
/* @TODO
self.header.restore(transaction, &record.id)?; */
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
pub fn save(&self, transaction: &Transaction, app_id: i64) -> Result<(), String> { pub fn save(&self, transaction: &Transaction, app_id: i64) -> Result<()> {
match database::insert(transaction, app_id) { let id = database::insert(transaction, app_id)?;
Ok(_) => { // Delegate save action to childs
let id = database::last_insert_id(transaction); self.widget.save(transaction, id)?;
self.window.save(transaction, id)?;
// Delegate save action to childs /* @TODO
self.widget.save(transaction, id)?; self.header.save(transaction, &id)?; */
self.window.save(transaction, id)?;
/* @TODO
self.header.save(transaction, &id)?; */
}
Err(e) => return Err(e.to_string()),
}
Ok(()) Ok(())
} }
pub fn init(&self, application: Option<&Application>) -> &Self { pub fn init(&self, application: Option<&Application>) -> &Self {
// Assign browser window to this application // Assign browser window to this application
self.widget.application_window.set_application(application); // @TODO self.widget.application_window.set_application(application); // @TODO
// Init main window
// Init main window
self.window.init(); self.window.init();
self self
} }
@ -184,11 +158,9 @@ impl Browser {
} }
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
/* @TODO /* @TODO

View file

@ -1,12 +1,13 @@
use sqlite::{Error, Transaction}; use anyhow::Result;
use sqlite::Transaction;
pub struct Table { pub struct Table {
pub id: i64, pub id: i64,
// pub app_id: i64, not in use // pub app_id: i64, not in use
} }
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser` "CREATE TABLE IF NOT EXISTS `app_browser`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -15,14 +16,15 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
FOREIGN KEY (`app_id`) REFERENCES `app`(`id`) FOREIGN KEY (`app_id`) REFERENCES `app`(`id`)
)", )",
[], [],
) )?)
} }
pub fn insert(tx: &Transaction, app_id: i64) -> Result<usize, Error> { pub fn insert(tx: &Transaction, app_id: i64) -> Result<i64> {
tx.execute("INSERT INTO `app_browser` (`app_id`) VALUES (?)", [app_id]) tx.execute("INSERT INTO `app_browser` (`app_id`) VALUES (?)", [app_id])?;
Ok(tx.last_insert_rowid())
} }
pub fn select(tx: &Transaction, app_id: i64) -> Result<Vec<Table>, Error> { pub fn select(tx: &Transaction, app_id: i64) -> Result<Vec<Table>> {
let mut stmt = tx.prepare("SELECT `id`, `app_id` FROM `app_browser` WHERE `app_id` = ?")?; let mut stmt = tx.prepare("SELECT `id`, `app_id` FROM `app_browser` WHERE `app_id` = ?")?;
let result = stmt.query_map([app_id], |row| { let result = stmt.query_map([app_id], |row| {
@ -42,10 +44,6 @@ pub fn select(tx: &Transaction, app_id: i64) -> Result<Vec<Table>, Error> {
Ok(records) Ok(records)
} }
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> { pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
tx.execute("DELETE FROM `app_browser` WHERE `id` = ?", [id]) Ok(tx.execute("DELETE FROM `app_browser` WHERE `id` = ?", [id])?)
}
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
} }

View file

@ -2,6 +2,7 @@ mod database;
use super::Window; use super::Window;
use adw::ApplicationWindow; use adw::ApplicationWindow;
use anyhow::Result;
use gtk::{ use gtk::{
gio::SimpleActionGroup, gio::SimpleActionGroup,
glib::GString, glib::GString,
@ -59,74 +60,44 @@ impl Widget {
} }
// Actions // Actions
pub fn clean(&self, transaction: &Transaction, app_browser_id: i64) -> Result<(), String> { pub fn clean(&self, transaction: &Transaction, app_browser_id: i64) -> Result<()> {
match database::select(transaction, app_browser_id) { for record in database::select(transaction, app_browser_id)? {
Ok(records) => { database::delete(transaction, record.id)?;
for record in records {
match database::delete(transaction, record.id) {
Ok(_) => {
// Delegate clean action to childs
// nothing yet..
}
Err(e) => return Err(e.to_string()),
}
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
pub fn restore(&self, transaction: &Transaction, app_browser_id: i64) -> Result<(), String> { pub fn restore(&self, transaction: &Transaction, app_browser_id: i64) -> Result<()> {
match database::select(transaction, app_browser_id) { for record in database::select(transaction, app_browser_id)? {
Ok(records) => { // Restore widget
for record in records { self.application_window.set_maximized(record.is_maximized);
// Restore widget self.application_window
self.application_window.set_maximized(record.is_maximized); .set_default_size(record.default_width, record.default_height);
self.application_window
.set_default_size(record.default_width, record.default_height);
// Delegate restore action to childs // Delegate restore action to childs
// nothing yet.. // nothing yet..
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
pub fn save(&self, transaction: &Transaction, app_browser_id: i64) -> Result<(), String> { pub fn save(&self, transaction: &Transaction, app_browser_id: i64) -> Result<()> {
match database::insert( database::insert(
transaction, transaction,
app_browser_id, app_browser_id,
self.application_window.default_width(), self.application_window.default_width(),
self.application_window.default_height(), self.application_window.default_height(),
self.application_window.is_maximized(), self.application_window.is_maximized(),
) { )?;
Ok(_) => {
// Delegate save action to childs
// let id = self.database.last_insert_id(transaction);
// nothing yet..
}
Err(e) => return Err(e.to_string()),
}
Ok(()) Ok(())
} }
} }
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
// nothing yet.. // nothing yet..
// Success // Success
Ok(()) Ok(())
} }

View file

@ -1,4 +1,5 @@
use sqlite::{Error, Transaction}; use anyhow::Result;
use sqlite::Transaction;
pub struct Table { pub struct Table {
pub id: i64, pub id: i64,
@ -8,8 +9,8 @@ pub struct Table {
pub is_maximized: bool, pub is_maximized: bool,
} }
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_widget` "CREATE TABLE IF NOT EXISTS `app_browser_widget`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -21,7 +22,7 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
FOREIGN KEY (`app_browser_id`) REFERENCES `app_browser`(`id`) FOREIGN KEY (`app_browser_id`) REFERENCES `app_browser`(`id`)
)", )",
[], [],
) )?)
} }
pub fn insert( pub fn insert(
@ -30,7 +31,7 @@ pub fn insert(
default_width: i32, default_width: i32,
default_height: i32, default_height: i32,
is_maximized: bool, is_maximized: bool,
) -> Result<usize, Error> { ) -> Result<i64> {
tx.execute( tx.execute(
"INSERT INTO `app_browser_widget` ( "INSERT INTO `app_browser_widget` (
`app_browser_id`, `app_browser_id`,
@ -44,10 +45,11 @@ pub fn insert(
default_height as i64, default_height as i64,
is_maximized as i64, is_maximized as i64,
], ],
) )?;
Ok(tx.last_insert_rowid())
} }
pub fn select(tx: &Transaction, app_browser_id: i64) -> Result<Vec<Table>, Error> { pub fn select(tx: &Transaction, app_browser_id: i64) -> Result<Vec<Table>> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,
`app_browser_id`, `app_browser_id`,
@ -76,11 +78,6 @@ pub fn select(tx: &Transaction, app_browser_id: i64) -> Result<Vec<Table>, Error
Ok(records) Ok(records)
} }
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> { pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
tx.execute("DELETE FROM `app_browser_widget` WHERE `id` = ?", [id]) Ok(tx.execute("DELETE FROM `app_browser_widget` WHERE `id` = ?", [id])?)
} }
/* not in use
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
} */

View file

@ -3,16 +3,16 @@ mod database;
mod header; mod header;
pub mod tab; pub mod tab;
use action::{Action, Position};
use adw::ToolbarView;
use header::Header;
use sqlite::Transaction;
use tab::Tab;
use super::Action as BrowserAction; use super::Action as BrowserAction;
use crate::Profile; use crate::Profile;
use action::{Action, Position};
use adw::ToolbarView;
use anyhow::Result;
use gtk::{prelude::BoxExt, Box, Orientation}; use gtk::{prelude::BoxExt, Box, Orientation};
use header::Header;
use sqlite::Transaction;
use std::rc::Rc; use std::rc::Rc;
use tab::Tab;
pub struct Window { pub struct Window {
pub action: Rc<Action>, pub action: Rc<Action>,
@ -145,52 +145,26 @@ impl Window {
self.tab.escape(); self.tab.escape();
} }
pub fn clean(&self, transaction: &Transaction, app_browser_id: i64) -> Result<(), String> { pub fn clean(&self, transaction: &Transaction, app_browser_id: i64) -> Result<()> {
match database::select(transaction, app_browser_id) { for record in database::select(transaction, app_browser_id)? {
Ok(records) => { database::delete(transaction, record.id)?;
for record in records { // Delegate clean action to childs
match database::delete(transaction, record.id) { self.tab.clean(transaction, record.id)?;
Ok(_) => {
// Delegate clean action to childs
self.tab.clean(transaction, record.id)?;
}
Err(e) => return Err(e.to_string()),
}
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
pub fn restore(&self, transaction: &Transaction, app_browser_id: i64) -> Result<(), String> { pub fn restore(&self, transaction: &Transaction, app_browser_id: i64) -> Result<()> {
match database::select(transaction, app_browser_id) { for record in database::select(transaction, app_browser_id)? {
Ok(records) => { // Delegate restore action to childs
for record in records { self.tab.restore(transaction, record.id)?;
// Delegate restore action to childs
self.tab.restore(transaction, record.id)?;
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
pub fn save(&self, transaction: &Transaction, app_browser_id: i64) -> Result<(), String> { pub fn save(&self, transaction: &Transaction, app_browser_id: i64) -> Result<()> {
match database::insert(transaction, app_browser_id) { self.tab
Ok(_) => { .save(transaction, database::insert(transaction, app_browser_id)?)?;
// Delegate save action to childs
if let Err(e) = self
.tab
.save(transaction, database::last_insert_id(transaction))
{
return Err(e.to_string());
}
}
Err(e) => return Err(e.to_string()),
}
Ok(()) Ok(())
} }
@ -200,11 +174,9 @@ impl Window {
} }
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
tab::migrate(tx)?; tab::migrate(tx)?;

View file

@ -1,12 +1,13 @@
use sqlite::{Error, Transaction}; use anyhow::Result;
use sqlite::Transaction;
pub struct Table { pub struct Table {
pub id: i64, pub id: i64,
// pub app_browser_id: i64, not in use // pub app_browser_id: i64, not in use
} }
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window` "CREATE TABLE IF NOT EXISTS `app_browser_window`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -15,17 +16,18 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
FOREIGN KEY (`app_browser_id`) REFERENCES `app_browser`(`id`) FOREIGN KEY (`app_browser_id`) REFERENCES `app_browser`(`id`)
)", )",
[], [],
) )?)
} }
pub fn insert(tx: &Transaction, app_browser_id: i64) -> Result<usize, Error> { pub fn insert(tx: &Transaction, app_browser_id: i64) -> Result<i64> {
tx.execute( tx.execute(
"INSERT INTO `app_browser_window` (`app_browser_id`) VALUES (?)", "INSERT INTO `app_browser_window` (`app_browser_id`) VALUES (?)",
[app_browser_id], [app_browser_id],
) )?;
Ok(tx.last_insert_rowid())
} }
pub fn select(tx: &Transaction, app_browser_id: i64) -> Result<Vec<Table>, Error> { pub fn select(tx: &Transaction, app_browser_id: i64) -> Result<Vec<Table>> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,
`app_browser_id` FROM `app_browser_window` `app_browser_id` FROM `app_browser_window`
@ -49,10 +51,6 @@ pub fn select(tx: &Transaction, app_browser_id: i64) -> Result<Vec<Table>, Error
Ok(records) Ok(records)
} }
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> { pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
tx.execute("DELETE FROM `app_browser_window` WHERE `id` = ?", [id]) Ok(tx.execute("DELETE FROM `app_browser_window` WHERE `id` = ?", [id])?)
}
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
} }

View file

@ -8,6 +8,7 @@ use super::{Action as WindowAction, BrowserAction, Position};
use crate::Profile; use crate::Profile;
use action::Action; use action::Action;
use adw::{TabPage, TabView}; use adw::{TabPage, TabView};
use anyhow::Result;
use error::Error; use error::Error;
use gtk::{ use gtk::{
gio::Icon, gio::Icon,
@ -309,102 +310,68 @@ impl Tab {
} }
} }
pub fn clean( pub fn clean(&self, transaction: &Transaction, app_browser_window_id: i64) -> Result<()> {
&self, for record in database::select(transaction, app_browser_window_id)? {
transaction: &Transaction, database::delete(transaction, record.id)?;
app_browser_window_id: i64, // Delegate clean action to childs
) -> Result<(), String> { for (_, item) in self.index.borrow().iter() {
match database::select(transaction, app_browser_window_id) { item.clean(transaction, record.id)?
Ok(records) => {
for record in records {
match database::delete(transaction, record.id) {
Ok(_) => {
// Delegate clean action to childs
for (_, item) in self.index.borrow().iter() {
item.clean(transaction, record.id)?
}
}
Err(e) => return Err(e.to_string()),
}
}
} }
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
pub fn restore( pub fn restore(&self, transaction: &Transaction, app_browser_window_id: i64) -> Result<()> {
&self, for tab_record in database::select(transaction, app_browser_window_id)? {
transaction: &Transaction, for item_record in item::restore(transaction, tab_record.id)? {
app_browser_window_id: i64, // Generate new `TabPage` with blank `Widget`
) -> Result<(), String> { let (tab_page, target_child) =
match database::select(transaction, app_browser_window_id) { new_tab_page(&self.tab_view, Position::Number(item_record.page_position));
Ok(tab_records) => {
for tab_record in tab_records {
for item_record in item::restore(transaction, tab_record.id)? {
// Generate new `TabPage` with blank `Widget`
let (tab_page, target_child) = new_tab_page(
&self.tab_view,
Position::Number(item_record.page_position),
);
// Init new tab item // Init new tab item
let item = Rc::new(Item::build( let item = Rc::new(Item::build(
(&tab_page, &target_child), (&tab_page, &target_child),
&self.profile, &self.profile,
// Actions // Actions
(&self.browser_action, &self.window_action, &self.action), (&self.browser_action, &self.window_action, &self.action),
// Options // Options
None, None,
false, false,
)); ));
// Relate with GTK `TabPage` with app `Item` // Relate with GTK `TabPage` with app `Item`
self.index self.index
.borrow_mut() .borrow_mut()
.insert(item.tab_page.clone(), item.clone()); .insert(item.tab_page.clone(), item.clone());
// Setup // Setup
self.tab_view self.tab_view
.set_page_pinned(&item.tab_page, item_record.is_pinned); .set_page_pinned(&item.tab_page, item_record.is_pinned);
if item_record.is_selected { if item_record.is_selected {
self.tab_view.set_selected_page(&item.tab_page); self.tab_view.set_selected_page(&item.tab_page);
}
// Forcefully update global actions on HashMap index build complete
// * `selected_page_notify` runs this action also, just before Item init @TODO
update_actions(
&self.tab_view,
self.tab_view.selected_page().as_ref(),
&self.index,
&self.window_action,
);
// Restore children components
item.page.restore(transaction, item_record.id)?;
}
} }
// Forcefully update global actions on HashMap index build complete
// * `selected_page_notify` runs this action also, just before Item init @TODO
update_actions(
&self.tab_view,
self.tab_view.selected_page().as_ref(),
&self.index,
&self.window_action,
);
// Restore children components
item.page.restore(transaction, item_record.id)?;
} }
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
pub fn save( pub fn save(&self, transaction: &Transaction, app_browser_window_id: i64) -> Result<()> {
&self, let id = database::insert(transaction, app_browser_window_id)?;
transaction: &Transaction, for (_, item) in self.index.borrow().iter() {
app_browser_window_id: i64, item.save(transaction, id, self.tab_view.page_position(&item.tab_page))?;
) -> Result<(), String> {
match database::insert(transaction, app_browser_window_id) {
Ok(_) => {
// Delegate save action to childs
let id = database::last_insert_id(transaction);
for (_, item) in self.index.borrow().iter() {
item.save(transaction, id, self.tab_view.page_position(&item.tab_page))?;
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
@ -430,11 +397,9 @@ impl Tab {
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
item::migrate(tx)?; item::migrate(tx)?;

View file

@ -1,12 +1,13 @@
use sqlite::{Error, Transaction}; use anyhow::Result;
use sqlite::Transaction;
pub struct Table { pub struct Table {
pub id: i64, pub id: i64,
// pub app_browser_window_id: i64, not in use // pub app_browser_window_id: i64, not in use
} }
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab` "CREATE TABLE IF NOT EXISTS `app_browser_window_tab`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -15,19 +16,20 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
FOREIGN KEY (`app_browser_window_id`) REFERENCES `app_browser_window`(`id`) FOREIGN KEY (`app_browser_window_id`) REFERENCES `app_browser_window`(`id`)
)", )",
[], [],
) )?)
} }
pub fn insert(tx: &Transaction, app_browser_window_id: i64) -> Result<usize, Error> { pub fn insert(tx: &Transaction, app_browser_window_id: i64) -> Result<i64> {
tx.execute( tx.execute(
"INSERT INTO `app_browser_window_tab` ( "INSERT INTO `app_browser_window_tab` (
`app_browser_window_id` `app_browser_window_id`
) VALUES (?)", ) VALUES (?)",
[app_browser_window_id], [app_browser_window_id],
) )?;
Ok(tx.last_insert_rowid())
} }
pub fn select(tx: &Transaction, app_browser_window_id: i64) -> Result<Vec<Table>, Error> { pub fn select(tx: &Transaction, app_browser_window_id: i64) -> Result<Vec<Table>> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,
`app_browser_window_id` FROM `app_browser_window_tab` `app_browser_window_id` FROM `app_browser_window_tab`
@ -51,10 +53,6 @@ pub fn select(tx: &Transaction, app_browser_window_id: i64) -> Result<Vec<Table>
Ok(records) Ok(records)
} }
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> { pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
tx.execute("DELETE FROM `app_browser_window_tab` WHERE `id` = ?", [id]) Ok(tx.execute("DELETE FROM `app_browser_window_tab` WHERE `id` = ?", [id])?)
}
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
} }

View file

@ -7,6 +7,7 @@ use super::{Action as TabAction, BrowserAction, WindowAction};
use crate::Profile; use crate::Profile;
use action::Action; use action::Action;
use adw::TabPage; use adw::TabPage;
use anyhow::Result;
use client::Client; use client::Client;
use gtk::{ use gtk::{
prelude::{ActionExt, ActionMapExt, BoxExt}, prelude::{ActionExt, ActionMapExt, BoxExt},
@ -164,26 +165,12 @@ impl Item {
} }
} }
pub fn clean( pub fn clean(&self, transaction: &Transaction, app_browser_window_tab_id: i64) -> Result<()> {
&self, for record in database::select(transaction, app_browser_window_tab_id)? {
transaction: &Transaction, database::delete(transaction, record.id)?;
app_browser_window_tab_id: i64, // Delegate clean action to the item childs
) -> Result<(), String> { self.page.clean(transaction, record.id)?;
match database::select(transaction, app_browser_window_tab_id) {
Ok(records) => {
for record in records {
match database::delete(transaction, record.id) {
Ok(_) => {
// Delegate clean action to the item childs
self.page.clean(transaction, record.id)?;
}
Err(e) => return Err(e.to_string()),
}
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
@ -192,32 +179,23 @@ impl Item {
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_id: i64, app_browser_window_tab_id: i64,
page_position: i32, page_position: i32,
) -> Result<(), String> { ) -> Result<()> {
match database::insert( let id = database::insert(
transaction, transaction,
app_browser_window_tab_id, app_browser_window_tab_id,
page_position, page_position,
self.tab_page.is_pinned(), self.tab_page.is_pinned(),
self.tab_page.is_selected(), self.tab_page.is_selected(),
) { )?;
Ok(_) => { self.page.save(transaction, id)?;
// Delegate save action to childs
let id = database::last_insert_id(transaction);
self.page.save(transaction, id)?;
}
Err(e) => return Err(e.to_string()),
}
Ok(()) Ok(())
} }
} }
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
page::migrate(tx)?; page::migrate(tx)?;
@ -231,9 +209,6 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> {
pub fn restore( pub fn restore(
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_id: i64, app_browser_window_tab_id: i64,
) -> Result<Vec<database::Table>, String> { ) -> Result<Vec<database::Table>> {
match database::select(transaction, app_browser_window_tab_id) { database::select(transaction, app_browser_window_tab_id)
Ok(records) => Ok(records),
Err(e) => Err(e.to_string()),
}
} }

View file

@ -1,4 +1,5 @@
use sqlite::{Error, Transaction}; use anyhow::Result;
use sqlite::Transaction;
pub struct Table { pub struct Table {
pub id: i64, pub id: i64,
@ -8,8 +9,8 @@ pub struct Table {
pub is_selected: bool, pub is_selected: bool,
} }
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item` "CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -21,7 +22,7 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
FOREIGN KEY (`app_browser_window_tab_id`) REFERENCES `app_browser_window_tab` (`id`) FOREIGN KEY (`app_browser_window_tab_id`) REFERENCES `app_browser_window_tab` (`id`)
)", )",
[], [],
) )?)
} }
pub fn insert( pub fn insert(
@ -30,7 +31,7 @@ pub fn insert(
page_position: i32, page_position: i32,
is_pinned: bool, is_pinned: bool,
is_selected: bool, is_selected: bool,
) -> Result<usize, Error> { ) -> Result<i64> {
tx.execute( tx.execute(
"INSERT INTO `app_browser_window_tab_item` ( "INSERT INTO `app_browser_window_tab_item` (
`app_browser_window_tab_id`, `app_browser_window_tab_id`,
@ -44,10 +45,11 @@ pub fn insert(
is_pinned as i64, is_pinned as i64,
is_selected as i64, is_selected as i64,
], ],
) )?;
Ok(tx.last_insert_rowid())
} }
pub fn select(tx: &Transaction, app_browser_window_tab_id: i64) -> Result<Vec<Table>, Error> { pub fn select(tx: &Transaction, app_browser_window_tab_id: i64) -> Result<Vec<Table>> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,
`app_browser_window_tab_id`, `app_browser_window_tab_id`,
@ -79,13 +81,9 @@ pub fn select(tx: &Transaction, app_browser_window_tab_id: i64) -> Result<Vec<Ta
Ok(records) Ok(records)
} }
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> { pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
tx.execute( Ok(tx.execute(
"DELETE FROM `app_browser_window_tab_item` WHERE `id` = ?", "DELETE FROM `app_browser_window_tab_item` WHERE `id` = ?",
[id], [id],
) )?)
}
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
} }

View file

@ -7,6 +7,7 @@ mod search;
use super::{Action as ItemAction, BrowserAction, Profile, TabAction, WindowAction}; use super::{Action as ItemAction, BrowserAction, Profile, TabAction, WindowAction};
use adw::TabPage; use adw::TabPage;
use anyhow::Result;
use content::Content; use content::Content;
use error::Error; use error::Error;
use input::Input; use input::Input;
@ -104,20 +105,11 @@ impl Page {
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_id: i64, app_browser_window_tab_item_id: i64,
) -> Result<(), String> { ) -> Result<()> {
match database::select(transaction, app_browser_window_tab_item_id) { for record in database::select(transaction, app_browser_window_tab_item_id)? {
Ok(records) => { database::delete(transaction, record.id)?;
for record in records { // Delegate clean action to the item childs
match database::delete(transaction, record.id) { self.navigation.clean(transaction, &record.id)?;
Ok(_) => {
// Delegate clean action to the item childs
self.navigation.clean(transaction, &record.id)?;
}
Err(e) => return Err(e.to_string()),
}
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
@ -127,25 +119,20 @@ impl Page {
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_id: i64, app_browser_window_tab_item_id: i64,
) -> Result<(), String> { ) -> Result<()> {
// Begin page restore // Begin page restore
match database::select(transaction, app_browser_window_tab_item_id) { for record in database::select(transaction, app_browser_window_tab_item_id)? {
Ok(records) => { // Restore `Self`
for record in records { if let Some(title) = record.title {
// Restore `Self` self.set_title(title.as_str());
if let Some(title) = record.title { }
self.set_title(title.as_str()); self.set_needs_attention(record.is_needs_attention);
} // Restore child components
self.set_needs_attention(record.is_needs_attention); self.navigation.restore(transaction, &record.id)?;
// Restore child components // Make initial page history snap using `navigation` values restored
self.navigation.restore(transaction, &record.id)?; if let Some(uri) = self.navigation.uri() {
// Make initial page history snap using `navigation` values restored self.profile.history.memory.request.set(uri);
if let Some(uri) = self.navigation.uri() {
self.profile.history.memory.request.set(uri);
}
}
} }
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
@ -155,10 +142,10 @@ impl Page {
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_id: i64, app_browser_window_tab_item_id: i64,
) -> Result<(), String> { ) -> Result<()> {
// Keep value in memory until operation complete // Keep value in memory until operation complete
let title = self.tab_page.title(); let title = self.tab_page.title();
match database::insert( let id = database::insert(
transaction, transaction,
app_browser_window_tab_item_id, app_browser_window_tab_item_id,
self.tab_page.needs_attention(), self.tab_page.needs_attention(),
@ -166,15 +153,9 @@ impl Page {
true => None, true => None,
false => Some(title.as_str()), false => Some(title.as_str()),
}, },
) { )?;
Ok(_) => { // Delegate save action to childs
let id = database::last_insert_id(transaction); self.navigation.save(transaction, &id)?;
// Delegate save action to childs
self.navigation.save(transaction, &id)?;
}
Err(e) => return Err(e.to_string()),
}
Ok(()) Ok(())
} }
@ -198,11 +179,9 @@ impl Page {
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
navigation::migrate(tx)?; navigation::migrate(tx)?;

View file

@ -1,4 +1,5 @@
use sqlite::{Error, Transaction}; use anyhow::Result;
use sqlite::Transaction;
pub struct Table { pub struct Table {
pub id: i64, pub id: i64,
@ -7,8 +8,8 @@ pub struct Table {
pub title: Option<String>, pub title: Option<String>,
} }
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page` "CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -19,7 +20,7 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
FOREIGN KEY (`app_browser_window_tab_item_id`) REFERENCES `app_browser_window_tab_item` (`id`) FOREIGN KEY (`app_browser_window_tab_item_id`) REFERENCES `app_browser_window_tab_item` (`id`)
)", )",
[], [],
) )?)
} }
pub fn insert( pub fn insert(
@ -27,7 +28,7 @@ pub fn insert(
app_browser_window_tab_item_id: i64, app_browser_window_tab_item_id: i64,
is_needs_attention: bool, is_needs_attention: bool,
title: Option<&str>, title: Option<&str>,
) -> Result<usize, Error> { ) -> Result<i64> {
tx.execute( tx.execute(
"INSERT INTO `app_browser_window_tab_item_page` ( "INSERT INTO `app_browser_window_tab_item_page` (
`app_browser_window_tab_item_id`, `app_browser_window_tab_item_id`,
@ -35,10 +36,11 @@ pub fn insert(
`title` `title`
) VALUES (?, ?, ?)", ) VALUES (?, ?, ?)",
(app_browser_window_tab_item_id, is_needs_attention, title), (app_browser_window_tab_item_id, is_needs_attention, title),
) )?;
Ok(tx.last_insert_rowid())
} }
pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<Vec<Table>, Error> { pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<Vec<Table>> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,
`app_browser_window_tab_item_id`, `app_browser_window_tab_item_id`,
@ -67,13 +69,9 @@ pub fn select(tx: &Transaction, app_browser_window_tab_item_id: i64) -> Result<V
Ok(records) Ok(records)
} }
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> { pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
tx.execute( Ok(tx.execute(
"DELETE FROM `app_browser_window_tab_item_page` WHERE `id` = ?", "DELETE FROM `app_browser_window_tab_item_page` WHERE `id` = ?",
[id], [id],
) )?)
}
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
} }

View file

@ -6,6 +6,7 @@ mod reload;
mod request; mod request;
use super::{ItemAction, Profile, TabAction, WindowAction}; use super::{ItemAction, Profile, TabAction, WindowAction};
use anyhow::Result;
use bookmark::Bookmark; use bookmark::Bookmark;
use gtk::{ use gtk::{
glib::{GString, Uri}, glib::{GString, Uri},
@ -72,22 +73,12 @@ impl Navigation {
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_page_id: &i64, app_browser_window_tab_item_page_id: &i64,
) -> Result<(), String> { ) -> Result<()> {
match database::select(transaction, app_browser_window_tab_item_page_id) { for record in database::select(transaction, app_browser_window_tab_item_page_id)? {
Ok(records) => { database::delete(transaction, &record.id)?;
for record in records { // Delegate clean action to the item childs
match database::delete(transaction, &record.id) { self.request.clean(transaction, &record.id)?;
Ok(_) => {
// Delegate clean action to the item childs
self.request.clean(transaction, &record.id)?;
}
Err(e) => return Err(e.to_string()),
}
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
@ -95,17 +86,11 @@ impl Navigation {
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_page_id: &i64, app_browser_window_tab_item_page_id: &i64,
) -> Result<(), String> { ) -> Result<()> {
match database::select(transaction, app_browser_window_tab_item_page_id) { for record in database::select(transaction, app_browser_window_tab_item_page_id)? {
Ok(records) => { // Delegate restore action to the item childs
for record in records { self.request.restore(transaction, &record.id)?;
// Delegate restore action to the item childs
self.request.restore(transaction, &record.id)?;
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
@ -113,17 +98,10 @@ impl Navigation {
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_page_id: &i64, app_browser_window_tab_item_page_id: &i64,
) -> Result<(), String> { ) -> Result<()> {
match database::insert(transaction, app_browser_window_tab_item_page_id) { let id = database::insert(transaction, app_browser_window_tab_item_page_id)?;
Ok(_) => { // Delegate save action to childs
let id = database::last_insert_id(transaction); self.request.save(transaction, &id)?;
// Delegate save action to childs
self.request.save(transaction, &id)?;
}
Err(e) => return Err(e.to_string()),
}
Ok(()) Ok(())
} }
@ -173,11 +151,9 @@ impl Navigation {
} }
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
request::migrate(tx)?; request::migrate(tx)?;

View file

@ -48,7 +48,7 @@ impl Bookmark for Button {
} }
fn update(&self, profile: &Profile, request: &Entry) { fn update(&self, profile: &Profile, request: &Entry) {
let has_bookmark = profile.bookmark.get(&request.text()).is_ok(); let has_bookmark = profile.bookmark.get(&request.text()).is_some();
self.set_icon_name(icon_name(has_bookmark)); self.set_icon_name(icon_name(has_bookmark));
self.set_tooltip_text(Some(tooltip_text(has_bookmark))); self.set_tooltip_text(Some(tooltip_text(has_bookmark)));
} }

View file

@ -1,12 +1,13 @@
use sqlite::{Error, Transaction}; use anyhow::Result;
use sqlite::Transaction;
pub struct Table { pub struct Table {
pub id: i64, pub id: i64,
// pub app_browser_window_tab_item_page_id: i64, not in use // pub app_browser_window_tab_item_page_id: i64, not in use
} }
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page_navigation` "CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page_navigation`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -15,22 +16,20 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
FOREIGN KEY (`app_browser_window_tab_item_page_id`) REFERENCES `app_browser_window_tab_item_page`(`id`) FOREIGN KEY (`app_browser_window_tab_item_page_id`) REFERENCES `app_browser_window_tab_item_page`(`id`)
)", )",
[], [],
) )?)
} }
pub fn insert(tx: &Transaction, app_browser_window_tab_item_page_id: &i64) -> Result<usize, Error> { pub fn insert(tx: &Transaction, app_browser_window_tab_item_page_id: &i64) -> Result<i64> {
tx.execute( tx.execute(
"INSERT INTO `app_browser_window_tab_item_page_navigation` ( "INSERT INTO `app_browser_window_tab_item_page_navigation` (
`app_browser_window_tab_item_page_id` `app_browser_window_tab_item_page_id`
) VALUES (?)", ) VALUES (?)",
[app_browser_window_tab_item_page_id], [app_browser_window_tab_item_page_id],
) )?;
Ok(tx.last_insert_rowid())
} }
pub fn select( pub fn select(tx: &Transaction, app_browser_window_tab_item_page_id: &i64) -> Result<Vec<Table>> {
tx: &Transaction,
app_browser_window_tab_item_page_id: &i64,
) -> Result<Vec<Table>, Error> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,
`app_browser_window_tab_item_page_id` `app_browser_window_tab_item_page_id`
@ -55,13 +54,9 @@ pub fn select(
Ok(records) Ok(records)
} }
pub fn delete(tx: &Transaction, id: &i64) -> Result<usize, Error> { pub fn delete(tx: &Transaction, id: &i64) -> Result<usize> {
tx.execute( Ok(tx.execute(
"DELETE FROM `app_browser_window_tab_item_page_navigation` WHERE `id` = ?", "DELETE FROM `app_browser_window_tab_item_page_navigation` WHERE `id` = ?",
[id], [id],
) )?)
}
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
} }

View file

@ -3,15 +3,15 @@ mod identity;
mod primary_icon; mod primary_icon;
mod search; mod search;
use adw::{prelude::AdwDialogExt, AlertDialog};
use primary_icon::PrimaryIcon;
use super::{ItemAction, Profile}; use super::{ItemAction, Profile};
use adw::{prelude::AdwDialogExt, AlertDialog};
use anyhow::Result;
use gtk::{ use gtk::{
glib::{gformat, GString, Uri, UriFlags}, glib::{gformat, GString, Uri, UriFlags},
prelude::{EditableExt, EntryExt, WidgetExt}, prelude::{EditableExt, EntryExt, WidgetExt},
Entry, EntryIconPosition, StateFlags, Entry, EntryIconPosition, StateFlags,
}; };
use primary_icon::PrimaryIcon;
use sqlite::Transaction; use sqlite::Transaction;
use std::{cell::Cell, rc::Rc}; use std::{cell::Cell, rc::Rc};
@ -29,19 +29,19 @@ pub trait Request {
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64, app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String>; ) -> Result<()>;
fn restore( fn restore(
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64, app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String>; ) -> Result<()>;
fn save( fn save(
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64, app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String>; ) -> Result<()>;
fn update_primary_icon(&self, profile: &Profile); fn update_primary_icon(&self, profile: &Profile);
fn update_secondary_icon(&self); fn update_secondary_icon(&self);
@ -147,22 +147,13 @@ impl Request for Entry {
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64, app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String> { ) -> Result<()> {
match database::select(transaction, app_browser_window_tab_item_page_navigation_id) { for record in database::select(transaction, app_browser_window_tab_item_page_navigation_id)?
Ok(records) => { {
for record in records { database::delete(transaction, &record.id)?;
match database::delete(transaction, &record.id) { // Delegate clean action to the item childs
Ok(_) => { // nothing yet..
// Delegate clean action to the item childs
// nothing yet..
}
Err(e) => return Err(e.to_string()),
}
}
}
Err(e) => return Err(e.to_string()),
} }
Ok(()) Ok(())
} }
@ -170,21 +161,15 @@ impl Request for Entry {
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64, app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String> { ) -> Result<()> {
match database::select(transaction, app_browser_window_tab_item_page_navigation_id) { for record in database::select(transaction, app_browser_window_tab_item_page_navigation_id)?
Ok(records) => { {
for record in records { if let Some(text) = record.text {
if let Some(text) = record.text { self.set_text(&text);
self.set_text(&text);
}
// Delegate restore action to the item childs
// nothing yet..
}
} }
Err(e) => return Err(e.to_string()), // Delegate restore action to the item childs
// nothing yet..
} }
Ok(()) Ok(())
} }
@ -192,27 +177,19 @@ impl Request for Entry {
&self, &self,
transaction: &Transaction, transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64, app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String> { ) -> Result<()> {
// Keep value in memory until operation complete // Keep value in memory until operation complete
let text = self.text(); let text = self.text();
let _id = database::insert(
match database::insert(
transaction, transaction,
app_browser_window_tab_item_page_navigation_id, app_browser_window_tab_item_page_navigation_id,
match text.is_empty() { match text.is_empty() {
true => None, true => None,
false => Some(text.as_str()), false => Some(text.as_str()),
}, },
) { )?;
Ok(_) => { // Delegate save action to childs
// let id = database::last_insert_id(transaction); // nothing yet..
// Delegate save action to childs
// nothing yet..
}
Err(e) => return Err(e.to_string()),
}
Ok(()) Ok(())
} }
@ -369,11 +346,9 @@ impl Request for Entry {
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
// nothing yet.. // nothing yet..

View file

@ -1,4 +1,5 @@
use sqlite::{Error, Transaction}; use anyhow::Result;
use sqlite::Transaction;
pub struct Table { pub struct Table {
pub id: i64, pub id: i64,
@ -6,8 +7,8 @@ pub struct Table {
pub text: Option<String>, // can be stored as NULL pub text: Option<String>, // can be stored as NULL
} }
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok( tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page_navigation_request` "CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page_navigation_request`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -17,27 +18,28 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
FOREIGN KEY (`app_browser_window_tab_item_page_navigation_id`) REFERENCES `app_browser_window_tab_item_page_navigation`(`id`) FOREIGN KEY (`app_browser_window_tab_item_page_navigation_id`) REFERENCES `app_browser_window_tab_item_page_navigation`(`id`)
)", )",
[], [],
) )?)
} }
pub fn insert( pub fn insert(
tx: &Transaction, tx: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64, app_browser_window_tab_item_page_navigation_id: &i64,
text: Option<&str>, text: Option<&str>,
) -> Result<usize, Error> { ) -> Result<i64> {
tx.execute( tx.execute(
"INSERT INTO `app_browser_window_tab_item_page_navigation_request` ( "INSERT INTO `app_browser_window_tab_item_page_navigation_request` (
`app_browser_window_tab_item_page_navigation_id`, `app_browser_window_tab_item_page_navigation_id`,
`text` `text`
) VALUES (?, ?)", ) VALUES (?, ?)",
(app_browser_window_tab_item_page_navigation_id, text), (app_browser_window_tab_item_page_navigation_id, text),
) )?;
Ok(tx.last_insert_rowid())
} }
pub fn select( pub fn select(
tx: &Transaction, tx: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64, app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<Vec<Table>, Error> { ) -> Result<Vec<Table>> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,
`app_browser_window_tab_item_page_navigation_id`, `app_browser_window_tab_item_page_navigation_id`,
@ -64,14 +66,9 @@ pub fn select(
Ok(records) Ok(records)
} }
pub fn delete(tx: &Transaction, id: &i64) -> Result<usize, Error> { pub fn delete(tx: &Transaction, id: &i64) -> Result<usize> {
tx.execute( Ok(tx.execute(
"DELETE FROM `app_browser_window_tab_item_page_navigation_request` WHERE `id` = ?", "DELETE FROM `app_browser_window_tab_item_page_navigation_request` WHERE `id` = ?",
[id], [id],
) )?)
} }
/* not in use
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
} */

View file

@ -9,7 +9,7 @@ use std::rc::Rc;
fn main() -> ExitCode { fn main() -> ExitCode {
match gtk::init() { match gtk::init() {
Ok(_) => App::build(&Rc::new(Profile::new())).run(), Ok(_) => App::build(&Rc::new(Profile::new().unwrap())).run(),
Err(_) => ExitCode::FAILURE, Err(_) => ExitCode::FAILURE,
} }
} }

View file

@ -10,6 +10,7 @@ use history::History;
use identity::Identity; use identity::Identity;
use search::Search; use search::Search;
use anyhow::Result;
use gtk::glib::{user_config_dir, DateTime}; use gtk::glib::{user_config_dir, DateTime};
use sqlite::{Connection, Transaction}; use sqlite::{Connection, Transaction};
use std::{fs::create_dir_all, path::PathBuf, rc::Rc, sync::RwLock}; use std::{fs::create_dir_all, path::PathBuf, rc::Rc, sync::RwLock};
@ -29,16 +30,10 @@ pub struct Profile {
pub config_path: PathBuf, pub config_path: PathBuf,
} }
impl Default for Profile {
fn default() -> Self {
Self::new()
}
}
impl Profile { impl Profile {
// Constructors // Constructors
pub fn new() -> Self { pub fn new() -> Result<Self> {
// Init profile path // Init profile path
let mut config_path = user_config_dir(); let mut config_path = user_config_dir();
@ -51,44 +46,26 @@ impl Profile {
env!("CARGO_PKG_VERSION_MINOR") env!("CARGO_PKG_VERSION_MINOR")
)); // @TODO remove after auto-migrate feature implementation )); // @TODO remove after auto-migrate feature implementation
if let Err(e) = create_dir_all(&config_path) { create_dir_all(&config_path)?;
panic!("{e}")
}
// Init database path // Init database path
let mut database_path = config_path.clone(); let mut database_path = config_path.clone();
database_path.push(DB_NAME); database_path.push(DB_NAME);
// Init database connection // Init database connection
let connection = match Connection::open(database_path.as_path()) { let connection = Rc::new(RwLock::new(Connection::open(database_path.as_path())?));
Ok(connection) => Rc::new(RwLock::new(connection)),
Err(e) => panic!("{e}"),
};
// Init profile components // Init profile components
{ {
// Init writable connection // Init writable connection
let mut connection = match connection.write() { let mut connection = connection.write().unwrap(); // @TODO handle
Ok(connection) => connection,
Err(e) => todo!("{e}"),
};
// Init new transaction // Init new transaction
let transaction = match connection.transaction() { let transaction = connection.transaction()?;
Ok(transaction) => transaction,
Err(e) => todo!("{e}"),
};
// Begin migration // Begin migration
match migrate(&transaction) { migrate(&transaction)?;
Ok(_) => { transaction.commit()?;
// Confirm changes
if let Err(e) = transaction.commit() {
todo!("{e}")
}
}
Err(e) => todo!("{e}"),
}
} // unlock database } // unlock database
// Init model // Init model
@ -104,7 +81,7 @@ impl Profile {
}); });
// Init components // Init components
let bookmark = Rc::new(Bookmark::build(&connection, &profile_id)); let bookmark = Rc::new(Bookmark::build(&connection, &profile_id)?);
let history = Rc::new(History::build(&connection, &profile_id)); let history = Rc::new(History::build(&connection, &profile_id));
let search = Rc::new(Search::build(&connection, &profile_id).unwrap()); // @TODO handle let search = Rc::new(Search::build(&connection, &profile_id).unwrap()); // @TODO handle
let identity = Rc::new(match Identity::build(&connection, &profile_id) { let identity = Rc::new(match Identity::build(&connection, &profile_id) {
@ -113,22 +90,20 @@ impl Profile {
}); });
// Result // Result
Self { Ok(Self {
bookmark, bookmark,
database, database,
history, history,
identity, identity,
search, search,
config_path, config_path,
} })
} }
} }
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to children components // Delegate migration to children components
bookmark::migrate(tx)?; bookmark::migrate(tx)?;

View file

@ -1,12 +1,10 @@
mod database; mod database;
mod error;
mod memory; mod memory;
use anyhow::Result;
use database::Database; use database::Database;
use error::Error;
use memory::Memory;
use gtk::glib::DateTime; use gtk::glib::DateTime;
use memory::Memory;
use sqlite::{Connection, Transaction}; use sqlite::{Connection, Transaction};
use std::{rc::Rc, sync::RwLock}; use std::{rc::Rc, sync::RwLock};
@ -19,48 +17,38 @@ impl Bookmark {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn build(connection: &Rc<RwLock<Connection>>, profile_id: &Rc<i64>) -> Self { pub fn build(connection: &Rc<RwLock<Connection>>, profile_id: &Rc<i64>) -> Result<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());
// Build initial index // Build initial index
match database.records(None) { for record in database.records(None)? {
Ok(records) => { memory.add(record.request, record.id)?;
for record in records {
if let Err(e) = memory.add(record.request, record.id) {
todo!("{}", e.to_string())
}
}
}
Err(e) => todo!("{}", e.to_string()),
} }
// Return new `Self` // Return new `Self`
Self { database, memory } Ok(Self { database, memory })
} }
// Actions // Actions
/// Get record `id` by `request` from memory index /// Get record `id` by `request` from memory index
pub fn get(&self, request: &str) -> Result<i64, Error> { pub fn get(&self, request: &str) -> Option<i64> {
match self.memory.get(request) { self.memory.get(request)
Ok(id) => Ok(id),
Err(_) => Err(Error::MemoryNotFound),
}
} }
/// Toggle record in `database` and `memory` index /// Toggle record in `database` and `memory` index
/// * return `true` on bookmark created, `false` on deleted /// * return `true` on bookmark created, `false` on deleted
pub fn toggle(&self, request: &str) -> Result<bool, Error> { pub fn toggle(&self, request: &str) -> Result<bool> {
// Delete record if exists // Delete record if exists
if let Ok(id) = self.get(request) { if let Some(id) = self.get(request) {
match self.database.delete(id) { match self.database.delete(id) {
Ok(_) => match self.memory.delete(request) { Ok(_) => match self.memory.delete(request) {
Ok(_) => Ok(false), Ok(_) => Ok(false),
Err(_) => Err(Error::MemoryDelete), Err(_) => panic!(), // unexpected
}, },
Err(_) => Err(Error::DatabaseDelete), Err(_) => panic!(), // unexpected
} }
// Otherwise, create new record // Otherwise, create new record
} else { } else {
@ -70,9 +58,9 @@ impl Bookmark {
{ {
Ok(id) => match self.memory.add(request.into(), id) { Ok(id) => match self.memory.add(request.into(), id) {
Ok(_) => Ok(true), Ok(_) => Ok(true),
Err(_) => Err(Error::MemoryAdd), Err(_) => panic!(), // unexpected
}, },
Err(_) => Err(Error::DatabaseAdd), Err(_) => panic!(), // unexpected
} }
} // @TODO return affected rows on success? } // @TODO return affected rows on success?
} }
@ -80,11 +68,9 @@ impl Bookmark {
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
// nothing yet.. // nothing yet..

View file

@ -1,5 +1,6 @@
use anyhow::Result;
use gtk::glib::DateTime; use gtk::glib::DateTime;
use sqlite::{Connection, Error, Transaction}; use sqlite::{Connection, Transaction};
use std::{rc::Rc, sync::RwLock}; use std::{rc::Rc, sync::RwLock};
pub struct Table { pub struct Table {
@ -28,7 +29,7 @@ impl Database {
// Getters // Getters
/// Get bookmark records from database with optional filter by `request` /// Get bookmark records from database with optional filter by `request`
pub fn records(&self, request: Option<&str>) -> Result<Vec<Table>, Error> { pub fn records(&self, request: Option<&str>) -> Result<Vec<Table>> {
let readable = self.connection.read().unwrap(); // @TODO let readable = self.connection.read().unwrap(); // @TODO
let tx = readable.unchecked_transaction()?; let tx = readable.unchecked_transaction()?;
select(&tx, *self.profile_id, request) select(&tx, *self.profile_id, request)
@ -38,45 +39,28 @@ impl Database {
/// Create new bookmark record in database /// Create new bookmark record in database
/// * return last insert ID on success /// * return last insert ID on success
pub fn add(&self, time: DateTime, request: String) -> Result<i64, Error> { pub fn add(&self, time: DateTime, request: String) -> Result<i64> {
// Begin new transaction
let mut writable = self.connection.write().unwrap(); // @TODO let mut writable = self.connection.write().unwrap(); // @TODO
let tx = writable.transaction()?; let tx = writable.transaction()?;
let id = insert(&tx, *self.profile_id, time, request)?;
// Create new record tx.commit()?;
insert(&tx, *self.profile_id, time, request)?; Ok(id)
// Hold insert ID for result
let id = last_insert_id(&tx);
// Done
match tx.commit() {
Ok(_) => Ok(id),
Err(e) => Err(e),
}
} }
/// Delete bookmark record from database /// Delete bookmark record from database
pub fn delete(&self, id: i64) -> Result<(), Error> { pub fn delete(&self, id: i64) -> Result<usize> {
// Begin new transaction
let mut writable = self.connection.write().unwrap(); // @TODO let mut writable = self.connection.write().unwrap(); // @TODO
let tx = writable.transaction()?; let tx = writable.transaction()?;
let usize = delete(&tx, id)?;
// Delete record by ID tx.commit()?;
match delete(&tx, id) { Ok(usize)
Ok(_) => match tx.commit() {
Ok(_) => Ok(()),
Err(e) => Err(e),
},
Err(e) => Err(e),
}
} }
} }
// Low-level DB API // Low-level DB API
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `profile_bookmark` "CREATE TABLE IF NOT EXISTS `profile_bookmark`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -87,15 +71,10 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
FOREIGN KEY (`profile_id`) REFERENCES `profile`(`id`) FOREIGN KEY (`profile_id`) REFERENCES `profile`(`id`)
)", )",
[], [],
) )?)
} }
pub fn insert( pub fn insert(tx: &Transaction, profile_id: i64, time: DateTime, request: String) -> Result<i64> {
tx: &Transaction,
profile_id: i64,
time: DateTime,
request: String,
) -> Result<usize, Error> {
tx.execute( tx.execute(
"INSERT INTO `profile_bookmark` ( "INSERT INTO `profile_bookmark` (
`profile_id`, `profile_id`,
@ -103,14 +82,11 @@ pub fn insert(
`request` `request`
) VALUES (?, ?, ?)", ) VALUES (?, ?, ?)",
(profile_id, time.to_unix(), request), (profile_id, time.to_unix(), request),
) )?;
Ok(tx.last_insert_rowid())
} }
pub fn select( pub fn select(tx: &Transaction, profile_id: i64, request: Option<&str>) -> Result<Vec<Table>> {
tx: &Transaction,
profile_id: i64,
request: Option<&str>,
) -> Result<Vec<Table>, Error> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, `profile_id`, `time`, `request` "SELECT `id`, `profile_id`, `time`, `request`
FROM `profile_bookmark` FROM `profile_bookmark`
@ -136,10 +112,6 @@ pub fn select(
Ok(records) Ok(records)
} }
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> { pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
tx.execute("DELETE FROM `profile_bookmark` WHERE `id` = ?", [id]) Ok(tx.execute("DELETE FROM `profile_bookmark` WHERE `id` = ?", [id])?)
}
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
} }

View file

@ -1,8 +0,0 @@
#[derive(Debug)]
pub enum Error {
DatabaseAdd,
DatabaseDelete,
MemoryAdd,
MemoryDelete,
MemoryNotFound,
}

View file

@ -1,6 +1,4 @@
mod error; use anyhow::Result;
use error::Error;
use itertools::Itertools; use itertools::Itertools;
use std::{cell::RefCell, collections::HashMap}; use std::{cell::RefCell, collections::HashMap};
@ -29,37 +27,34 @@ impl Memory {
/// Add new record with `request` as key and `id` as value /// Add new record with `request` as key and `id` as value
/// * validate record with same key does not exist yet /// * validate record with same key does not exist yet
pub fn add(&self, request: String, id: i64) -> Result<(), Error> { pub fn add(&self, request: String, id: i64) -> Result<()> {
// Borrow shared index access // Borrow shared index access
let mut index = self.index.borrow_mut(); let mut index = self.index.borrow_mut();
// Prevent existing key overwrite // Prevent existing key overwrite
if index.contains_key(&request) { if index.contains_key(&request) {
return Err(Error::Overwrite(request)); panic!() // unexpected
} }
// Slot should be free, let check it twice // Slot should be free, let check it twice
match index.insert(request, id) { match index.insert(request, id) {
Some(_) => Err(Error::Unexpected), Some(_) => panic!(), // unexpected
None => Ok(()), None => Ok(()),
} }
} }
/// Delete record from index by `request` /// Delete record from index by `request`
/// * validate record key is exist /// * validate record key is exist
pub fn delete(&self, request: &str) -> Result<(), Error> { pub fn delete(&self, request: &str) -> Result<()> {
match self.index.borrow_mut().remove(request) { match self.index.borrow_mut().remove(request) {
Some(_) => Ok(()), Some(_) => Ok(()),
None => Err(Error::Unexpected), // @TODO None => panic!(), // unexpected
} }
} }
/// Get `id` by `request` from memory index /// Get `id` by `request` from memory index
pub fn get(&self, request: &str) -> Result<i64, Error> { pub fn get(&self, request: &str) -> Option<i64> {
match self.index.borrow().get(request) { self.index.borrow().get(request).copied()
Some(&value) => Ok(value),
None => Err(Error::Unexpected), // @TODO
}
} }
/// Get recent requests vector sorted by `ID` DESC /// Get recent requests vector sorted by `ID` DESC

View file

@ -1,18 +0,0 @@
use std::fmt::{Display, Formatter, Result};
#[derive(Debug)]
pub enum Error {
Overwrite(String),
Unexpected,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
Self::Overwrite(key) => {
write!(f, "Overwrite attempt for existing record `{key}`")
}
Self::Unexpected => write!(f, "Unexpected error"),
}
}
}

View file

@ -1,5 +1,6 @@
use anyhow::Result;
use gtk::glib::DateTime; use gtk::glib::DateTime;
use sqlite::{Connection, Error, Transaction}; use sqlite::{Connection, Transaction};
use std::{rc::Rc, sync::RwLock}; use std::{rc::Rc, sync::RwLock};
pub struct Table { pub struct Table {
@ -26,14 +27,14 @@ impl Database {
// Getters // Getters
/// Get all records /// Get all records
pub fn records(&self) -> Result<Vec<Table>, Error> { pub fn records(&self) -> Result<Vec<Table>> {
let readable = self.connection.read().unwrap(); let readable = self.connection.read().unwrap();
let tx = readable.unchecked_transaction()?; let tx = readable.unchecked_transaction()?;
select(&tx) select(&tx)
} }
/// Get active profile record if exist /// Get active profile record if exist
pub fn active(&self) -> Result<Option<Table>, Error> { pub fn active(&self) -> Result<Option<Table>> {
let records = self.records()?; let records = self.records()?;
Ok(records.into_iter().find(|record| record.is_active)) Ok(records.into_iter().find(|record| record.is_active))
} }
@ -41,36 +42,24 @@ impl Database {
// Setters // Setters
/// Create new record in `Self` database connected /// Create new record in `Self` database connected
pub fn add(&self, is_active: bool, time: DateTime, name: Option<String>) -> Result<i64, Error> { pub fn add(&self, is_active: bool, time: DateTime, name: Option<String>) -> Result<i64> {
// 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()?;
// New record has active status
if is_active { if is_active {
// Deactivate other records as only one profile should be active
for record in select(&tx)? { for record in select(&tx)? {
update(&tx, record.id, false, record.time, record.name)?; update(&tx, record.id, false, record.time, record.name)?;
} }
} }
let id = insert(&tx, is_active, time, name)?;
// Create new record
insert(&tx, is_active, time, name)?;
// Hold insert ID for result
let id = last_insert_id(&tx);
// Done
tx.commit()?; tx.commit()?;
Ok(id) Ok(id)
} }
} }
// Low-level DB API // Low-level DB API
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `profile` "CREATE TABLE IF NOT EXISTS `profile`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -79,7 +68,7 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
`name` VARCHAR(255) `name` VARCHAR(255)
)", )",
[], [],
) )?)
} }
pub fn insert( pub fn insert(
@ -87,7 +76,7 @@ pub fn insert(
is_active: bool, is_active: bool,
time: DateTime, time: DateTime,
name: Option<String>, name: Option<String>,
) -> Result<usize, Error> { ) -> Result<i64> {
tx.execute( tx.execute(
"INSERT INTO `profile` ( "INSERT INTO `profile` (
`is_active`, `is_active`,
@ -95,7 +84,8 @@ pub fn insert(
`name` `name`
) VALUES (?, ?, ?)", ) VALUES (?, ?, ?)",
(is_active, time.to_unix(), name), (is_active, time.to_unix(), name),
) )?;
Ok(tx.last_insert_rowid())
} }
pub fn update( pub fn update(
@ -104,14 +94,14 @@ pub fn update(
is_active: bool, is_active: bool,
time: DateTime, time: DateTime,
name: Option<String>, name: Option<String>,
) -> Result<usize, Error> { ) -> Result<usize> {
tx.execute( Ok(tx.execute(
"UPDATE `profile` SET `is_active` = ?, `time` = ?, `name` = ? WHERE `id` = ?", "UPDATE `profile` SET `is_active` = ?, `time` = ?, `name` = ? WHERE `id` = ?",
(is_active, time.to_unix(), name, id), (is_active, time.to_unix(), name, id),
) )?)
} }
pub fn select(tx: &Transaction) -> Result<Vec<Table>, Error> { pub fn select(tx: &Transaction) -> Result<Vec<Table>> {
let mut stmt = tx.prepare("SELECT `id`, `is_active`, `time`, `name` FROM `profile`")?; let mut stmt = tx.prepare("SELECT `id`, `is_active`, `time`, `name` FROM `profile`")?;
let result = stmt.query_map([], |row| { let result = stmt.query_map([], |row| {
Ok(Table { Ok(Table {
@ -131,7 +121,3 @@ pub fn select(tx: &Transaction) -> Result<Vec<Table>, Error> {
Ok(records) Ok(records)
} }
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
}

View file

@ -1,80 +0,0 @@
use gtk::glib::DateTime;
use sqlite::{Error, Transaction};
pub struct Table {
pub id: i64,
pub profile_id: i64,
pub time: DateTime,
pub request: String,
}
pub struct Database {
// nothing yet..
}
// Low-level DB API
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
"CREATE TABLE IF NOT EXISTS `profile_history`
(
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
`profile_id` INTEGER NOT NULL,
`time` INTEGER NOT NULL,
`request` TEXT NOT NULL,
FOREIGN KEY (`profile_id`) REFERENCES `profile`(`id`)
)",
[],
)
}
pub fn insert(
tx: &Transaction,
profile_id: i64,
time: DateTime,
request: String,
) -> Result<usize, Error> {
tx.execute(
"INSERT INTO `history` (
`profile_id`,
`time`,
`request`
) VALUES (?, ?, ?)",
(profile_id, time.to_unix(), request),
)
}
pub fn select(
tx: &Transaction,
profile_id: i64,
request: Option<String>,
) -> Result<Vec<Table>, Error> {
let mut stmt = tx.prepare(
"SELECT `id`, `profile_id`, `time`, `request`
FROM `profile_history`
WHERE `profile_id` = ? AND `request` LIKE ?",
)?;
let result = stmt.query_map((profile_id, request.unwrap_or("%".to_string())), |row| {
Ok(Table {
id: row.get(0)?,
profile_id: row.get(1)?,
time: DateTime::from_unix_local(row.get(2)?).unwrap(),
request: row.get(3)?,
})
})?;
let mut records = Vec::new();
for record in result {
let table = record?;
records.push(table);
}
Ok(records)
}
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
tx.execute("DELETE FROM `profile_history` WHERE `id` = ?", [id])
}

View file

@ -1,18 +1,15 @@
mod auth; mod auth;
mod certificate; mod certificate;
mod database; mod database;
mod error;
mod item; mod item;
mod memory; mod memory;
use anyhow::{bail, Result};
use auth::Auth; use auth::Auth;
use database::Database; use database::Database;
pub use error::Error;
use item::Item;
use memory::Memory;
use gtk::glib::DateTime; use gtk::glib::DateTime;
use item::Item;
use memory::Memory;
use sqlite::{Connection, Transaction}; use sqlite::{Connection, Transaction};
use std::{rc::Rc, sync::RwLock}; use std::{rc::Rc, sync::RwLock};
@ -32,11 +29,11 @@ impl Identity {
pub fn build( 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> {
// Init components // Init components
let auth = match Auth::build(connection) { let auth = match Auth::build(connection) {
Ok(auth) => Rc::new(auth), Ok(auth) => Rc::new(auth),
Err(e) => return Err(Error::Auth(e)), Err(e) => bail!("Could not create auth: {e}"),
}; };
let database = Rc::new(Database::build(connection, profile_identity_id)); let database = Rc::new(Database::build(connection, profile_identity_id));
let memory = Rc::new(Memory::new()); let memory = Rc::new(Memory::new());
@ -58,33 +55,23 @@ impl Identity {
/// Add new record to database, update memory index /// Add new record to database, update memory index
/// * return new `profile_identity_id` on success /// * return new `profile_identity_id` on success
pub fn add(&self, pem: &str) -> Result<i64, Error> { pub fn add(&self, pem: &str) -> Result<i64> {
match self.database.add(pem) { let profile_identity_id = self.database.add(pem)?;
Ok(profile_identity_id) => { self.index()?;
self.index()?; Ok(profile_identity_id)
Ok(profile_identity_id)
}
Err(e) => Err(Error::Database(e)),
}
} }
/// Delete record from database including children dependencies, update memory index /// Delete record from database including children dependencies, update memory index
pub fn delete(&self, profile_identity_id: i64) -> Result<(), Error> { pub fn delete(&self, profile_identity_id: i64) -> Result<()> {
match self.auth.remove_ref(profile_identity_id) { self.auth.remove_ref(profile_identity_id)?;
Ok(_) => match self.database.delete(profile_identity_id) { self.database.delete(profile_identity_id)?;
Ok(_) => { self.index()?;
self.index()?; Ok(())
Ok(())
}
Err(e) => Err(Error::Database(e)),
},
Err(e) => Err(Error::Auth(e)),
}
} }
/// Generate new certificate and insert record to DB, update memory index /// Generate new certificate and insert record to DB, update memory index
/// * return new `profile_identity_id` on success /// * return new `profile_identity_id` on success
pub fn make(&self, time: Option<(DateTime, DateTime)>, name: &str) -> Result<i64, Error> { pub fn make(&self, time: Option<(DateTime, DateTime)>, name: &str) -> Result<i64> {
// Generate new certificate // Generate new certificate
match certificate::generate( match certificate::generate(
match time { match time {
@ -97,29 +84,17 @@ impl Identity {
name, name,
) { ) {
Ok(pem) => self.add(&pem), Ok(pem) => self.add(&pem),
Err(e) => Err(Error::Certificate(e)), Err(e) => bail!("Could not create certificate: {e}"),
} }
} }
/// Create new `Memory` index from `Database` for `Self` /// Create new `Memory` index from `Database` for `Self`
pub fn index(&self) -> Result<(), Error> { pub fn index(&self) -> Result<()> {
// Clear previous records // Clear previous records
if let Err(e) = self.memory.clear() { self.memory.clear()?;
return Err(Error::Memory(e)); for record in self.database.records()? {
self.memory.add(record.id, record.pem)?;
} }
// Build new index
match self.database.records() {
Ok(records) => {
for record in records {
if let Err(e) = self.memory.add(record.id, record.pem) {
return Err(Error::Memory(e));
}
}
}
Err(e) => return Err(Error::Database(e)),
};
Ok(()) Ok(())
} }
@ -144,11 +119,9 @@ impl Identity {
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
auth::migrate(tx)?; auth::migrate(tx)?;

View file

@ -1,13 +1,11 @@
//! Controller for children `database` and `memory` components //! Controller for children `database` and `memory` components
mod database; mod database;
mod error;
mod memory; mod memory;
use anyhow::Result;
use database::Database; use database::Database;
pub use error::Error;
use memory::Memory; use memory::Memory;
use sqlite::{Connection, Transaction}; use sqlite::{Connection, Transaction};
use std::{rc::Rc, sync::RwLock}; use std::{rc::Rc, sync::RwLock};
@ -21,7 +19,7 @@ impl Auth {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn build(connection: &Rc<RwLock<Connection>>) -> Result<Self, Error> { pub fn build(connection: &Rc<RwLock<Connection>>) -> Result<Self> {
// Init `Self` // Init `Self`
let this = Self { let this = Self {
database: Rc::new(Database::build(connection)), database: Rc::new(Database::build(connection)),
@ -41,18 +39,14 @@ impl Auth {
/// * deactivate active auth by remove previous records from `Self` database /// * deactivate active auth by remove previous records from `Self` database
/// * reindex `Self` memory index on success /// * reindex `Self` memory index on success
/// * return last insert `profile_identity_auth_id` on success /// * return last insert `profile_identity_auth_id` on success
pub fn apply(&self, profile_identity_id: i64, request: &str) -> Result<i64, Error> { pub fn apply(&self, profile_identity_id: i64, request: &str) -> Result<i64> {
// Cleanup records match `scope` (unauthorize) // Cleanup records match `scope` (unauthorize)
self.remove(request)?; self.remove(request)?;
// Create new record (auth) // Create new record (auth)
let profile_identity_auth_id = match self let profile_identity_auth_id = self
.database .database
.add(profile_identity_id, &filter_scope(request)) .add(profile_identity_id, &filter_scope(request))?;
{
Ok(id) => id,
Err(e) => return Err(Error::Database(e)),
};
// Reindex // Reindex
self.index()?; self.index()?;
@ -62,56 +56,31 @@ impl Auth {
} }
/// Remove all records match request (unauthorize) /// Remove all records match request (unauthorize)
pub fn remove(&self, request: &str) -> Result<(), Error> { pub fn remove(&self, request: &str) -> Result<()> {
match self.database.records_scope(Some(&filter_scope(request))) { for record in self.database.records_scope(Some(&filter_scope(request)))? {
Ok(records) => { self.database.delete(record.id)?;
for record in records {
if let Err(e) = self.database.delete(record.id) {
return Err(Error::Database(e));
}
}
}
Err(e) => return Err(Error::Database(e)),
} }
self.index()?; self.index()?;
Ok(()) Ok(())
} }
/// Remove all records match `profile_identity_id` foreign reference key /// Remove all records match `profile_identity_id` foreign reference key
pub fn remove_ref(&self, profile_identity_id: i64) -> Result<(), Error> { pub fn remove_ref(&self, profile_identity_id: i64) -> Result<()> {
match self.database.records_ref(profile_identity_id) { for record in self.database.records_ref(profile_identity_id)? {
Ok(records) => { self.database.delete(record.id)?;
for record in records {
if let Err(e) = self.database.delete(record.id) {
return Err(Error::Database(e));
}
}
}
Err(e) => return Err(Error::Database(e)),
} }
self.index()?; self.index()?;
Ok(()) Ok(())
} }
/// Create new `Memory` index from `Database` for `Self` /// Create new `Memory` index from `Database` for `Self`
pub fn index(&self) -> Result<(), Error> { pub fn index(&self) -> Result<()> {
// Clear previous records // Clear previous records
if let Err(e) = self.memory.clear() { self.memory.clear()?;
return Err(Error::Memory(e));
}
// Build new index // Build new index
match self.database.records_scope(None) { for record in self.database.records_scope(None)? {
Ok(records) => { self.memory.add(record.scope, record.profile_identity_id)?;
for record in records {
if let Err(e) = self.memory.add(record.scope, record.profile_identity_id) {
return Err(Error::Memory(e));
}
}
}
Err(e) => return Err(Error::Database(e)),
} }
Ok(()) Ok(())
} }
@ -154,11 +123,9 @@ impl Auth {
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
// nothing yet.. // nothing yet..

View file

@ -1,4 +1,5 @@
use sqlite::{Connection, Error, Transaction}; use anyhow::Result;
use sqlite::{Connection, Transaction};
use std::{rc::Rc, sync::RwLock}; use std::{rc::Rc, sync::RwLock};
pub struct Table { pub struct Table {
@ -25,51 +26,34 @@ impl Database {
// Actions // Actions
/// Create new record in database /// Create new record in database
pub fn add(&self, profile_identity_id: i64, scope: &str) -> Result<i64, Error> { pub fn add(&self, profile_identity_id: i64, scope: &str) -> Result<i64> {
// Begin new transaction
let mut writable = self.connection.write().unwrap(); // @TODO let mut writable = self.connection.write().unwrap(); // @TODO
let tx = writable.transaction()?; let tx = writable.transaction()?;
let id = insert(&tx, profile_identity_id, scope)?;
// Create new record tx.commit()?;
insert(&tx, profile_identity_id, scope)?; Ok(id)
// Hold insert ID for result
let id = last_insert_id(&tx);
// Done
match tx.commit() {
Ok(_) => Ok(id),
Err(e) => Err(e),
}
} }
/// Delete record with given `id` from database /// Delete record with given `id` from database
pub fn delete(&self, id: i64) -> Result<(), Error> { pub fn delete(&self, id: i64) -> Result<()> {
// Begin new transaction
let mut writable = self.connection.write().unwrap(); // @TODO let mut writable = self.connection.write().unwrap(); // @TODO
let tx = writable.transaction()?; let tx = writable.transaction()?;
// Create new record
delete(&tx, id)?; delete(&tx, id)?;
tx.commit()?;
// Done Ok(())
match tx.commit() {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
} }
// Getters // Getters
/// Get records from database match current `profile_id` optionally filtered by `scope` /// Get records from database match current `profile_id` optionally filtered by `scope`
pub fn records_scope(&self, scope: Option<&str>) -> Result<Vec<Table>, Error> { pub fn records_scope(&self, scope: Option<&str>) -> Result<Vec<Table>> {
let readable = self.connection.read().unwrap(); // @TODO let readable = self.connection.read().unwrap(); // @TODO
let tx = readable.unchecked_transaction()?; let tx = readable.unchecked_transaction()?;
select_scope(&tx, scope) select_scope(&tx, scope)
} }
/// Get records from database match current `profile_id` optionally filtered by `scope` /// Get records from database match current `profile_id` optionally filtered by `scope`
pub fn records_ref(&self, profile_identity_id: i64) -> Result<Vec<Table>, Error> { pub fn records_ref(&self, profile_identity_id: i64) -> Result<Vec<Table>> {
let readable = self.connection.read().unwrap(); // @TODO let readable = self.connection.read().unwrap(); // @TODO
let tx = readable.unchecked_transaction()?; let tx = readable.unchecked_transaction()?;
select_ref(&tx, profile_identity_id) select_ref(&tx, profile_identity_id)
@ -78,8 +62,8 @@ impl Database {
// Low-level DB API // Low-level DB API
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `profile_identity_auth` "CREATE TABLE IF NOT EXISTS `profile_identity_auth`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -90,24 +74,25 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
UNIQUE (`scope`) UNIQUE (`scope`)
)", )",
[], [],
) )?)
} }
pub fn insert(tx: &Transaction, profile_identity_id: i64, scope: &str) -> Result<usize, Error> { pub fn insert(tx: &Transaction, profile_identity_id: i64, scope: &str) -> Result<i64> {
tx.execute( tx.execute(
"INSERT INTO `profile_identity_auth` ( "INSERT INTO `profile_identity_auth` (
`profile_identity_id`, `profile_identity_id`,
`scope` `scope`
) VALUES (?, ?)", ) VALUES (?, ?)",
(profile_identity_id, scope), (profile_identity_id, scope),
) )?;
Ok(tx.last_insert_rowid())
} }
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> { pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
tx.execute("DELETE FROM `profile_identity_auth` WHERE `id` = ?", [id]) Ok(tx.execute("DELETE FROM `profile_identity_auth` WHERE `id` = ?", [id])?)
} }
pub fn select_scope(tx: &Transaction, scope: Option<&str>) -> Result<Vec<Table>, Error> { pub fn select_scope(tx: &Transaction, scope: Option<&str>) -> Result<Vec<Table>> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,
`profile_identity_id`, `profile_identity_id`,
@ -135,7 +120,7 @@ pub fn select_scope(tx: &Transaction, scope: Option<&str>) -> Result<Vec<Table>,
Ok(records) Ok(records)
} }
pub fn select_ref(tx: &Transaction, profile_identity_id: i64) -> Result<Vec<Table>, Error> { pub fn select_ref(tx: &Transaction, profile_identity_id: i64) -> Result<Vec<Table>> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, "SELECT `id`,
`profile_identity_id`, `profile_identity_id`,
@ -162,7 +147,3 @@ pub fn select_ref(tx: &Transaction, profile_identity_id: i64) -> Result<Vec<Tabl
Ok(records) Ok(records)
} }
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
}

View file

@ -1,16 +0,0 @@
use std::fmt::{Display, Formatter, Result};
#[derive(Debug)]
pub enum Error {
Database(sqlite::Error),
Memory(super::memory::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
Self::Database(e) => write!(f, "Database error: {e}"),
Self::Memory(e) => write!(f, "Memory error: {e}"),
}
}
}

View file

@ -1,9 +1,7 @@
pub mod auth; pub mod auth;
pub mod error;
use anyhow::{bail, Result};
pub use auth::Auth; pub use auth::Auth;
pub use error::Error;
use std::{cell::RefCell, collections::HashMap}; use std::{cell::RefCell, collections::HashMap};
/// Reduce disk usage by cache Auth index in memory /// Reduce disk usage by cache Auth index in memory
@ -31,30 +29,30 @@ impl Memory {
/// Add new record with `scope` as key and `profile_identity_id` as value /// Add new record with `scope` as key and `profile_identity_id` as value
/// * validate record with same key does not exist yet /// * validate record with same key does not exist yet
pub fn add(&self, scope: String, profile_identity_id: i64) -> Result<(), Error> { pub fn add(&self, scope: String, profile_identity_id: i64) -> Result<()> {
// Borrow shared index access // Borrow shared index access
let mut index = self.index.borrow_mut(); let mut index = self.index.borrow_mut();
// Prevent existing key overwrite // Prevent existing key overwrite
if index.contains_key(&scope) { if index.contains_key(&scope) {
return Err(Error::Overwrite(scope)); bail!("Overwrite attempt for existing record `{scope}`")
} }
// Slot should be free, let check it twice // Slot should be free, let check it twice
match index.insert(scope, profile_identity_id) { match index.insert(scope, profile_identity_id) {
Some(_) => Err(Error::Unexpected), Some(_) => bail!("Unexpected error"),
None => Ok(()), None => Ok(()),
} }
} }
/// Cleanup index /// Cleanup index
pub fn clear(&self) -> Result<(), Error> { pub fn clear(&self) -> Result<()> {
let mut index = self.index.borrow_mut(); let mut index = self.index.borrow_mut();
index.clear(); index.clear();
if index.is_empty() { if index.is_empty() {
Ok(()) Ok(())
} else { } else {
Err(Error::Clear) bail!("Could not cleanup memory index")
} }
} }

View file

@ -1,20 +0,0 @@
use std::fmt::{Display, Formatter, Result};
#[derive(Debug)]
pub enum Error {
Clear,
Overwrite(String),
Unexpected,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
Self::Clear => write!(f, "Could not cleanup memory index"),
Self::Overwrite(key) => {
write!(f, "Overwrite attempt for existing record `{key}`")
}
Self::Unexpected => write!(f, "Unexpected error"),
}
}
}

View file

@ -1,24 +0,0 @@
use std::fmt::{Display, Formatter, Result};
#[derive(Debug)]
pub enum Error {
Auth(super::auth::Error),
Certificate(Box<dyn std::error::Error>),
Database(sqlite::Error),
Memory(super::memory::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
Self::Auth(e) => write!(f, "Could not create auth: {e}"),
Self::Certificate(e) => {
write!(f, "Could not create certificate: {e}")
}
Self::Database(e) => {
write!(f, "Database error: {e}")
}
Self::Memory(e) => write!(f, "Memory error: {e}"),
}
}
}

View file

@ -1,6 +1,4 @@
mod error; use anyhow::{bail, Result};
use error::Error;
use gtk::gio::TlsCertificate; use gtk::gio::TlsCertificate;
/// Gemini identity holder for cached record in application-wide struct format. /// Gemini identity holder for cached record in application-wide struct format.
@ -12,10 +10,10 @@ pub struct Item {
impl Item { impl Item {
/// Convert `Self` to [TlsCertificate](https://docs.gtk.org/gio/class.TlsCertificate.html) /// Convert `Self` to [TlsCertificate](https://docs.gtk.org/gio/class.TlsCertificate.html)
pub fn to_tls_certificate(&self) -> Result<TlsCertificate, Error> { pub fn to_tls_certificate(&self) -> Result<TlsCertificate> {
match TlsCertificate::from_pem(&self.pem) { match TlsCertificate::from_pem(&self.pem) {
Ok(certificate) => Ok(certificate), Ok(certificate) => Ok(certificate),
Err(e) => Err(Error::TlsCertificate(e)), Err(e) => bail!("TLS certificate error: {e}"),
} }
} }
} }

View file

@ -1,15 +0,0 @@
use gtk::glib;
use std::fmt::{Display, Formatter, Result};
#[derive(Debug)]
pub enum Error {
TlsCertificate(glib::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
Self::TlsCertificate(e) => write!(f, "TLS certificate error: {e}"),
}
}
}

View file

@ -1,6 +1,4 @@
pub mod error; use anyhow::{bail, Result};
pub use error::Error;
use std::{cell::RefCell, collections::HashMap}; use std::{cell::RefCell, collections::HashMap};
/// Reduce disk usage by cache index in memory /// Reduce disk usage by cache index in memory
@ -28,38 +26,38 @@ impl Memory {
/// Add new record with `id` as key and `pem` as value /// Add new record with `id` as key and `pem` as value
/// * validate record with same key does not exist yet /// * validate record with same key does not exist yet
pub fn add(&self, profile_identity_id: i64, pem: String) -> Result<(), Error> { pub fn add(&self, profile_identity_id: i64, pem: String) -> Result<()> {
// Borrow shared index access // Borrow shared index access
let mut index = self.index.borrow_mut(); let mut index = self.index.borrow_mut();
// Prevent existing key overwrite // Prevent existing key overwrite
if index.contains_key(&profile_identity_id) { if index.contains_key(&profile_identity_id) {
return Err(Error::Overwrite(profile_identity_id)); bail!("Overwrite attempt for existing record `{profile_identity_id}`")
} }
// Slot should be free, let check it twice // Slot should be free, let check it twice
match index.insert(profile_identity_id, pem) { match index.insert(profile_identity_id, pem) {
Some(_) => Err(Error::Unexpected), Some(_) => bail!("Unexpected error"),
None => Ok(()), None => Ok(()),
} }
} }
/// Get `pem` clone by `id` from memory index /// Get `pem` clone by `id` from memory index
pub fn get(&self, id: i64) -> Result<String, Error> { pub fn get(&self, id: i64) -> Result<String> {
match self.index.borrow().get(&id) { match self.index.borrow().get(&id) {
Some(pem) => Ok(pem.clone()), Some(pem) => Ok(pem.clone()),
None => Err(Error::NotFound(id)), None => bail!("Record `{id}` not found in memory index"),
} }
} }
/// Cleanup index /// Cleanup index
pub fn clear(&self) -> Result<(), Error> { pub fn clear(&self) -> Result<()> {
let mut index = self.index.borrow_mut(); let mut index = self.index.borrow_mut();
index.clear(); index.clear();
if index.is_empty() { if index.is_empty() {
Ok(()) Ok(())
} else { } else {
Err(Error::Clear) bail!("Could not cleanup memory index")
} }
} }
} }

View file

@ -1,22 +0,0 @@
use std::fmt::{Display, Formatter, Result};
#[derive(Debug)]
pub enum Error {
Clear,
NotFound(i64),
Overwrite(i64),
Unexpected,
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
Self::Clear => write!(f, "Could not cleanup memory index"),
Self::NotFound(key) => {
write!(f, "Record `{key}` not found in memory index")
}
Self::Overwrite(key) => write!(f, "Overwrite attempt for existing record `{key}`"),
Self::Unexpected => write!(f, "Unexpected error"),
}
}
}

View file

@ -1,9 +1,8 @@
mod database; mod database;
mod error;
mod memory; mod memory;
use anyhow::Result;
use database::Database; use database::Database;
use error::Error;
use gtk::glib::Uri; use gtk::glib::Uri;
use memory::Memory; use memory::Memory;
use sqlite::{Connection, Transaction}; use sqlite::{Connection, Transaction};
@ -18,40 +17,30 @@ impl Search {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn build(connection: &Rc<RwLock<Connection>>, profile_id: &Rc<i64>) -> Result<Self, Error> { pub fn build(connection: &Rc<RwLock<Connection>>, profile_id: &Rc<i64>) -> Result<Self> {
match Database::init(connection, profile_id) { let database = Database::init(connection, profile_id)?;
Ok(database) => { // Init fast search index
// Init fast search index let memory = Memory::init();
let memory = Memory::init(); // Build initial index
index(&database, &memory)?;
// Build initial index // Return new `Self`
index(&database, &memory)?; Ok(Self { database, memory })
// Return new `Self`
Ok(Self { database, memory })
}
Err(e) => Err(Error::Database(e)),
}
} }
// Actions // Actions
/// Add new search provider record /// Add new search provider record
/// * requires valid [Uri](https://docs.gtk.org/glib/struct.Uri.html) /// * requires valid [Uri](https://docs.gtk.org/glib/struct.Uri.html)
pub fn add(&self, query: &Uri, is_default: bool) -> Result<(), Error> { pub fn add(&self, query: &Uri, is_default: bool) -> Result<()> {
match self.database.add(query.to_string(), is_default) { self.database.add(query.to_string(), is_default)?;
Ok(_) => index(&self.database, &self.memory), Ok(())
Err(e) => Err(Error::Database(e)),
}
} }
/// Add new search provider record /// Add new search provider record
/// * requires valid [Uri](https://docs.gtk.org/glib/struct.Uri.html) /// * requires valid [Uri](https://docs.gtk.org/glib/struct.Uri.html)
pub fn set_default(&self, profile_search_id: i64) -> Result<(), Error> { pub fn set_default(&self, profile_search_id: i64) -> Result<()> {
match self.database.set_default(profile_search_id) { self.database.set_default(profile_search_id)?;
Ok(_) => index(&self.database, &self.memory), index(&self.database, &self.memory)
Err(e) => Err(Error::Database(e)),
}
} }
/// Get records from the memory index /// Get records from the memory index
@ -60,11 +49,9 @@ impl Search {
} }
/// Delete record from `database` and `memory` index /// Delete record from `database` and `memory` index
pub fn delete(&self, id: i64) -> Result<(), Error> { pub fn delete(&self, id: i64) -> Result<()> {
match self.database.delete(id) { self.database.delete(id)?;
Ok(_) => index(&self.database, &self.memory), index(&self.database, &self.memory)
Err(e) => Err(Error::Database(e)),
}
} }
// Getters // Getters
@ -77,11 +64,9 @@ impl Search {
// Tools // Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> { pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components // Migrate self components
if let Err(e) = database::init(tx) { database::init(tx)?;
return Err(e.to_string());
}
// Delegate migration to childs // Delegate migration to childs
// nothing yet.. // nothing yet..
@ -91,15 +76,10 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> {
} }
/// Sync memory index with database /// Sync memory index with database
fn index(database: &Database, memory: &Memory) -> Result<(), Error> { fn index(database: &Database, memory: &Memory) -> Result<()> {
memory.clear(); memory.clear();
match database.records() { for record in database.records()? {
Ok(records) => { memory.push(record.id, record.query, record.is_default)
for record in records {
memory.push(record.id, record.query, record.is_default)
}
}
Err(e) => return Err(Error::Database(e)),
} }
Ok(()) Ok(())
} }

View file

@ -1,4 +1,5 @@
use sqlite::{Connection, Error, Transaction}; use anyhow::Result;
use sqlite::{Connection, Transaction};
use std::{rc::Rc, sync::RwLock}; use std::{rc::Rc, sync::RwLock};
#[derive(Clone)] #[derive(Clone)]
@ -18,7 +19,7 @@ impl Database {
// Constructors // Constructors
/// Create new `Self` /// Create new `Self`
pub fn init(connection: &Rc<RwLock<Connection>>, profile_id: &Rc<i64>) -> Result<Self, Error> { pub fn init(connection: &Rc<RwLock<Connection>>, profile_id: &Rc<i64>) -> Result<Self> {
let mut writable = connection.write().unwrap(); // @TODO handle let mut writable = connection.write().unwrap(); // @TODO handle
let tx = writable.transaction()?; let tx = writable.transaction()?;
@ -38,7 +39,7 @@ impl Database {
// Getters // Getters
/// Get records from database /// Get records from database
pub fn records(&self) -> Result<Vec<Row>, Error> { pub fn records(&self) -> Result<Vec<Row>> {
let readable = self.connection.read().unwrap(); // @TODO handle let readable = self.connection.read().unwrap(); // @TODO handle
let tx = readable.unchecked_transaction()?; let tx = readable.unchecked_transaction()?;
select(&tx, *self.profile_id) select(&tx, *self.profile_id)
@ -48,30 +49,19 @@ impl Database {
/// Create new record in database /// Create new record in database
/// * return last insert ID on success /// * return last insert ID on success
pub fn add(&self, query: String, is_default: bool) -> Result<i64, Error> { pub fn add(&self, query: String, is_default: bool) -> Result<i64> {
// Begin new transaction
let mut writable = self.connection.write().unwrap(); // @TODO handle let mut writable = self.connection.write().unwrap(); // @TODO handle
let tx = writable.transaction()?; let tx = writable.transaction()?;
// Create new record
if is_default { if is_default {
// make sure only one default provider in set
reset(&tx, *self.profile_id, !is_default)?; reset(&tx, *self.profile_id, !is_default)?;
} }
insert(&tx, *self.profile_id, query, is_default)?; let id = insert(&tx, *self.profile_id, query, is_default)?;
tx.commit()?;
// Hold insert ID for result Ok(id)
let id = last_insert_id(&tx);
// Done
match tx.commit() {
Ok(_) => Ok(id),
Err(e) => Err(e),
}
} }
/// Delete record from database /// Delete record from database
pub fn delete(&self, id: i64) -> Result<(), Error> { pub fn delete(&self, id: i64) -> Result<()> {
// Begin new transaction // Begin new transaction
let mut writable = self.connection.write().unwrap(); // @TODO let mut writable = self.connection.write().unwrap(); // @TODO
let tx = writable.transaction()?; let tx = writable.transaction()?;
@ -100,11 +90,12 @@ impl Database {
} }
// Done // Done
tx.commit() tx.commit()?;
Ok(())
} }
/// Delete record from database /// Delete record from database
pub fn set_default(&self, id: i64) -> Result<(), Error> { pub fn set_default(&self, id: i64) -> Result<()> {
// Begin new transaction // Begin new transaction
let mut writable = self.connection.write().unwrap(); // @TODO let mut writable = self.connection.write().unwrap(); // @TODO
let tx = writable.transaction()?; let tx = writable.transaction()?;
@ -114,14 +105,15 @@ impl Database {
// Delete record by ID // Delete record by ID
set_default(&tx, *self.profile_id, id, true)?; set_default(&tx, *self.profile_id, id, true)?;
tx.commit() tx.commit()?;
Ok(())
} }
} }
// Low-level DB API // Low-level DB API
pub fn init(tx: &Transaction) -> Result<usize, Error> { pub fn init(tx: &Transaction) -> Result<usize> {
tx.execute( Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `profile_search` "CREATE TABLE IF NOT EXISTS `profile_search`
( (
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -132,15 +124,10 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
FOREIGN KEY (`profile_id`) REFERENCES `profile` (`id`) FOREIGN KEY (`profile_id`) REFERENCES `profile` (`id`)
)", )",
[], [],
) )?)
} }
fn insert( fn insert(tx: &Transaction, profile_id: i64, query: String, is_default: bool) -> Result<i64> {
tx: &Transaction,
profile_id: i64,
query: String,
is_default: bool,
) -> Result<usize, Error> {
tx.execute( tx.execute(
"INSERT INTO `profile_search` ( "INSERT INTO `profile_search` (
`profile_id`, `profile_id`,
@ -148,10 +135,11 @@ fn insert(
`query` `query`
) VALUES (?, ?, ?)", ) VALUES (?, ?, ?)",
(profile_id, is_default, query), (profile_id, is_default, query),
) )?;
Ok(tx.last_insert_rowid())
} }
fn select(tx: &Transaction, profile_id: i64) -> Result<Vec<Row>, Error> { fn select(tx: &Transaction, profile_id: i64) -> Result<Vec<Row>> {
let mut stmt = tx.prepare( let mut stmt = tx.prepare(
"SELECT `id`, `profile_id`, `is_default`, `query` "SELECT `id`, `profile_id`, `is_default`, `query`
FROM `profile_search` FROM `profile_search`
@ -177,35 +165,26 @@ fn select(tx: &Transaction, profile_id: i64) -> Result<Vec<Row>, Error> {
Ok(records) Ok(records)
} }
fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> { fn delete(tx: &Transaction, id: i64) -> Result<usize> {
tx.execute("DELETE FROM `profile_search` WHERE `id` = ?", [id]) Ok(tx.execute("DELETE FROM `profile_search` WHERE `id` = ?", [id])?)
} }
fn reset(tx: &Transaction, profile_id: i64, is_default: bool) -> Result<usize, Error> { fn reset(tx: &Transaction, profile_id: i64, is_default: bool) -> Result<usize> {
tx.execute( Ok(tx.execute(
"UPDATE `profile_search` SET `is_default` = ? WHERE `profile_id` = ?", "UPDATE `profile_search` SET `is_default` = ? WHERE `profile_id` = ?",
(is_default, profile_id), (is_default, profile_id),
) )?)
} }
fn set_default( fn set_default(tx: &Transaction, profile_id: i64, id: i64, is_default: bool) -> Result<usize> {
tx: &Transaction, Ok(tx.execute(
profile_id: i64,
id: i64,
is_default: bool,
) -> Result<usize, Error> {
tx.execute(
"UPDATE `profile_search` SET `is_default` = ? WHERE `profile_id` = ? AND `id` = ?", "UPDATE `profile_search` SET `is_default` = ? WHERE `profile_id` = ? AND `id` = ?",
(is_default, profile_id, id), (is_default, profile_id, id),
) )?)
}
fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
} }
/// Init default search providers list for given profile /// Init default search providers list for given profile
fn add_defaults(tx: &Transaction, profile_id: i64) -> Result<(), Error> { fn add_defaults(tx: &Transaction, profile_id: i64) -> Result<()> {
for (provider, is_default) in &[ for (provider, is_default) in &[
("gemini://tlgs.one/search/search", true), ("gemini://tlgs.one/search/search", true),
("gemini://kennedy.gemi.dev/search", false), ("gemini://kennedy.gemi.dev/search", false),

View file

@ -1,16 +0,0 @@
use std::fmt::{Display, Formatter, Result};
#[derive(Debug)]
pub enum Error {
Database(sqlite::Error),
}
impl Display for Error {
fn fmt(&self, f: &mut Formatter) -> Result {
match self {
Self::Database(e) => {
write!(f, "Database error: {e}")
}
}
}
}