zwischenstand

This commit is contained in:
Martin Drechsel
2025-05-06 11:09:56 +02:00
parent 410a885d21
commit b729c8ebbe
40 changed files with 2252 additions and 376 deletions

View File

@ -1,13 +1,74 @@
// database/core.rs
use crate::database::DbConnection;
use rusqlite::{Connection, OpenFlags};
use serde_json::json;
use base64::{engine::general_purpose::STANDARD, Engine as _};
use rusqlite::{
types::{Value as RusqliteValue, ValueRef},
Connection, OpenFlags, ToSql,
};
use serde_json::Value as JsonValue;
use std::fs;
use std::path::Path;
use tauri::State;
// --- Hilfsfunktion: Konvertiert JSON Value zu etwas, das rusqlite versteht ---
// Diese Funktion ist etwas knifflig wegen Ownership und Lifetimes.
// Eine einfachere Variante ist oft, direkt rusqlite::types::Value zu erstellen.
// Hier ein Beispiel, das owned Values erstellt (braucht evtl. Anpassung je nach rusqlite-Version/Nutzung)
fn json_to_rusqlite_value(json_val: &JsonValue) -> Result<RusqliteValue, String> {
match json_val {
JsonValue::Null => Ok(RusqliteValue::Null),
JsonValue::Bool(b) => Ok(RusqliteValue::Integer(*b as i64)), // SQLite hat keinen BOOLEAN
JsonValue::Number(n) => {
if let Some(i) = n.as_i64() {
Ok(RusqliteValue::Integer(i))
} else if let Some(f) = n.as_f64() {
Ok(RusqliteValue::Real(f))
} else {
Err("Ungültiger Zahlenwert".to_string())
}
}
JsonValue::String(s) => Ok(RusqliteValue::Text(s.clone())),
JsonValue::Array(_) | JsonValue::Object(_) => {
// SQLite kann Arrays/Objects nicht direkt speichern (außer als TEXT/BLOB)
// Konvertiere sie zu JSON-Strings, wenn das gewünscht ist
Ok(RusqliteValue::Text(
serde_json::to_string(json_val).map_err(|e| e.to_string())?,
))
// Oder gib einen Fehler zurück, wenn Arrays/Objekte nicht erlaubt sind
// Err("Arrays oder Objekte werden nicht direkt als Parameter unterstützt".to_string())
}
}
}
// --- Tauri Command für INSERT/UPDATE/DELETE ---
#[tauri::command]
pub async fn execute(
sql: String,
params: Vec<JsonValue>,
state: &State<'_, DbConnection>,
) -> Result<usize, String> {
// Gibt Anzahl betroffener Zeilen zurück
let params_converted: Vec<RusqliteValue> = params
.iter()
.map(json_to_rusqlite_value)
.collect::<Result<Vec<_>, _>>()?;
let params_sql: Vec<&dyn ToSql> = params_converted.iter().map(|v| v as &dyn ToSql).collect();
let db_lock = state
.0
.lock()
.map_err(|e| format!("Mutex Lock Fehler: {}", e))?;
let conn = db_lock.as_ref().ok_or("Keine Datenbankverbindung")?;
let affected_rows = conn
.execute(&sql, &params_sql[..])
.map_err(|e| format!("SQL Execute Fehler: {}", e))?;
Ok(affected_rows)
}
/// Führt SQL-Schreiboperationen (INSERT, UPDATE, DELETE, CREATE) ohne Berechtigungsprüfung aus
pub async fn execute(
/* pub async fn execute(
sql: &str,
params: &[String],
state: &State<'_, DbConnection>,
@ -26,14 +87,107 @@ pub async fn execute(
"last_insert_id": last_id
}))
.map_err(|e| format!("JSON-Serialisierungsfehler: {}", e))?)
} */
#[tauri::command]
pub async fn select(
sql: String,
params: Vec<JsonValue>, // Parameter als JSON Values empfangen
state: &State<'_, DbConnection>,
) -> Result<Vec<Vec<JsonValue>>, String> {
// Ergebnis als Vec<RowObject>
// Konvertiere JSON Params zu rusqlite Values für die Abfrage
// Wir sammeln sie als owned Values, da `params_from_iter` Referenzen braucht,
// was mit lokalen Konvertierungen schwierig ist.
let params_converted: Vec<RusqliteValue> = params
.iter()
.map(json_to_rusqlite_value)
.collect::<Result<Vec<_>, _>>()?; // Sammle Ergebnisse, gibt Fehler weiter
// Konvertiere zu Slice von ToSql-Referenzen (erfordert, dass die Values leben)
let params_sql: Vec<&dyn ToSql> = params_converted.iter().map(|v| v as &dyn ToSql).collect();
// Zugriff auf die Verbindung (blockierend, okay für SQLite in vielen Fällen)
let db_lock = state
.0
.lock()
.map_err(|e| format!("Mutex Lock Fehler: {}", e))?;
let conn = db_lock.as_ref().ok_or("Keine Datenbankverbindung")?;
let mut stmt = conn
.prepare(&sql)
.map_err(|e| format!("SQL Prepare Fehler: {}", e))?;
let column_names: Vec<String> = stmt
.column_names()
.into_iter()
.map(|s| s.to_string())
.collect();
let num_columns = column_names.len();
let mut rows = stmt
.query(&params_sql[..])
.map_err(|e| format!("SQL Query Fehler: {}", e))?;
let mut result_vec: Vec<Vec<JsonValue>> = Vec::new();
println!();
println!();
println!();
println!();
while let Some(row) = rows.next().map_err(|e| format!("Row Next Fehler: {}", e))? {
//let mut row_map = HashMap::new();
let mut row_data: Vec<JsonValue> = Vec::with_capacity(num_columns);
for i in 0..num_columns {
let col_name = &column_names[i];
println!(
"--- Processing Column --- Index: {}, Name: '{}'",
i, col_name
);
let value_ref = row
.get_ref(i)
.map_err(|e| format!("Get Ref Fehler Spalte {}: {}", i, e))?;
// Wandle rusqlite ValueRef zurück zu serde_json Value
let json_val = match value_ref {
ValueRef::Null => JsonValue::Null,
ValueRef::Integer(i) => JsonValue::Number(i.into()),
ValueRef::Real(f) => JsonValue::Number(
serde_json::Number::from_f64(f).unwrap_or(serde_json::Number::from(0)),
), // Fallback für NaN/Infinity
ValueRef::Text(t) => {
let s = String::from_utf8_lossy(t).to_string();
// Versuche, als JSON zu parsen, falls es ursprünglich ein Array/Objekt war
//serde_json::from_str(&s).unwrap_or(JsonValue::String(s))
JsonValue::String(s)
}
ValueRef::Blob(b) => {
// BLOBs z.B. als Base64-String zurückgeben
JsonValue::String(STANDARD.encode(b))
}
};
println!(
"new row: name: {} with value: {}",
column_names[i].clone(),
json_val,
);
row_data.push(json_val);
//row_map.insert(column_names[i].clone(), json_val);
}
//result_vec.push(row_map);
result_vec.push(row_data);
}
Ok(result_vec)
}
/// Führt SQL-Leseoperationen (SELECT) ohne Berechtigungsprüfung aus
pub async fn select(
/* pub async fn select(
sql: &str,
params: &[String],
state: &State<'_, DbConnection>,
) -> Result<Vec<Vec<String>>, String> {
) -> Result<Vec<Vec<Option<String>>>, String> {
let db = state.0.lock().map_err(|e| format!("Mutex-Fehler: {}", e))?;
let conn = db.as_ref().ok_or("Keine Datenbankverbindung vorhanden")?;
@ -52,16 +206,20 @@ pub async fn select(
{
let mut row_data = Vec::new();
for i in 0..columns {
let value: String = row
let value = row
.get(i)
.map_err(|e| format!("Datentypfehler in Spalte {}: {}", i, e))?;
row_data.push(value);
}
/* println!(
"Select Row Data: {}",
&row_data.clone().join("").to_string()
); */
result.push(row_data);
}
Ok(result)
}
} */
/// Öffnet und initialisiert eine Datenbank mit Verschlüsselung
pub fn open_and_init_db(path: &str, key: &str, create: bool) -> Result<Connection, String> {
@ -75,6 +233,9 @@ 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())?;
Ok(conn)
}

