From 8df429e98f7520d4d0dcd77e9b5b78b833573949 Mon Sep 17 00:00:00 2001 From: yggverse Date: Sat, 26 Jul 2025 07:50:19 +0300 Subject: [PATCH] implement backend for the proxy misc configuration --- src/profile/proxy.rs | 8 ++- src/profile/proxy/misc.rs | 81 +++++++++++++++++++++ src/profile/proxy/misc/database.rs | 99 ++++++++++++++++++++++++++ src/profile/proxy/misc/database/row.rs | 6 ++ src/profile/proxy/misc/memory.rs | 37 ++++++++++ src/profile/proxy/misc/memory/bool.rs | 35 +++++++++ 6 files changed, 265 insertions(+), 1 deletion(-) create mode 100644 src/profile/proxy/misc.rs create mode 100644 src/profile/proxy/misc/database.rs create mode 100644 src/profile/proxy/misc/database/row.rs create mode 100644 src/profile/proxy/misc/memory.rs create mode 100644 src/profile/proxy/misc/memory/bool.rs diff --git a/src/profile/proxy.rs b/src/profile/proxy.rs index a66e6ed6..5bdbcb18 100644 --- a/src/profile/proxy.rs +++ b/src/profile/proxy.rs @@ -1,9 +1,11 @@ mod ignore; +mod misc; mod rule; use anyhow::Result; use gtk::gio::{ProxyResolver, SimpleProxyResolver}; use ignore::Ignore; +use misc::Misc; use r2d2::Pool; use r2d2_sqlite::SqliteConnectionManager; use rule::Rule; @@ -11,6 +13,7 @@ use rule::Rule; pub struct Proxy { pub ignore: Ignore, pub rule: Rule, + pub misc: Misc, } impl Proxy { @@ -19,6 +22,7 @@ impl Proxy { pub fn init(database_pool: &Pool, profile_id: i64) -> Result { Ok(Self { ignore: Ignore::init(database_pool, profile_id)?, + misc: Misc::init(database_pool, profile_id)?, rule: Rule::init(database_pool, profile_id)?, }) } @@ -26,8 +30,9 @@ impl Proxy { // Actions pub fn save(&self) -> Result<()> { - self.rule.save()?; self.ignore.save()?; + self.misc.save()?; + self.rule.save()?; Ok(()) } @@ -63,6 +68,7 @@ pub fn migrate(tx: &sqlite::Transaction) -> Result<()> { // Delegate migration to childs ignore::migrate(tx)?; + misc::migrate(tx)?; rule::migrate(tx)?; Ok(()) diff --git a/src/profile/proxy/misc.rs b/src/profile/proxy/misc.rs new file mode 100644 index 00000000..416ebd60 --- /dev/null +++ b/src/profile/proxy/misc.rs @@ -0,0 +1,81 @@ +mod database; +mod memory; + +use anyhow::Result; +use database::Database; +use memory::Memory; +use r2d2::Pool; +use r2d2_sqlite::SqliteConnectionManager; +use std::{cell::RefCell, collections::HashSet}; + +pub struct Misc { + database: Database, + memory: RefCell>, +} + +impl Misc { + // Constructors + + pub fn init(database_pool: &Pool, profile_id: i64) -> Result { + let database = Database::init(database_pool, profile_id); + + let rows = database.rows()?; + let memory = RefCell::new(HashSet::with_capacity(rows.len())); + + { + // build in-memory index... + let mut m = memory.borrow_mut(); + // create initial preset (populate index with the default values) + assert!(m.insert(Memory::highlight_request_entry(true))); + + // update values from the DB (if exists) + for row in rows { + assert!(!m.insert(Memory::from_db_row(&row.key, row.value.as_deref()).unwrap())) + // * panics if the DB was malformed or changed unexpectedly + } + } + + Ok(Self { database, memory }) + } + + // Setters + + pub fn save(&self) -> Result<()> { + for k in self.memory.take() { + self.database.set(k.into_db_row())?; + } + Ok(()) + } + + pub fn set_highlight_request_entry(&self, value: bool) { + assert!( + self.memory + .borrow_mut() + .insert(Memory::highlight_request_entry(value)), + ) + } + + // Getters + + pub fn is_highlight_request_entry(&self) -> bool { + if let Some(k) = self.memory.borrow().iter().next() { + match k { + Memory::HighlightRequestEntry(v) => return v.is_true(), + } + } + false + } +} + +// Tools + +pub fn migrate(tx: &sqlite::Transaction) -> Result<()> { + // Migrate self components + database::init(tx)?; + + // Delegate migration to childs + // nothing yet... + + // Success + Ok(()) +} diff --git a/src/profile/proxy/misc/database.rs b/src/profile/proxy/misc/database.rs new file mode 100644 index 00000000..7a36254c --- /dev/null +++ b/src/profile/proxy/misc/database.rs @@ -0,0 +1,99 @@ +mod row; + +use anyhow::Result; +use r2d2::Pool; +use r2d2_sqlite::SqliteConnectionManager; +use row::Row; +use sqlite::Transaction; + +pub struct Database { + pool: Pool, + profile_id: i64, +} + +impl Database { + // Constructors + + pub fn init(pool: &Pool, profile_id: i64) -> Self { + Self { + pool: pool.clone(), + profile_id, + } + } + + // Getters + + pub fn rows(&self) -> Result> { + rows(&self.pool.get()?.unchecked_transaction()?, self.profile_id) + } + + // Setters + + pub fn set(&self, (key, value): (String, String)) -> Result { + let mut c = self.pool.get()?; + let tx = c.transaction()?; + let id = set(&tx, self.profile_id, key, value)?; + tx.commit()?; + Ok(id) + } +} + +// Low-level DB API + +pub fn init(tx: &Transaction) -> Result { + Ok(tx.execute( + "CREATE TABLE IF NOT EXISTS `profile_proxy_misc` + ( + `id` INTEGER PRIMARY KEY AUTOINCREMENT NOT NULL, + `profile_id` INTEGER NOT NULL, + `key` VARCHAR(255) NOT NULL, + `value` TEXT NULL, + + FOREIGN KEY (`profile_id`) REFERENCES `profile` (`id`), + UNIQUE (`key`) + )", + [], + )?) +} + +fn set(tx: &Transaction, profile_id: i64, key: String, value: String) -> Result { + tx.execute( + "INSERT INTO `profile_proxy_misc` ( + `profile_id`, + `key`, + `value` + ) VALUES (?, ?, ?) ON CONFLICT (`key`) DO UPDATE SET `value` = `excluded`.`value`", + (profile_id, key, value), + )?; + Ok(tx.last_insert_rowid()) +} + +fn rows(tx: &Transaction, profile_id: i64) -> Result> { + let mut stmt = tx.prepare( + "SELECT `id`, + `profile_id`, + `key`, + `value` + + FROM `profile_proxy_misc` + WHERE `profile_id` = ?", + )?; + + let result = stmt.query_map([profile_id], |row| { + Ok(Row { + //id: row.get(0)?, + //profile_id: row.get(1)?, + key: row.get(2)?, + value: row.get(3)?, + }) + })?; + + let mut rows = Vec::new(); + + for r in result { + let row = r?; + rows.push(row); + } + + Ok(rows) +} diff --git a/src/profile/proxy/misc/database/row.rs b/src/profile/proxy/misc/database/row.rs new file mode 100644 index 00000000..104d0d33 --- /dev/null +++ b/src/profile/proxy/misc/database/row.rs @@ -0,0 +1,6 @@ +pub struct Row { + //pub id: i64, + //pub profile_id: i64, + pub key: String, + pub value: Option, +} diff --git a/src/profile/proxy/misc/memory.rs b/src/profile/proxy/misc/memory.rs new file mode 100644 index 00000000..fd0ac933 --- /dev/null +++ b/src/profile/proxy/misc/memory.rs @@ -0,0 +1,37 @@ +mod bool; +use bool::Bool; + +// Shared values + +const HIGHLIGHT_REQUEST_ENTRY: &str = "highlight_request_entry"; + +#[derive(Eq, Hash, PartialEq)] +pub enum Memory { + HighlightRequestEntry(Bool), +} + +impl Memory { + // Constructors + + pub fn from_db_row(key: &str, value: Option<&str>) -> Option { + if key == HIGHLIGHT_REQUEST_ENTRY { + Some(Self::HighlightRequestEntry(Bool::from_db_value(value))) + } else { + None + } + } + + pub fn highlight_request_entry(value: bool) -> Self { + Memory::HighlightRequestEntry(Bool::from(value)) + } + + // Actions + + pub fn into_db_row(self) -> (String, String) { + match self { + Self::HighlightRequestEntry(value) => { + (HIGHLIGHT_REQUEST_ENTRY.to_string(), value.into_db_value()) + } + } + } +} diff --git a/src/profile/proxy/misc/memory/bool.rs b/src/profile/proxy/misc/memory/bool.rs new file mode 100644 index 00000000..90304f18 --- /dev/null +++ b/src/profile/proxy/misc/memory/bool.rs @@ -0,0 +1,35 @@ +const TRUE: &str = "1"; +const FALSE: &str = "0"; + +#[derive(Eq, Hash, PartialEq, Default)] +pub enum Bool { + True, + #[default] + False, +} + +impl Bool { + pub fn from(value: bool) -> Self { + if value { Self::True } else { Self::False } + } + + pub fn from_db_value(key: Option<&str>) -> Self { + if key.is_some_and(|k| k == TRUE) { + Self::True + } else { + Self::False + } + } + + pub fn into_db_value(self) -> String { + match self { + Self::True => TRUE, + Self::False => FALSE, + } + .to_string() + } + + pub fn is_true(&self) -> bool { + matches!(self, Self::True) + } +}