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]
ansi-parser = "0.9.1"
anyhow = "1.0.97"
ggemini = "0.17.0"
ggemtext = "0.3.1"
indexmap = "2.7.0"

View File

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

View File

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

View File

@ -1,12 +1,13 @@
use sqlite::{Error, Transaction};
use anyhow::Result;
use sqlite::Transaction;
pub struct Table {
pub id: i64,
// pub app_id: i64, not in use
}
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
pub fn init(tx: &Transaction) -> Result<usize> {
Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser`
(
`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`)
)",
[],
)
)?)
}
pub fn insert(tx: &Transaction, app_id: i64) -> Result<usize, Error> {
tx.execute("INSERT INTO `app_browser` (`app_id`) VALUES (?)", [app_id])
pub fn insert(tx: &Transaction, app_id: i64) -> Result<i64> {
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 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)
}
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
tx.execute("DELETE FROM `app_browser` WHERE `id` = ?", [id])
}
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
Ok(tx.execute("DELETE FROM `app_browser` WHERE `id` = ?", [id])?)
}

View File

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

View File

@ -1,4 +1,5 @@
use sqlite::{Error, Transaction};
use anyhow::Result;
use sqlite::Transaction;
pub struct Table {
pub id: i64,
@ -8,8 +9,8 @@ pub struct Table {
pub is_maximized: bool,
}
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
pub fn init(tx: &Transaction) -> Result<usize> {
Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_widget`
(
`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`)
)",
[],
)
)?)
}
pub fn insert(
@ -30,7 +31,7 @@ pub fn insert(
default_width: i32,
default_height: i32,
is_maximized: bool,
) -> Result<usize, Error> {
) -> Result<i64> {
tx.execute(
"INSERT INTO `app_browser_widget` (
`app_browser_id`,
@ -44,10 +45,11 @@ pub fn insert(
default_height 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(
"SELECT `id`,
`app_browser_id`,
@ -76,11 +78,6 @@ pub fn select(tx: &Transaction, app_browser_id: i64) -> Result<Vec<Table>, Error
Ok(records)
}
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
tx.execute("DELETE FROM `app_browser_widget` WHERE `id` = ?", [id])
pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
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;
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 crate::Profile;
use action::{Action, Position};
use adw::ToolbarView;
use anyhow::Result;
use gtk::{prelude::BoxExt, Box, Orientation};
use header::Header;
use sqlite::Transaction;
use std::rc::Rc;
use tab::Tab;
pub struct Window {
pub action: Rc<Action>,
@ -145,52 +145,26 @@ impl Window {
self.tab.escape();
}
pub fn clean(&self, transaction: &Transaction, app_browser_id: i64) -> Result<(), String> {
match database::select(transaction, app_browser_id) {
Ok(records) => {
for record in records {
match database::delete(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()),
pub fn clean(&self, transaction: &Transaction, app_browser_id: i64) -> Result<()> {
for record in database::select(transaction, app_browser_id)? {
database::delete(transaction, record.id)?;
// Delegate clean action to childs
self.tab.clean(transaction, record.id)?;
}
Ok(())
}
pub fn restore(&self, transaction: &Transaction, app_browser_id: i64) -> Result<(), String> {
match database::select(transaction, app_browser_id) {
Ok(records) => {
for record in records {
// Delegate restore action to childs
self.tab.restore(transaction, record.id)?;
}
}
Err(e) => return Err(e.to_string()),
pub fn restore(&self, transaction: &Transaction, app_browser_id: i64) -> Result<()> {
for record in database::select(transaction, app_browser_id)? {
// Delegate restore action to childs
self.tab.restore(transaction, record.id)?;
}
Ok(())
}
pub fn save(&self, transaction: &Transaction, app_browser_id: i64) -> Result<(), String> {
match database::insert(transaction, app_browser_id) {
Ok(_) => {
// 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()),
}
pub fn save(&self, transaction: &Transaction, app_browser_id: i64) -> Result<()> {
self.tab
.save(transaction, database::insert(transaction, app_browser_id)?)?;
Ok(())
}
@ -200,11 +174,9 @@ impl Window {
}
// Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> {
pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components
if let Err(e) = database::init(tx) {
return Err(e.to_string());
}
database::init(tx)?;
// Delegate migration to childs
tab::migrate(tx)?;

View File

@ -1,12 +1,13 @@
use sqlite::{Error, Transaction};
use anyhow::Result;
use sqlite::Transaction;
pub struct Table {
pub id: i64,
// pub app_browser_id: i64, not in use
}
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
pub fn init(tx: &Transaction) -> Result<usize> {
Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window`
(
`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`)
)",
[],
)
)?)
}
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(
"INSERT INTO `app_browser_window` (`app_browser_id`) VALUES (?)",
[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(
"SELECT `id`,
`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)
}
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
tx.execute("DELETE FROM `app_browser_window` WHERE `id` = ?", [id])
}
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
Ok(tx.execute("DELETE FROM `app_browser_window` WHERE `id` = ?", [id])?)
}

View File

@ -8,6 +8,7 @@ use super::{Action as WindowAction, BrowserAction, Position};
use crate::Profile;
use action::Action;
use adw::{TabPage, TabView};
use anyhow::Result;
use error::Error;
use gtk::{
gio::Icon,
@ -309,102 +310,68 @@ impl Tab {
}
}
pub fn clean(
&self,
transaction: &Transaction,
app_browser_window_id: i64,
) -> Result<(), String> {
match database::select(transaction, app_browser_window_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()),
}
}
pub fn clean(&self, transaction: &Transaction, app_browser_window_id: i64) -> Result<()> {
for record in database::select(transaction, app_browser_window_id)? {
database::delete(transaction, record.id)?;
// Delegate clean action to childs
for (_, item) in self.index.borrow().iter() {
item.clean(transaction, record.id)?
}
Err(e) => return Err(e.to_string()),
}
Ok(())
}
pub fn restore(
&self,
transaction: &Transaction,
app_browser_window_id: i64,
) -> Result<(), String> {
match database::select(transaction, app_browser_window_id) {
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),
);
pub fn restore(&self, transaction: &Transaction, app_browser_window_id: i64) -> Result<()> {
for tab_record in database::select(transaction, app_browser_window_id)? {
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
let item = Rc::new(Item::build(
(&tab_page, &target_child),
&self.profile,
// Actions
(&self.browser_action, &self.window_action, &self.action),
// Options
None,
false,
));
// Init new tab item
let item = Rc::new(Item::build(
(&tab_page, &target_child),
&self.profile,
// Actions
(&self.browser_action, &self.window_action, &self.action),
// Options
None,
false,
));
// Relate with GTK `TabPage` with app `Item`
self.index
.borrow_mut()
.insert(item.tab_page.clone(), item.clone());
// Relate with GTK `TabPage` with app `Item`
self.index
.borrow_mut()
.insert(item.tab_page.clone(), item.clone());
// Setup
self.tab_view
.set_page_pinned(&item.tab_page, item_record.is_pinned);
// Setup
self.tab_view
.set_page_pinned(&item.tab_page, item_record.is_pinned);
if item_record.is_selected {
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)?;
}
if item_record.is_selected {
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)?;
}
Err(e) => return Err(e.to_string()),
}
Ok(())
}
pub fn save(
&self,
transaction: &Transaction,
app_browser_window_id: i64,
) -> 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()),
pub fn save(&self, transaction: &Transaction, app_browser_window_id: i64) -> Result<()> {
let id = database::insert(transaction, app_browser_window_id)?;
for (_, item) in self.index.borrow().iter() {
item.save(transaction, id, self.tab_view.page_position(&item.tab_page))?;
}
Ok(())
}
@ -430,11 +397,9 @@ impl Tab {
// Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> {
pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components
if let Err(e) = database::init(tx) {
return Err(e.to_string());
}
database::init(tx)?;
// Delegate migration to childs
item::migrate(tx)?;

View File

@ -1,12 +1,13 @@
use sqlite::{Error, Transaction};
use anyhow::Result;
use sqlite::Transaction;
pub struct Table {
pub id: i64,
// pub app_browser_window_id: i64, not in use
}
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
pub fn init(tx: &Transaction) -> Result<usize> {
Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab`
(
`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`)
)",
[],
)
)?)
}
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(
"INSERT INTO `app_browser_window_tab` (
`app_browser_window_id`
) VALUES (?)",
[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(
"SELECT `id`,
`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)
}
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
tx.execute("DELETE FROM `app_browser_window_tab` WHERE `id` = ?", [id])
}
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
Ok(tx.execute("DELETE FROM `app_browser_window_tab` WHERE `id` = ?", [id])?)
}

View File

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

View File

@ -1,4 +1,5 @@
use sqlite::{Error, Transaction};
use anyhow::Result;
use sqlite::Transaction;
pub struct Table {
pub id: i64,
@ -8,8 +9,8 @@ pub struct Table {
pub is_selected: bool,
}
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
pub fn init(tx: &Transaction) -> Result<usize> {
Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item`
(
`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`)
)",
[],
)
)?)
}
pub fn insert(
@ -30,7 +31,7 @@ pub fn insert(
page_position: i32,
is_pinned: bool,
is_selected: bool,
) -> Result<usize, Error> {
) -> Result<i64> {
tx.execute(
"INSERT INTO `app_browser_window_tab_item` (
`app_browser_window_tab_id`,
@ -44,10 +45,11 @@ pub fn insert(
is_pinned 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(
"SELECT `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)
}
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
tx.execute(
pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
Ok(tx.execute(
"DELETE FROM `app_browser_window_tab_item` WHERE `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 adw::TabPage;
use anyhow::Result;
use content::Content;
use error::Error;
use input::Input;
@ -104,20 +105,11 @@ impl Page {
&self,
transaction: &Transaction,
app_browser_window_tab_item_id: i64,
) -> Result<(), String> {
match database::select(transaction, app_browser_window_tab_item_id) {
Ok(records) => {
for record in records {
match database::delete(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()),
) -> Result<()> {
for record in database::select(transaction, app_browser_window_tab_item_id)? {
database::delete(transaction, record.id)?;
// Delegate clean action to the item childs
self.navigation.clean(transaction, &record.id)?;
}
Ok(())
}
@ -127,25 +119,20 @@ impl Page {
&self,
transaction: &Transaction,
app_browser_window_tab_item_id: i64,
) -> Result<(), String> {
) -> Result<()> {
// Begin page restore
match database::select(transaction, app_browser_window_tab_item_id) {
Ok(records) => {
for record in records {
// Restore `Self`
if let Some(title) = record.title {
self.set_title(title.as_str());
}
self.set_needs_attention(record.is_needs_attention);
// Restore child components
self.navigation.restore(transaction, &record.id)?;
// Make initial page history snap using `navigation` values restored
if let Some(uri) = self.navigation.uri() {
self.profile.history.memory.request.set(uri);
}
}
for record in database::select(transaction, app_browser_window_tab_item_id)? {
// Restore `Self`
if let Some(title) = record.title {
self.set_title(title.as_str());
}
self.set_needs_attention(record.is_needs_attention);
// Restore child components
self.navigation.restore(transaction, &record.id)?;
// Make initial page history snap using `navigation` values restored
if let Some(uri) = self.navigation.uri() {
self.profile.history.memory.request.set(uri);
}
Err(e) => return Err(e.to_string()),
}
Ok(())
}
@ -155,10 +142,10 @@ impl Page {
&self,
transaction: &Transaction,
app_browser_window_tab_item_id: i64,
) -> Result<(), String> {
) -> Result<()> {
// Keep value in memory until operation complete
let title = self.tab_page.title();
match database::insert(
let id = database::insert(
transaction,
app_browser_window_tab_item_id,
self.tab_page.needs_attention(),
@ -166,15 +153,9 @@ impl Page {
true => None,
false => Some(title.as_str()),
},
) {
Ok(_) => {
let id = database::last_insert_id(transaction);
// Delegate save action to childs
self.navigation.save(transaction, &id)?;
}
Err(e) => return Err(e.to_string()),
}
)?;
// Delegate save action to childs
self.navigation.save(transaction, &id)?;
Ok(())
}
@ -198,11 +179,9 @@ impl Page {
// Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> {
pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components
if let Err(e) = database::init(tx) {
return Err(e.to_string());
}
database::init(tx)?;
// Delegate migration to childs
navigation::migrate(tx)?;

View File

@ -1,4 +1,5 @@
use sqlite::{Error, Transaction};
use anyhow::Result;
use sqlite::Transaction;
pub struct Table {
pub id: i64,
@ -7,8 +8,8 @@ pub struct Table {
pub title: Option<String>,
}
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
pub fn init(tx: &Transaction) -> Result<usize> {
Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page`
(
`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`)
)",
[],
)
)?)
}
pub fn insert(
@ -27,7 +28,7 @@ pub fn insert(
app_browser_window_tab_item_id: i64,
is_needs_attention: bool,
title: Option<&str>,
) -> Result<usize, Error> {
) -> Result<i64> {
tx.execute(
"INSERT INTO `app_browser_window_tab_item_page` (
`app_browser_window_tab_item_id`,
@ -35,10 +36,11 @@ pub fn insert(
`title`
) VALUES (?, ?, ?)",
(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(
"SELECT `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)
}
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
tx.execute(
pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
Ok(tx.execute(
"DELETE FROM `app_browser_window_tab_item_page` WHERE `id` = ?",
[id],
)
}
pub fn last_insert_id(tx: &Transaction) -> i64 {
tx.last_insert_rowid()
)?)
}

View File

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

View File

@ -48,7 +48,7 @@ impl Bookmark for Button {
}
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_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 id: i64,
// pub app_browser_window_tab_item_page_id: i64, not in use
}
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
pub fn init(tx: &Transaction) -> Result<usize> {
Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page_navigation`
(
`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`)
)",
[],
)
)?)
}
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(
"INSERT INTO `app_browser_window_tab_item_page_navigation` (
`app_browser_window_tab_item_page_id`
) VALUES (?)",
[app_browser_window_tab_item_page_id],
)
)?;
Ok(tx.last_insert_rowid())
}
pub fn select(
tx: &Transaction,
app_browser_window_tab_item_page_id: &i64,
) -> Result<Vec<Table>, Error> {
pub fn select(tx: &Transaction, app_browser_window_tab_item_page_id: &i64) -> Result<Vec<Table>> {
let mut stmt = tx.prepare(
"SELECT `id`,
`app_browser_window_tab_item_page_id`
@ -55,13 +54,9 @@ pub fn select(
Ok(records)
}
pub fn delete(tx: &Transaction, id: &i64) -> Result<usize, Error> {
tx.execute(
pub fn delete(tx: &Transaction, id: &i64) -> Result<usize> {
Ok(tx.execute(
"DELETE FROM `app_browser_window_tab_item_page_navigation` WHERE `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 search;
use adw::{prelude::AdwDialogExt, AlertDialog};
use primary_icon::PrimaryIcon;
use super::{ItemAction, Profile};
use adw::{prelude::AdwDialogExt, AlertDialog};
use anyhow::Result;
use gtk::{
glib::{gformat, GString, Uri, UriFlags},
prelude::{EditableExt, EntryExt, WidgetExt},
Entry, EntryIconPosition, StateFlags,
};
use primary_icon::PrimaryIcon;
use sqlite::Transaction;
use std::{cell::Cell, rc::Rc};
@ -29,19 +29,19 @@ pub trait Request {
&self,
transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String>;
) -> Result<()>;
fn restore(
&self,
transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String>;
) -> Result<()>;
fn save(
&self,
transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String>;
) -> Result<()>;
fn update_primary_icon(&self, profile: &Profile);
fn update_secondary_icon(&self);
@ -147,22 +147,13 @@ impl Request for Entry {
&self,
transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String> {
match database::select(transaction, app_browser_window_tab_item_page_navigation_id) {
Ok(records) => {
for record in records {
match database::delete(transaction, &record.id) {
Ok(_) => {
// Delegate clean action to the item childs
// nothing yet..
}
Err(e) => return Err(e.to_string()),
}
}
}
Err(e) => return Err(e.to_string()),
) -> Result<()> {
for record in database::select(transaction, app_browser_window_tab_item_page_navigation_id)?
{
database::delete(transaction, &record.id)?;
// Delegate clean action to the item childs
// nothing yet..
}
Ok(())
}
@ -170,21 +161,15 @@ impl Request for Entry {
&self,
transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String> {
match database::select(transaction, app_browser_window_tab_item_page_navigation_id) {
Ok(records) => {
for record in records {
if let Some(text) = record.text {
self.set_text(&text);
}
// Delegate restore action to the item childs
// nothing yet..
}
) -> Result<()> {
for record in database::select(transaction, app_browser_window_tab_item_page_navigation_id)?
{
if let Some(text) = record.text {
self.set_text(&text);
}
Err(e) => return Err(e.to_string()),
// Delegate restore action to the item childs
// nothing yet..
}
Ok(())
}
@ -192,27 +177,19 @@ impl Request for Entry {
&self,
transaction: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<(), String> {
) -> Result<()> {
// Keep value in memory until operation complete
let text = self.text();
match database::insert(
let _id = database::insert(
transaction,
app_browser_window_tab_item_page_navigation_id,
match text.is_empty() {
true => None,
false => Some(text.as_str()),
},
) {
Ok(_) => {
// let id = database::last_insert_id(transaction);
// Delegate save action to childs
// nothing yet..
}
Err(e) => return Err(e.to_string()),
}
)?;
// Delegate save action to childs
// nothing yet..
Ok(())
}
@ -369,11 +346,9 @@ impl Request for Entry {
// Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> {
pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components
if let Err(e) = database::init(tx) {
return Err(e.to_string());
}
database::init(tx)?;
// Delegate migration to childs
// nothing yet..

View File

@ -1,4 +1,5 @@
use sqlite::{Error, Transaction};
use anyhow::Result;
use sqlite::Transaction;
pub struct Table {
pub id: i64,
@ -6,8 +7,8 @@ pub struct Table {
pub text: Option<String>, // can be stored as NULL
}
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
pub fn init(tx: &Transaction) -> Result<usize> {
Ok( tx.execute(
"CREATE TABLE IF NOT EXISTS `app_browser_window_tab_item_page_navigation_request`
(
`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`)
)",
[],
)
)?)
}
pub fn insert(
tx: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64,
text: Option<&str>,
) -> Result<usize, Error> {
) -> Result<i64> {
tx.execute(
"INSERT INTO `app_browser_window_tab_item_page_navigation_request` (
`app_browser_window_tab_item_page_navigation_id`,
`text`
) VALUES (?, ?)",
(app_browser_window_tab_item_page_navigation_id, text),
)
)?;
Ok(tx.last_insert_rowid())
}
pub fn select(
tx: &Transaction,
app_browser_window_tab_item_page_navigation_id: &i64,
) -> Result<Vec<Table>, Error> {
) -> Result<Vec<Table>> {
let mut stmt = tx.prepare(
"SELECT `id`,
`app_browser_window_tab_item_page_navigation_id`,
@ -64,14 +66,9 @@ pub fn select(
Ok(records)
}
pub fn delete(tx: &Transaction, id: &i64) -> Result<usize, Error> {
tx.execute(
pub fn delete(tx: &Transaction, id: &i64) -> Result<usize> {
Ok(tx.execute(
"DELETE FROM `app_browser_window_tab_item_page_navigation_request` WHERE `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 {
match gtk::init() {
Ok(_) => App::build(&Rc::new(Profile::new())).run(),
Ok(_) => App::build(&Rc::new(Profile::new().unwrap())).run(),
Err(_) => ExitCode::FAILURE,
}
}

View File

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

View File

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

View File

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

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 error::Error;
use anyhow::Result;
use itertools::Itertools;
use std::{cell::RefCell, collections::HashMap};
@ -29,37 +27,34 @@ impl Memory {
/// Add new record with `request` as key and `id` as value
/// * validate record with same key does not exist yet
pub fn add(&self, request: String, id: i64) -> Result<(), Error> {
pub fn add(&self, request: String, id: i64) -> Result<()> {
// Borrow shared index access
let mut index = self.index.borrow_mut();
// Prevent existing key overwrite
if index.contains_key(&request) {
return Err(Error::Overwrite(request));
panic!() // unexpected
}
// Slot should be free, let check it twice
match index.insert(request, id) {
Some(_) => Err(Error::Unexpected),
Some(_) => panic!(), // unexpected
None => Ok(()),
}
}
/// Delete record from index by `request`
/// * 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) {
Some(_) => Ok(()),
None => Err(Error::Unexpected), // @TODO
None => panic!(), // unexpected
}
}
/// Get `id` by `request` from memory index
pub fn get(&self, request: &str) -> Result<i64, Error> {
match self.index.borrow().get(request) {
Some(&value) => Ok(value),
None => Err(Error::Unexpected), // @TODO
}
pub fn get(&self, request: &str) -> Option<i64> {
self.index.borrow().get(request).copied()
}
/// 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 sqlite::{Connection, Error, Transaction};
use sqlite::{Connection, Transaction};
use std::{rc::Rc, sync::RwLock};
pub struct Table {
@ -26,14 +27,14 @@ impl Database {
// Getters
/// 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 tx = readable.unchecked_transaction()?;
select(&tx)
}
/// 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()?;
Ok(records.into_iter().find(|record| record.is_active))
}
@ -41,36 +42,24 @@ impl Database {
// Setters
/// Create new record in `Self` database connected
pub fn add(&self, is_active: bool, time: DateTime, name: Option<String>) -> Result<i64, Error> {
// Begin new transaction
pub fn add(&self, is_active: bool, time: DateTime, name: Option<String>) -> Result<i64> {
let mut writable = self.connection.write().unwrap();
let tx = writable.transaction()?;
// New record has active status
if is_active {
// Deactivate other records as only one profile should be active
for record in select(&tx)? {
update(&tx, record.id, false, record.time, record.name)?;
}
}
// Create new record
insert(&tx, is_active, time, name)?;
// Hold insert ID for result
let id = last_insert_id(&tx);
// Done
let id = insert(&tx, is_active, time, name)?;
tx.commit()?;
Ok(id)
}
}
// Low-level DB API
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
pub fn init(tx: &Transaction) -> Result<usize> {
Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `profile`
(
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -79,7 +68,7 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
`name` VARCHAR(255)
)",
[],
)
)?)
}
pub fn insert(
@ -87,7 +76,7 @@ pub fn insert(
is_active: bool,
time: DateTime,
name: Option<String>,
) -> Result<usize, Error> {
) -> Result<i64> {
tx.execute(
"INSERT INTO `profile` (
`is_active`,
@ -95,7 +84,8 @@ pub fn insert(
`name`
) VALUES (?, ?, ?)",
(is_active, time.to_unix(), name),
)
)?;
Ok(tx.last_insert_rowid())
}
pub fn update(
@ -104,14 +94,14 @@ pub fn update(
is_active: bool,
time: DateTime,
name: Option<String>,
) -> Result<usize, Error> {
tx.execute(
) -> Result<usize> {
Ok(tx.execute(
"UPDATE `profile` SET `is_active` = ?, `time` = ?, `name` = ? WHERE `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 result = stmt.query_map([], |row| {
Ok(Table {
@ -131,7 +121,3 @@ pub fn select(tx: &Transaction) -> Result<Vec<Table>, Error> {
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 certificate;
mod database;
mod error;
mod item;
mod memory;
use anyhow::{bail, Result};
use auth::Auth;
use database::Database;
pub use error::Error;
use item::Item;
use memory::Memory;
use gtk::glib::DateTime;
use item::Item;
use memory::Memory;
use sqlite::{Connection, Transaction};
use std::{rc::Rc, sync::RwLock};
@ -32,11 +29,11 @@ impl Identity {
pub fn build(
connection: &Rc<RwLock<Connection>>,
profile_identity_id: &Rc<i64>,
) -> Result<Self, Error> {
) -> Result<Self> {
// Init components
let auth = match Auth::build(connection) {
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 memory = Rc::new(Memory::new());
@ -58,33 +55,23 @@ impl Identity {
/// Add new record to database, update memory index
/// * return new `profile_identity_id` on success
pub fn add(&self, pem: &str) -> Result<i64, Error> {
match self.database.add(pem) {
Ok(profile_identity_id) => {
self.index()?;
Ok(profile_identity_id)
}
Err(e) => Err(Error::Database(e)),
}
pub fn add(&self, pem: &str) -> Result<i64> {
let profile_identity_id = self.database.add(pem)?;
self.index()?;
Ok(profile_identity_id)
}
/// Delete record from database including children dependencies, update memory index
pub fn delete(&self, profile_identity_id: i64) -> Result<(), Error> {
match self.auth.remove_ref(profile_identity_id) {
Ok(_) => match self.database.delete(profile_identity_id) {
Ok(_) => {
self.index()?;
Ok(())
}
Err(e) => Err(Error::Database(e)),
},
Err(e) => Err(Error::Auth(e)),
}
pub fn delete(&self, profile_identity_id: i64) -> Result<()> {
self.auth.remove_ref(profile_identity_id)?;
self.database.delete(profile_identity_id)?;
self.index()?;
Ok(())
}
/// Generate new certificate and insert record to DB, update memory index
/// * 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
match certificate::generate(
match time {
@ -97,29 +84,17 @@ impl Identity {
name,
) {
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`
pub fn index(&self) -> Result<(), Error> {
pub fn index(&self) -> Result<()> {
// Clear previous records
if let Err(e) = self.memory.clear() {
return Err(Error::Memory(e));
self.memory.clear()?;
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(())
}
@ -144,11 +119,9 @@ impl Identity {
// Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> {
pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components
if let Err(e) = database::init(tx) {
return Err(e.to_string());
}
database::init(tx)?;
// Delegate migration to childs
auth::migrate(tx)?;

View File

@ -1,13 +1,11 @@
//! Controller for children `database` and `memory` components
mod database;
mod error;
mod memory;
use anyhow::Result;
use database::Database;
pub use error::Error;
use memory::Memory;
use sqlite::{Connection, Transaction};
use std::{rc::Rc, sync::RwLock};
@ -21,7 +19,7 @@ impl Auth {
// Constructors
/// Create new `Self`
pub fn build(connection: &Rc<RwLock<Connection>>) -> Result<Self, Error> {
pub fn build(connection: &Rc<RwLock<Connection>>) -> Result<Self> {
// Init `Self`
let this = Self {
database: Rc::new(Database::build(connection)),
@ -41,18 +39,14 @@ impl Auth {
/// * deactivate active auth by remove previous records from `Self` database
/// * reindex `Self` memory index 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)
self.remove(request)?;
// Create new record (auth)
let profile_identity_auth_id = match self
let profile_identity_auth_id = self
.database
.add(profile_identity_id, &filter_scope(request))
{
Ok(id) => id,
Err(e) => return Err(Error::Database(e)),
};
.add(profile_identity_id, &filter_scope(request))?;
// Reindex
self.index()?;
@ -62,56 +56,31 @@ impl Auth {
}
/// Remove all records match request (unauthorize)
pub fn remove(&self, request: &str) -> Result<(), Error> {
match self.database.records_scope(Some(&filter_scope(request))) {
Ok(records) => {
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)),
pub fn remove(&self, request: &str) -> Result<()> {
for record in self.database.records_scope(Some(&filter_scope(request)))? {
self.database.delete(record.id)?;
}
self.index()?;
Ok(())
}
/// Remove all records match `profile_identity_id` foreign reference key
pub fn remove_ref(&self, profile_identity_id: i64) -> Result<(), Error> {
match self.database.records_ref(profile_identity_id) {
Ok(records) => {
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)),
pub fn remove_ref(&self, profile_identity_id: i64) -> Result<()> {
for record in self.database.records_ref(profile_identity_id)? {
self.database.delete(record.id)?;
}
self.index()?;
Ok(())
}
/// Create new `Memory` index from `Database` for `Self`
pub fn index(&self) -> Result<(), Error> {
pub fn index(&self) -> Result<()> {
// Clear previous records
if let Err(e) = self.memory.clear() {
return Err(Error::Memory(e));
}
self.memory.clear()?;
// Build new index
match self.database.records_scope(None) {
Ok(records) => {
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)),
for record in self.database.records_scope(None)? {
self.memory.add(record.scope, record.profile_identity_id)?;
}
Ok(())
}
@ -154,11 +123,9 @@ impl Auth {
// Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> {
pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components
if let Err(e) = database::init(tx) {
return Err(e.to_string());
}
database::init(tx)?;
// Delegate migration to childs
// 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};
pub struct Table {
@ -25,51 +26,34 @@ impl Database {
// Actions
/// Create new record in database
pub fn add(&self, profile_identity_id: i64, scope: &str) -> Result<i64, Error> {
// Begin new transaction
pub fn add(&self, profile_identity_id: i64, scope: &str) -> Result<i64> {
let mut writable = self.connection.write().unwrap(); // @TODO
let tx = writable.transaction()?;
// Create new record
insert(&tx, profile_identity_id, scope)?;
// Hold insert ID for result
let id = last_insert_id(&tx);
// Done
match tx.commit() {
Ok(_) => Ok(id),
Err(e) => Err(e),
}
let id = insert(&tx, profile_identity_id, scope)?;
tx.commit()?;
Ok(id)
}
/// Delete record with given `id` from database
pub fn delete(&self, id: i64) -> Result<(), Error> {
// Begin new transaction
pub fn delete(&self, id: i64) -> Result<()> {
let mut writable = self.connection.write().unwrap(); // @TODO
let tx = writable.transaction()?;
// Create new record
delete(&tx, id)?;
// Done
match tx.commit() {
Ok(_) => Ok(()),
Err(e) => Err(e),
}
tx.commit()?;
Ok(())
}
// Getters
/// 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 tx = readable.unchecked_transaction()?;
select_scope(&tx, 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 tx = readable.unchecked_transaction()?;
select_ref(&tx, profile_identity_id)
@ -78,8 +62,8 @@ impl Database {
// Low-level DB API
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
pub fn init(tx: &Transaction) -> Result<usize> {
Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `profile_identity_auth`
(
`id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL,
@ -90,24 +74,25 @@ pub fn init(tx: &Transaction) -> Result<usize, Error> {
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(
"INSERT INTO `profile_identity_auth` (
`profile_identity_id`,
`scope`
) VALUES (?, ?)",
(profile_identity_id, scope),
)
)?;
Ok(tx.last_insert_rowid())
}
pub fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
tx.execute("DELETE FROM `profile_identity_auth` WHERE `id` = ?", [id])
pub fn delete(tx: &Transaction, id: i64) -> Result<usize> {
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(
"SELECT `id`,
`profile_identity_id`,
@ -135,7 +120,7 @@ pub fn select_scope(tx: &Transaction, scope: Option<&str>) -> Result<Vec<Table>,
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(
"SELECT `id`,
`profile_identity_id`,
@ -162,7 +147,3 @@ pub fn select_ref(tx: &Transaction, profile_identity_id: i64) -> Result<Vec<Tabl
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 error;
use anyhow::{bail, Result};
pub use auth::Auth;
pub use error::Error;
use std::{cell::RefCell, collections::HashMap};
/// 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
/// * 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
let mut index = self.index.borrow_mut();
// Prevent existing key overwrite
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
match index.insert(scope, profile_identity_id) {
Some(_) => Err(Error::Unexpected),
Some(_) => bail!("Unexpected error"),
None => Ok(()),
}
}
/// Cleanup index
pub fn clear(&self) -> Result<(), Error> {
pub fn clear(&self) -> Result<()> {
let mut index = self.index.borrow_mut();
index.clear();
if index.is_empty() {
Ok(())
} 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 error::Error;
use anyhow::{bail, Result};
use gtk::gio::TlsCertificate;
/// Gemini identity holder for cached record in application-wide struct format.
@ -12,10 +10,10 @@ pub struct Item {
impl Item {
/// 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) {
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;
pub use error::Error;
use anyhow::{bail, Result};
use std::{cell::RefCell, collections::HashMap};
/// 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
/// * 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
let mut index = self.index.borrow_mut();
// Prevent existing key overwrite
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
match index.insert(profile_identity_id, pem) {
Some(_) => Err(Error::Unexpected),
Some(_) => bail!("Unexpected error"),
None => Ok(()),
}
}
/// 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) {
Some(pem) => Ok(pem.clone()),
None => Err(Error::NotFound(id)),
None => bail!("Record `{id}` not found in memory index"),
}
}
/// Cleanup index
pub fn clear(&self) -> Result<(), Error> {
pub fn clear(&self) -> Result<()> {
let mut index = self.index.borrow_mut();
index.clear();
if index.is_empty() {
Ok(())
} 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 error;
mod memory;
use anyhow::Result;
use database::Database;
use error::Error;
use gtk::glib::Uri;
use memory::Memory;
use sqlite::{Connection, Transaction};
@ -18,40 +17,30 @@ impl Search {
// Constructors
/// Create new `Self`
pub fn build(connection: &Rc<RwLock<Connection>>, profile_id: &Rc<i64>) -> Result<Self, Error> {
match Database::init(connection, profile_id) {
Ok(database) => {
// Init fast search index
let memory = Memory::init();
// Build initial index
index(&database, &memory)?;
// Return new `Self`
Ok(Self { database, memory })
}
Err(e) => Err(Error::Database(e)),
}
pub fn build(connection: &Rc<RwLock<Connection>>, profile_id: &Rc<i64>) -> Result<Self> {
let database = Database::init(connection, profile_id)?;
// Init fast search index
let memory = Memory::init();
// Build initial index
index(&database, &memory)?;
// Return new `Self`
Ok(Self { database, memory })
}
// Actions
/// Add new search provider record
/// * requires valid [Uri](https://docs.gtk.org/glib/struct.Uri.html)
pub fn add(&self, query: &Uri, is_default: bool) -> Result<(), Error> {
match self.database.add(query.to_string(), is_default) {
Ok(_) => index(&self.database, &self.memory),
Err(e) => Err(Error::Database(e)),
}
pub fn add(&self, query: &Uri, is_default: bool) -> Result<()> {
self.database.add(query.to_string(), is_default)?;
Ok(())
}
/// Add new search provider record
/// * requires valid [Uri](https://docs.gtk.org/glib/struct.Uri.html)
pub fn set_default(&self, profile_search_id: i64) -> Result<(), Error> {
match self.database.set_default(profile_search_id) {
Ok(_) => index(&self.database, &self.memory),
Err(e) => Err(Error::Database(e)),
}
pub fn set_default(&self, profile_search_id: i64) -> Result<()> {
self.database.set_default(profile_search_id)?;
index(&self.database, &self.memory)
}
/// Get records from the memory index
@ -60,11 +49,9 @@ impl Search {
}
/// Delete record from `database` and `memory` index
pub fn delete(&self, id: i64) -> Result<(), Error> {
match self.database.delete(id) {
Ok(_) => index(&self.database, &self.memory),
Err(e) => Err(Error::Database(e)),
}
pub fn delete(&self, id: i64) -> Result<()> {
self.database.delete(id)?;
index(&self.database, &self.memory)
}
// Getters
@ -77,11 +64,9 @@ impl Search {
// Tools
pub fn migrate(tx: &Transaction) -> Result<(), String> {
pub fn migrate(tx: &Transaction) -> Result<()> {
// Migrate self components
if let Err(e) = database::init(tx) {
return Err(e.to_string());
}
database::init(tx)?;
// Delegate migration to childs
// nothing yet..
@ -91,15 +76,10 @@ pub fn migrate(tx: &Transaction) -> Result<(), String> {
}
/// Sync memory index with database
fn index(database: &Database, memory: &Memory) -> Result<(), Error> {
fn index(database: &Database, memory: &Memory) -> Result<()> {
memory.clear();
match database.records() {
Ok(records) => {
for record in records {
memory.push(record.id, record.query, record.is_default)
}
}
Err(e) => return Err(Error::Database(e)),
for record in database.records()? {
memory.push(record.id, record.query, record.is_default)
}
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};
#[derive(Clone)]
@ -18,7 +19,7 @@ impl Database {
// Constructors
/// 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 tx = writable.transaction()?;
@ -38,7 +39,7 @@ impl Database {
// Getters
/// 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 tx = readable.unchecked_transaction()?;
select(&tx, *self.profile_id)
@ -48,30 +49,19 @@ impl Database {
/// Create new record in database
/// * return last insert ID on success
pub fn add(&self, query: String, is_default: bool) -> Result<i64, Error> {
// Begin new transaction
pub fn add(&self, query: String, is_default: bool) -> Result<i64> {
let mut writable = self.connection.write().unwrap(); // @TODO handle
let tx = writable.transaction()?;
// Create new record
if is_default {
// make sure only one default provider in set
reset(&tx, *self.profile_id, !is_default)?;
}
insert(&tx, *self.profile_id, query, is_default)?;
// Hold insert ID for result
let id = last_insert_id(&tx);
// Done
match tx.commit() {
Ok(_) => Ok(id),
Err(e) => Err(e),
}
let id = insert(&tx, *self.profile_id, query, is_default)?;
tx.commit()?;
Ok(id)
}
/// Delete record 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 tx = writable.transaction()?;
@ -100,11 +90,12 @@ impl Database {
}
// Done
tx.commit()
tx.commit()?;
Ok(())
}
/// Delete record from database
pub fn set_default(&self, id: i64) -> Result<(), Error> {
pub fn set_default(&self, id: i64) -> Result<()> {
// Begin new transaction
let mut writable = self.connection.write().unwrap(); // @TODO
let tx = writable.transaction()?;
@ -114,14 +105,15 @@ impl Database {
// Delete record by ID
set_default(&tx, *self.profile_id, id, true)?;
tx.commit()
tx.commit()?;
Ok(())
}
}
// Low-level DB API
pub fn init(tx: &Transaction) -> Result<usize, Error> {
tx.execute(
pub fn init(tx: &Transaction) -> Result<usize> {
Ok(tx.execute(
"CREATE TABLE IF NOT EXISTS `profile_search`
(
`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`)
)",
[],
)
)?)
}
fn insert(
tx: &Transaction,
profile_id: i64,
query: String,
is_default: bool,
) -> Result<usize, Error> {
fn insert(tx: &Transaction, profile_id: i64, query: String, is_default: bool) -> Result<i64> {
tx.execute(
"INSERT INTO `profile_search` (
`profile_id`,
@ -148,10 +135,11 @@ fn insert(
`query`
) VALUES (?, ?, ?)",
(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(
"SELECT `id`, `profile_id`, `is_default`, `query`
FROM `profile_search`
@ -177,35 +165,26 @@ fn select(tx: &Transaction, profile_id: i64) -> Result<Vec<Row>, Error> {
Ok(records)
}
fn delete(tx: &Transaction, id: i64) -> Result<usize, Error> {
tx.execute("DELETE FROM `profile_search` WHERE `id` = ?", [id])
fn delete(tx: &Transaction, id: i64) -> Result<usize> {
Ok(tx.execute("DELETE FROM `profile_search` WHERE `id` = ?", [id])?)
}
fn reset(tx: &Transaction, profile_id: i64, is_default: bool) -> Result<usize, Error> {
tx.execute(
fn reset(tx: &Transaction, profile_id: i64, is_default: bool) -> Result<usize> {
Ok(tx.execute(
"UPDATE `profile_search` SET `is_default` = ? WHERE `profile_id` = ?",
(is_default, profile_id),
)
)?)
}
fn set_default(
tx: &Transaction,
profile_id: i64,
id: i64,
is_default: bool,
) -> Result<usize, Error> {
tx.execute(
fn set_default(tx: &Transaction, profile_id: i64, id: i64, is_default: bool) -> Result<usize> {
Ok(tx.execute(
"UPDATE `profile_search` SET `is_default` = ? WHERE `profile_id` = ? AND `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
fn add_defaults(tx: &Transaction, profile_id: i64) -> Result<(), Error> {
fn add_defaults(tx: &Transaction, profile_id: i64) -> Result<()> {
for (provider, is_default) in &[
("gemini://tlgs.one/search/search", true),
("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}")
}
}
}
}