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

@ -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}")
}
}
}
}