zwischenstand

This commit is contained in:
2025-09-15 16:58:46 +02:00
parent 0a7de8b78b
commit 2809a8deb4
20 changed files with 14067 additions and 225 deletions

View File

@ -1,32 +0,0 @@
use crdt::trigger::TriggerManager;
use rusqlite::{Connection, Result};
// anpassen an dein Crate-Modul
fn main() -> Result<()> {
// Vault-Datenbank öffnen
let conn = Connection::open("vault.db")?;
println!("🔄 Setup CRDT triggers...");
// Tabellen aus der DB holen
let mut stmt =
conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'haex_%' AND NOT LIKE 'haex_crdt_%';")?;
let table_iter = stmt.query_map([], |row| row.get::<_, String>(0))?;
for table_name in table_iter {
let table_name = table_name?;
println!("➡️ Processing table: {}", table_name);
// Trigger für die Tabelle neu anlegen
match TriggerManager::setup_triggers_for_table(&conn, &table_name) {
Ok(_) => println!(" ✅ Triggers created for {}", table_name),
Err(e) => println!(
" ⚠️ Could not create triggers for {}: {:?}",
table_name, e
),
}
}
println!("✨ Done setting up CRDT triggers.");
Ok(())
}

View File

@ -105,7 +105,7 @@ impl SqlProxy {
hlc_timestamp: Option<&Timestamp>,
) -> Result<Option<String>, ProxyError> {
match stmt {
sqlparser::ast::Statement::Query(query) => {
Statement::Query(query) => {
if let SetExpr::Select(select) = &mut *query.body {
let mut tombstone_filters = Vec::new();
for twj in &select.from {
@ -162,6 +162,7 @@ impl SqlProxy {
// Hinweis: UNION, EXCEPT etc. werden hier nicht behandelt, was dem bisherigen Code entspricht.
}
Statement::CreateTable(create_table) => {
if self.is_audited_table(&create_table.name) {
self.add_crdt_columns(&mut create_table.columns);
@ -175,6 +176,7 @@ impl SqlProxy {
));
}
}
Statement::Insert(insert_stmt) => {
if let TableObject::TableName(name) = &insert_stmt.table {
if self.is_audited_table(name) {
@ -184,15 +186,7 @@ impl SqlProxy {
}
}
}
/* Statement::Update(update_stmt) => {
if let TableFactor::Table { name, .. } = &update_stmt.table.relation {
if self.is_audited_table(&name) {
if let Some(ts) = hlc_timestamp {
update_stmt.assignments.push(self.create_hlc_assignment(ts));
}
}
}
} */
Statement::Update {
table,
assignments,
@ -217,6 +211,7 @@ impl SqlProxy {
or: *or,
};
}
Statement::Delete(del_stmt) => {
let table_name = self.extract_table_name_from_from(&del_stmt.from);
if let Some(name) = table_name {
@ -232,6 +227,7 @@ impl SqlProxy {
});
}
}
Statement::AlterTable { name, .. } => {
if self.is_audited_table(name) {
return Ok(Some(

View File

@ -1,32 +1,129 @@
use rusqlite::{Connection, Result, Row};
use crate::table_names::{TABLE_CRDT_CONFIGS, TABLE_CRDT_LOGS};
use rusqlite::{Connection, Result as RusqliteResult, Row, Transaction};
use serde::Serialize;
use std::fmt::Write;
use std::error::Error;
use std::fmt::{self, Display, Formatter, Write};
use std::panic::{self, AssertUnwindSafe};
use ts_rs::TS;
// the z_ prefix should make sure that these triggers are executed lasts
// Die "z_"-Präfix soll sicherstellen, dass diese Trigger als Letzte ausgeführt werden
const INSERT_TRIGGER_TPL: &str = "z_crdt_{TABLE_NAME}_insert";
const UPDATE_TRIGGER_TPL: &str = "z_crdt_{TABLE_NAME}_update";
pub const LOG_TABLE_NAME: &str = "haex_crdt_logs";
const SYNC_ACTIVE_KEY: &str = "sync_active";
pub const TOMBSTONE_COLUMN: &str = "haex_tombstone";
pub const HLC_TIMESTAMP_COLUMN: &str = "haex_hlc_timestamp";
#[derive(Debug)]
pub enum CrdtSetupError {
/// Kapselt einen Fehler, der von der rusqlite-Bibliothek kommt.
DatabaseError(rusqlite::Error),
/// Die Tabelle hat keine Tombstone-Spalte, was eine CRDT-Voraussetzung ist.
TombstoneColumnMissing {
table_name: String,
column_name: String,
},
/// Die Tabelle hat keinen Primärschlüssel, was eine CRDT-Voraussetzung ist.
PrimaryKeyMissing { table_name: String },
}
// Implementierung, damit unser Error-Typ schön formatiert werden kann.
impl Display for CrdtSetupError {
fn fmt(&self, f: &mut Formatter<'_>) -> fmt::Result {
match self {
CrdtSetupError::DatabaseError(e) => write!(f, "Database error: {}", e),
CrdtSetupError::TombstoneColumnMissing {
table_name,
column_name,
} => write!(
f,
"Table '{}' is missing the required tombstone column '{}'",
table_name, column_name
),
CrdtSetupError::PrimaryKeyMissing { table_name } => {
write!(f, "Table '{}' has no primary key", table_name)
}
}
}
}
// Implementierung, damit unser Typ als "echter" Error erkannt wird.
impl Error for CrdtSetupError {}
// Wichtige Konvertierung: Erlaubt uns, den `?`-Operator auf Funktionen zu verwenden,
// die `rusqlite::Error` zurückgeben. Der Fehler wird automatisch in unseren
// `CrdtSetupError::DatabaseError` verpackt.
impl From<rusqlite::Error> for CrdtSetupError {
fn from(err: rusqlite::Error) -> Self {
CrdtSetupError::DatabaseError(err)
}
}
#[derive(Debug, Serialize, TS)]
#[ts(export)]
#[serde(tag = "status", content = "details")]
pub enum TriggerSetupResult {
Success,
TableNotFound,
TombstoneColumnMissing { column_name: String },
PrimaryKeyMissing,
}
fn set_sync_active(conn: &mut Connection) -> RusqliteResult<()> {
let sql = format!(
"INSERT OR REPLACE INTO \"{meta_table}\" (key, value) VALUES (?, '1');",
meta_table = TABLE_CRDT_CONFIGS
);
conn.execute(&sql, [SYNC_ACTIVE_KEY])?;
Ok(())
}
fn clear_sync_active(conn: &mut Connection) -> RusqliteResult<()> {
let sql = format!(
"DELETE FROM \"{meta_table}\" WHERE key = ?;",
meta_table = TABLE_CRDT_CONFIGS
);
conn.execute(&sql, [SYNC_ACTIVE_KEY])?;
Ok(())
}
/// Führt eine Aktion aus, während die Trigger temporär deaktiviert sind.
/// Diese Funktion stellt sicher, dass die Trigger auch bei einem Absturz (Panic)
/// wieder aktiviert werden.
pub fn with_triggers_paused<F, R>(conn: &mut Connection, action: F) -> RusqliteResult<R>
where
F: FnOnce(&mut Connection) -> RusqliteResult<R>,
{
set_sync_active(conn)?;
// AssertUnwindSafe wird benötigt, um den Mutex über eine Panic-Grenze hinweg zu verwenden.
// Wir fangen einen möglichen Panic in `action` ab.
let result = panic::catch_unwind(AssertUnwindSafe(|| action(conn)));
// Diese Aktion MUSS immer ausgeführt werden, egal ob `action` erfolgreich war oder nicht.
clear_sync_active(conn)?;
match result {
Ok(res) => res, // Alles gut, gib das Ergebnis von `action` zurück.
Err(e) => panic::resume_unwind(e), // Ein Panic ist aufgetreten, wir geben ihn weiter, nachdem wir aufgeräumt haben.
}
}
/// Erstellt die benötigte Meta-Tabelle, falls sie nicht existiert.
pub fn setup_meta_table(conn: &mut Connection) -> RusqliteResult<()> {
let sql = format!(
"CREATE TABLE IF NOT EXISTS \"{meta_table}\" (key TEXT PRIMARY KEY, value TEXT) WITHOUT ROWID;",
meta_table = TABLE_CRDT_CONFIGS
);
conn.execute(&sql, [])?;
Ok(())
}
#[derive(Debug)]
struct ColumnInfo {
name: String,
is_pk: bool,
}
impl ColumnInfo {
fn from_row(row: &Row) -> Result<Self> {
fn from_row(row: &Row) -> RusqliteResult<Self> {
Ok(ColumnInfo {
name: row.get("name")?,
is_pk: row.get::<_, i64>("pk")? > 0,
@ -34,145 +131,260 @@ impl ColumnInfo {
}
}
pub struct TriggerManager;
fn is_safe_identifier(name: &str) -> bool {
!name.is_empty() && name.chars().all(|c| c.is_alphanumeric() || c == '_')
}
impl TriggerManager {
pub fn new() -> Self {
TriggerManager {}
/// Richtet CRDT-Trigger für eine einzelne Tabelle ein.
pub fn setup_triggers_for_table(
conn: &mut Connection,
table_name: &str,
) -> Result<TriggerSetupResult, CrdtSetupError> {
if !is_safe_identifier(table_name) {
return Err(rusqlite::Error::InvalidParameterName(format!(
"Invalid or unsafe table name provided: {}",
table_name
))
.into());
}
pub fn setup_triggers_for_table(
&self,
conn: &mut Connection,
table_name: &str,
) -> Result<TriggerSetupResult, rusqlite::Error> {
let columns = self.get_table_schema(conn, table_name)?;
let columns = get_table_schema(conn, table_name)?;
if columns.is_empty() {
return Ok(TriggerSetupResult::TableNotFound);
}
if !columns.iter().any(|c| c.name == TOMBSTONE_COLUMN) {
return Ok(TriggerSetupResult::TombstoneColumnMissing {
column_name: TOMBSTONE_COLUMN.to_string(),
});
}
let pks: Vec<String> = columns
.iter()
.filter(|c| c.is_pk)
.map(|c| c.name.clone())
.collect();
if pks.is_empty() {
return Ok(TriggerSetupResult::PrimaryKeyMissing);
}
let cols_to_track: Vec<String> = columns
.iter()
.filter(|c| !c.is_pk && c.name != TOMBSTONE_COLUMN)
.map(|c| c.name.clone())
.collect();
let insert_trigger_sql = self.generate_insert_trigger_sql(table_name, &pks, &cols_to_track);
let update_trigger_sql = self.generate_update_trigger_sql(table_name, &pks, &cols_to_track);
let drop_insert_trigger_sql = self.drop_trigger_sql(table_name, "insert");
let tx = conn.transaction()?;
tx.execute_batch(&format!("{}\n{}", insert_trigger_sql, update_trigger_sql))?;
tx.commit()?;
Ok(TriggerSetupResult::Success)
if columns.is_empty() {
return Ok(TriggerSetupResult::TableNotFound);
}
fn get_table_schema(&self, conn: &Connection, table_name: &str) -> Result<Vec<ColumnInfo>> {
let sql = format!("PRAGMA table_info('{}');", table_name);
let mut stmt = conn.prepare(&sql)?;
let rows = stmt.query_map([], ColumnInfo::from_row)?;
rows.collect()
}
fn generate_insert_trigger_sql(
&self,
table_name: &str,
pks: &[String],
cols: &[String],
) -> String {
let pk_json_payload = pks
.iter()
.map(|pk| format!("'{}', NEW.\"{}\"", pk, pk))
.collect::<Vec<_>>()
.join(", ");
let column_inserts = cols.iter().fold(String::new(), |mut acc, col| {
writeln!(&mut acc, "INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pk, column_name, value) VALUES (NEW.\"{hlc_col}\", 'INSERT', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"));",
log_table = LOG_TABLE_NAME,
hlc_col = HLC_TIMESTAMP_COLUMN,
table = table_name,
pk_payload = pk_json_payload,
column = col
).unwrap();
acc
if !columns.iter().any(|c| c.name == TOMBSTONE_COLUMN) {
return Err(CrdtSetupError::TombstoneColumnMissing {
table_name: table_name.to_string(),
column_name: TOMBSTONE_COLUMN.to_string(),
});
}
// Verwende die neue Konstante für den Trigger-Namen
let trigger_name = INSERT_TRIGGER_TPL.replace("{TABLE_NAME}", table_name);
let pks: Vec<String> = columns
.iter()
.filter(|c| c.is_pk)
.map(|c| c.name.clone())
.collect();
format!(
"CREATE TRIGGER IF NOT EXISTS {trigger_name}
if pks.is_empty() {
return Err(CrdtSetupError::PrimaryKeyMissing {
table_name: table_name.to_string(),
});
}
let cols_to_track: Vec<String> = columns
.iter()
.filter(|c| !c.is_pk && c.name != TOMBSTONE_COLUMN && c.name != HLC_TIMESTAMP_COLUMN)
.map(|c| c.name.clone())
.collect();
let insert_trigger_sql = generate_insert_trigger_sql(table_name, &pks, &cols_to_track);
let update_trigger_sql = generate_update_trigger_sql(table_name, &pks, &cols_to_track);
let sql_batch = format!("{}\n{}", insert_trigger_sql, update_trigger_sql);
// Führe die Erstellung innerhalb einer Transaktion aus
let tx = conn.transaction()?;
tx.execute_batch(&sql_batch)?;
tx.commit()?;
Ok(TriggerSetupResult::Success)
}
/// Holt das Schema für eine gegebene Tabelle.
/// WICHTIG: Dies ist eine private Hilfsfunktion. Sie geht davon aus, dass `table_name`
/// bereits vom öffentlichen Aufrufer (setup_triggers_for_table) validiert wurde.
fn get_table_schema(conn: &Connection, table_name: &str) -> RusqliteResult<Vec<ColumnInfo>> {
let sql = format!("PRAGMA table_info(\"{}\");", table_name);
let mut stmt = conn.prepare(&sql)?;
let rows = stmt.query_map([], ColumnInfo::from_row)?;
rows.collect()
}
pub fn drop_triggers_for_table(
tx: &Transaction, // Arbeitet direkt auf einer Transaktion
table_name: &str,
) -> Result<(), CrdtSetupError> {
if !is_safe_identifier(table_name) {
return Err(rusqlite::Error::InvalidParameterName(format!(
"Invalid or unsafe table name provided: {}",
table_name
))
.into());
}
let drop_insert_trigger_sql =
drop_trigger_sql(INSERT_TRIGGER_TPL.replace("{TABLE_NAME}", table_name));
let drop_update_trigger_sql =
drop_trigger_sql(UPDATE_TRIGGER_TPL.replace("{TABLE_NAME}", table_name));
let sql_batch = format!("{}\n{}", drop_insert_trigger_sql, drop_update_trigger_sql);
tx.execute_batch(&sql_batch)?;
Ok(())
}
pub fn recreate_triggers_for_table(
conn: &mut Connection,
table_name: &str,
) -> Result<TriggerSetupResult, CrdtSetupError> {
// Starte eine einzige Transaktion für beide Operationen
let tx = conn.transaction()?;
// 1. Rufe die Drop-Funktion auf
drop_triggers_for_table(&tx, table_name)?;
// 2. Erstelle die Trigger neu (vereinfachte Logik ohne Drop)
// Wir rufen die `setup_triggers_for_table` Logik hier manuell nach,
// um die Transaktion weiterzuverwenden.
let columns = get_table_schema(&tx, table_name)?;
if columns.is_empty() {
tx.commit()?; // Wichtig: Transaktion beenden
return Ok(TriggerSetupResult::TableNotFound);
}
// ... (Validierungslogik wiederholen) ...
if !columns.iter().any(|c| c.name == TOMBSTONE_COLUMN) {
/* ... */
return Err(CrdtSetupError::TombstoneColumnMissing {
table_name: table_name.to_string(),
column_name: TOMBSTONE_COLUMN.to_string(),
});
}
let pks: Vec<String> = columns
.iter()
.filter(|c| c.is_pk)
.map(|c| c.name.clone())
.collect();
if pks.is_empty() {
/* ... */
return Err(CrdtSetupError::PrimaryKeyMissing {
table_name: table_name.to_string(),
});
}
let cols_to_track: Vec<String> = columns
.iter()
.filter(|c| !c.is_pk && c.name != TOMBSTONE_COLUMN && c.name != HLC_TIMESTAMP_COLUMN)
.map(|c| c.name.clone())
.collect();
let insert_trigger_sql = generate_insert_trigger_sql(table_name, &pks, &cols_to_track);
let update_trigger_sql = generate_update_trigger_sql(table_name, &pks, &cols_to_track);
let sql_batch = format!("{}\n{}", insert_trigger_sql, update_trigger_sql);
tx.execute_batch(&sql_batch)?;
// Beende die Transaktion
tx.commit()?;
Ok(TriggerSetupResult::Success)
}
/// Generiert das SQL für den INSERT-Trigger.
fn generate_insert_trigger_sql(table_name: &str, pks: &[String], cols: &[String]) -> String {
let pk_json_payload = pks
.iter()
.map(|pk| format!("'{}', NEW.\"{}\"", pk, pk))
.collect::<Vec<_>>()
.join(", ");
let column_inserts = cols.iter().fold(String::new(), |mut acc, col| {
writeln!(&mut acc, " INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pk, column_name, value) VALUES (NEW.\"{hlc_col}\", 'INSERT', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"));",
log_table = TABLE_CRDT_LOGS,
hlc_col = HLC_TIMESTAMP_COLUMN,
table = table_name,
pk_payload = pk_json_payload,
column = col
).unwrap();
acc
});
let trigger_name = INSERT_TRIGGER_TPL.replace("{TABLE_NAME}", table_name);
format!(
"CREATE TRIGGER IF NOT EXISTS \"{trigger_name}\"
AFTER INSERT ON \"{table_name}\"
WHEN (SELECT value FROM \"{config_table}\" WHERE key = '{sync_key}') IS NOT '1'
FOR EACH ROW
BEGIN
{column_inserts}
END;"
)
}
END;",
config_table = TABLE_CRDT_CONFIGS,
sync_key = SYNC_ACTIVE_KEY
)
}
fn drop_trigger_sql(&self, table: &str, action: &str) -> String {
format!("DROP TRIGGER IF EXISTS z_crdt_{table}_{action};")
}
/// Generiert das SQL zum Löschen eines Triggers.
fn drop_trigger_sql(trigger_name: String) -> String {
format!("DROP TRIGGER IF EXISTS \"{}\";", trigger_name)
}
fn generate_update_trigger_sql(
&self,
table_name: &str,
pks: &[String],
cols: &[String],
) -> String {
let pk_json_payload = pks
.iter()
.map(|pk| format!("'{}', NEW.\"{}\"", pk, pk))
.collect::<Vec<_>>()
.join(", ");
/// Generiert das SQL für den UPDATE-Trigger.
fn generate_update_trigger_sql(table_name: &str, pks: &[String], cols: &[String]) -> String {
let pk_json_payload = pks
.iter()
.map(|pk| format!("'{}', NEW.\"{}\"", pk, pk))
.collect::<Vec<_>>()
.join(", ");
let column_updates = cols.iter().fold(String::new(), |mut acc, col| {
writeln!(&mut acc, "IF NEW.\"{column}\" IS NOT OLD.\"{column}\" THEN INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pk, column_name, value, old_value) VALUES (NEW.\"{hlc_col}\", 'UPDATE', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"), json_object('value', OLD.\"{column}\")); END IF;",
log_table = LOG_TABLE_NAME,
hlc_col = HLC_TIMESTAMP_COLUMN,
table = table_name,
pk_payload = pk_json_payload,
column = col).unwrap();
acc
});
let soft_delete_logic = format!(
"IF NEW.{tombstone_col} = 1 AND OLD.{tombstone_col} = 0 THEN INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pk) VALUES (NEW.\"{hlc_col}\", 'DELETE', '{table}', json_object({pk_payload})); END IF;",
log_table = LOG_TABLE_NAME,
let column_updates = cols.iter().fold(String::new(), |mut acc, col| {
writeln!(&mut acc, " IF NEW.\"{column}\" IS NOT OLD.\"{column}\" THEN INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pk, column_name, value, old_value) VALUES (NEW.\"{hlc_col}\", 'UPDATE', '{table}', json_object({pk_payload}), '{column}', json_object('value', NEW.\"{column}\"), json_object('value', OLD.\"{column}\")); END IF;",
log_table = TABLE_CRDT_LOGS,
hlc_col = HLC_TIMESTAMP_COLUMN,
tombstone_col = TOMBSTONE_COLUMN,
table = table_name,
pk_payload = pk_json_payload
);
pk_payload = pk_json_payload,
column = col
).unwrap();
acc
});
// Verwende die neue Konstante für den Trigger-Namen
let trigger_name = UPDATE_TRIGGER_TPL.replace("{TABLE_NAME}", table_name);
let soft_delete_logic = format!(
" IF NEW.\"{tombstone_col}\" = 1 AND OLD.\"{tombstone_col}\" = 0 THEN INSERT INTO {log_table} (hlc_timestamp, op_type, table_name, row_pk) VALUES (NEW.\"{hlc_col}\", 'DELETE', '{table}', json_object({pk_payload})); END IF;",
log_table = TABLE_CRDT_LOGS,
hlc_col = HLC_TIMESTAMP_COLUMN,
tombstone_col = TOMBSTONE_COLUMN,
table = table_name,
pk_payload = pk_json_payload
);
format!(
"CREATE TRIGGER IF NOT EXISTS {trigger_name}
let trigger_name = UPDATE_TRIGGER_TPL.replace("{TABLE_NAME}", table_name);
format!(
"CREATE TRIGGER IF NOT EXISTS \"{trigger_name}\"
AFTER UPDATE ON \"{table_name}\"
WHEN (SELECT value FROM \"{config_table}\" WHERE key = '{sync_key}') IS NOT '1'
FOR EACH ROW
BEGIN
{column_updates}
{soft_delete_logic}
END;"
)
}
{column_updates}
{soft_delete_logic}
END;",
config_table = TABLE_CRDT_CONFIGS,
sync_key = SYNC_ACTIVE_KEY
)
}
/// Durchläuft alle `haex_`-Tabellen und richtet die CRDT-Trigger ein.
pub fn generate_haex_triggers(conn: &mut Connection) -> Result<(), rusqlite::Error> {
println!("🔄 Setup CRDT triggers...");
let table_names: Vec<String> = {
let mut stmt = conn.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name LIKE 'haex_%' AND name NOT LIKE 'haex_crdt_%';")?;
let rows = stmt.query_map([], |row| row.get::<_, String>(0))?;
rows.collect::<RusqliteResult<Vec<String>>>()?
};
for table_name in table_names {
if table_name == TABLE_CRDT_CONFIGS {
continue;
}
println!("➡️ Processing table: {}", table_name);
match setup_triggers_for_table(conn, &table_name) {
Ok(TriggerSetupResult::Success) => {
println!(" ✅ Triggers created for {}", table_name)
}
Ok(TriggerSetupResult::TableNotFound) => {
println!(" Table {} not found, skipping.", table_name)
}
Err(e) => println!(" ❌ Could not set up triggers for {}: {}", table_name, e),
}
}
println!("✨ Done setting up CRDT triggers.");
Ok(())
}

View File

@ -1,5 +1,5 @@
// database/core.rs
use crate::crdt::hlc;
use crate::database::DbConnection;
use base64::{engine::general_purpose::STANDARD, Engine as _};
use rusqlite::{
@ -166,7 +166,7 @@ pub fn open_and_init_db(path: &str, key: &str, create: bool) -> Result<Connectio
let conn = Connection::open_with_flags(path, flags).map_err(|e| {
format!(
"Dateiii gibt es nicht: {}. Habe nach {} gesucht",
"Datei gibt es nicht: {}. Habe nach {} gesucht",
e.to_string(),
path
)
@ -174,8 +174,8 @@ pub fn open_and_init_db(path: &str, key: &str, create: bool) -> Result<Connectio
conn.pragma_update(None, "key", key)
.map_err(|e| e.to_string())?;
conn.execute_batch("SELECT count(*) from haex_extensions")
.map_err(|e| e.to_string())?;
/* conn.execute_batch("SELECT count(*) from haex_extensions")
.map_err(|e| e.to_string())?; */
let journal_mode: String = conn
.query_row("PRAGMA journal_mode=WAL;", [], |row| row.get(0))

View File

@ -5,10 +5,10 @@ use rusqlite::Connection;
use serde_json::Value as JsonValue;
use std::fs;
use std::path::{Path, PathBuf};
use std::path::Path;
use std::str::FromStr;
use std::sync::{Arc, Mutex};
use tauri::{path::BaseDirectory, AppHandle, Manager, State, Wry};
use tauri::{path::BaseDirectory, AppHandle, Manager, State};
use crate::database::core::open_and_init_db;
pub struct HlcService(pub Mutex<uhlc::HLC>);

View File

@ -1,14 +1,19 @@
//mod browser;
mod android_storage;
//mod android_storage;
pub mod crdt;
mod database;
mod extension;
mod models;
pub mod table_names {
include!(concat!(env!("OUT_DIR"), "/tableNames.rs"));
}
use database::DbConnection;
use models::ExtensionState;
use rusqlite::{Connection, OpenFlags};
use std::sync::{Arc, Mutex};
use crate::database::DbConnection;
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
let protocol_name = "haex-extension";
@ -67,9 +72,9 @@ pub fn run() {
extension::copy_directory,
extension::database::extension_sql_execute,
extension::database::extension_sql_select,
android_storage::request_storage_permission,
/* android_storage::request_storage_permission,
android_storage::has_storage_permission,
android_storage::get_external_storage_paths,
android_storage::get_external_storage_paths, */
])
.run(tauri::generate_context!())
.expect("error while running tauri application");