View File

@ -1,30 +1,30 @@
// database/mod.rs
pub mod core;
use rusqlite::{Connection, OpenFlags};
use rusqlite::Connection;
use serde_json::Value as JsonValue;
use std::path::Path;
use std::sync::Mutex;
use tauri::{path::BaseDirectory, AppHandle, Manager, State};
pub struct DbConnection(pub Mutex<Option<Connection>>);
// Öffentliche Funktionen für direkten Datenbankzugriff
#[tauri::command]
pub async fn sql_select(
sql: String,
params: Vec<String>,
params: Vec<JsonValue>,
state: State<'_, DbConnection>,
) -> Result<Vec<Vec<String>>, String> {
core::select(&sql, &params, &state).await
) -> Result<Vec<Vec<JsonValue>>, String> {
core::select(sql, params, &state).await
}
#[tauri::command]
pub async fn sql_execute(
sql: String,
params: Vec<String>,
params: Vec<JsonValue>,
state: State<'_, DbConnection>,
) -> Result<String, String> {
core::execute(&sql, &params, &state).await
) -> Result<usize, String> {
core::execute(sql, params, &state).await
}
/// Erstellt eine verschlüsselte Datenbank
@ -58,7 +58,62 @@ pub fn create_encrypted_database(
}
}
// Neue Datenbank erstellen
//core::copy_file(&resource_path, &path)?;
println!(
"Öffne unverschlüsselte Datenbank: {}",
resource_path.as_path().display()
);
let conn = Connection::open(&resource_path).map_err(|e| {
format!(
"Fehler beim Öffnen der kopierten Datenbank: {}",
e.to_string()
)
})?;
//let conn = Connection::open(&resource_path)?;
println!("Hänge neue, verschlüsselte Datenbank an unter '{}'", &path);
// ATTACH DATABASE 'Dateiname' AS Alias KEY 'Passwort';
conn.execute("ATTACH DATABASE ?1 AS encrypted KEY ?2;", [&path, &key])
.map_err(|e| format!("Fehler bei ATTACH DATABASE: {}", e.to_string()))?;
println!(
"Exportiere Daten von 'main' nach 'encrypted' mit password {} ...",
&key
);
match conn.query_row("SELECT sqlcipher_export('encrypted');", [], |_row| Ok(())) {
Ok(_) => {
println!(">>> sqlcipher_export erfolgreich ausgeführt (Rückgabewert ignoriert).");
}
Err(e) => {
eprintln!("!!! FEHLER während sqlcipher_export: {}", e);
conn.execute("DETACH DATABASE encrypted;", []).ok(); // Versuche zu detachen
return Err(e.to_string()); // Gib den Fehler zurück
}
}
// sqlcipher_export('Alias') kopiert Schema und Daten von 'main' zur Alias-DB
/* conn.execute("SELECT sqlcipher_export('encrypted');", [])
.map_err(|e| {
format!(
"Fehler bei SELECT sqlcipher_export('encrypted'): {}",
e.to_string()
)
})?; */
println!("Löse die verschlüsselte Datenbank vom Handle...");
conn.execute("DETACH DATABASE encrypted;", [])
.map_err(|e| format!("Fehler bei DETACH DATABASE: {}", e.to_string()))?;
println!("Datenbank erfolgreich nach '{}' verschlüsselt.", &path);
println!(
"Die Originaldatei '{}' ist unverändert.",
resource_path.as_path().display()
);
/* // Neue Datenbank erstellen
let conn = Connection::open_with_flags(
&path,
OpenFlags::SQLITE_OPEN_READ_WRITE | OpenFlags::SQLITE_OPEN_CREATE,
@ -77,7 +132,7 @@ pub fn create_encrypted_database(
"Fehler beim Testen der verschlüsselten Datenbank: {}",
e.to_string()
));
}
} */
// 2. VERSUCHEN, EINE SQLCIPHER-SPEZIFISCHE OPERATION AUSZUFÜHREN
println!("Prüfe SQLCipher-Aktivität mit 'PRAGMA cipher_version;'...");
@ -88,7 +143,7 @@ pub fn create_encrypted_database(
Ok(version) => {
println!("SQLCipher ist aktiv! Version: {}", version);
// Fahre mit normalen Operationen fort
/* // Fahre mit normalen Operationen fort
println!("Erstelle Tabelle 'benutzer'...");
conn.execute(
"CREATE TABLE benutzer (id INTEGER PRIMARY KEY, name TEXT NOT NULL)",
@ -100,7 +155,7 @@ pub fn create_encrypted_database(
.map_err(|e| {
format!("Fehler beim Verschlüsseln der Datenbank: {}", e.to_string())
})?;
println!("Benutzer hinzugefügt.");
println!("Benutzer hinzugefügt."); */
}
Err(e) => {
eprintln!("FEHLER: SQLCipher scheint NICHT aktiv zu sein!");