mirror of
https://github.com/haexhub/haex-hub.git
synced 2025-12-16 22:20:51 +01:00
adjust for mobile
This commit is contained in:
@ -4,12 +4,17 @@ pub mod core;
|
||||
pub mod error;
|
||||
|
||||
use rusqlite::Connection;
|
||||
use serde::{Deserialize, Serialize};
|
||||
use serde_json::Value as JsonValue;
|
||||
use std::collections::HashMap;
|
||||
use std::path::Path;
|
||||
use std::sync::Mutex;
|
||||
use std::time::UNIX_EPOCH;
|
||||
use std::{fs, sync::Arc};
|
||||
use tauri::{path::BaseDirectory, AppHandle, Manager, State};
|
||||
use tauri_plugin_fs::FsExt;
|
||||
use thiserror::Error;
|
||||
use ts_rs::TS;
|
||||
|
||||
use crate::crdt::hlc::HlcService;
|
||||
use crate::database::error::DatabaseError;
|
||||
@ -17,6 +22,9 @@ use crate::table_names::TABLE_CRDT_CONFIGS;
|
||||
use crate::AppState;
|
||||
pub struct DbConnection(pub Arc<Mutex<Option<Connection>>>);
|
||||
|
||||
const VAULT_EXTENSION: &str = ".db";
|
||||
const VAULT_DIRECTORY: &str = "vaults";
|
||||
|
||||
#[tauri::command]
|
||||
pub fn sql_select(
|
||||
sql: String,
|
||||
@ -35,97 +43,251 @@ pub fn sql_execute(
|
||||
core::execute(sql, params, &state.db)
|
||||
}
|
||||
|
||||
/// Resolves a database name to the full vault path
|
||||
fn get_vault_path(app_handle: &AppHandle, vault_name: &str) -> Result<String, DatabaseError> {
|
||||
// Sicherstellen, dass der Name eine .db Endung hat
|
||||
let vault_file_name = if vault_name.ends_with(VAULT_EXTENSION) {
|
||||
vault_name.to_string()
|
||||
} else {
|
||||
format!("{}{VAULT_EXTENSION}", vault_name)
|
||||
};
|
||||
|
||||
let vault_directory = get_vaults_directory(app_handle)?;
|
||||
|
||||
let vault_path = app_handle
|
||||
.path()
|
||||
.resolve(
|
||||
format!("{vault_directory}/{}", vault_file_name),
|
||||
BaseDirectory::AppLocalData,
|
||||
)
|
||||
.map_err(|e| DatabaseError::PathResolutionError {
|
||||
reason: format!(
|
||||
"Failed to resolve vault path for '{}': {}",
|
||||
vault_file_name, e
|
||||
),
|
||||
})?;
|
||||
|
||||
// Sicherstellen, dass das vaults-Verzeichnis existiert
|
||||
if let Some(parent) = vault_path.parent() {
|
||||
fs::create_dir_all(parent).map_err(|e| DatabaseError::IoError {
|
||||
path: parent.display().to_string(),
|
||||
reason: format!("Failed to create vaults directory: {}", e),
|
||||
})?;
|
||||
}
|
||||
|
||||
Ok(vault_path.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
/// Returns the vaults directory path
|
||||
#[tauri::command]
|
||||
pub fn get_vaults_directory(app_handle: &AppHandle) -> Result<String, DatabaseError> {
|
||||
let vaults_dir = app_handle
|
||||
.path()
|
||||
.resolve(VAULT_DIRECTORY, BaseDirectory::AppLocalData)
|
||||
.map_err(|e| DatabaseError::PathResolutionError {
|
||||
reason: e.to_string(),
|
||||
})?;
|
||||
|
||||
Ok(vaults_dir.to_string_lossy().to_string())
|
||||
}
|
||||
|
||||
//#[serde(tag = "type", content = "details")]
|
||||
#[derive(Debug, Serialize, Deserialize, TS)]
|
||||
#[ts(export)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct VaultInfo {
|
||||
name: String,
|
||||
last_access: u64,
|
||||
path: String,
|
||||
}
|
||||
|
||||
/// Lists all vault databases in the vaults directory
|
||||
#[tauri::command]
|
||||
pub fn list_vaults(app_handle: AppHandle) -> Result<Vec<VaultInfo>, DatabaseError> {
|
||||
let vaults_dir_str = get_vaults_directory(&app_handle)?;
|
||||
let vaults_dir = Path::new(&vaults_dir_str);
|
||||
|
||||
println!("Suche vaults in {}", vaults_dir.display());
|
||||
|
||||
let mut vaults: Vec<VaultInfo> = vec![];
|
||||
|
||||
if !vaults_dir.exists() {
|
||||
println!("Vaults-Verzeichnis existiert nicht, gebe leere Liste zurück.");
|
||||
return Ok(vec![]);
|
||||
}
|
||||
|
||||
for entry in fs::read_dir(vaults_dir).map_err(|e| DatabaseError::IoError {
|
||||
path: "vaults directory".to_string(),
|
||||
reason: e.to_string(),
|
||||
})? {
|
||||
let entry = entry.map_err(|e| DatabaseError::IoError {
|
||||
path: "vaults directory entry".to_string(),
|
||||
reason: e.to_string(),
|
||||
})?;
|
||||
|
||||
println!("Suche entry {}", entry.path().to_string_lossy());
|
||||
let path = entry.path();
|
||||
if path.is_file() {
|
||||
if let Some(filename) = path.file_name().and_then(|n| n.to_str()) {
|
||||
if filename.ends_with(VAULT_EXTENSION) {
|
||||
// Entferne .db Endung für die Rückgabe
|
||||
println!("Vault gefunden {}", filename.to_string());
|
||||
|
||||
let metadata = fs::metadata(&path).map_err(|e| DatabaseError::IoError {
|
||||
path: path.to_string_lossy().to_string(),
|
||||
reason: format!("Metadaten konnten nicht gelesen werden: {}", e),
|
||||
})?;
|
||||
|
||||
let last_access_timestamp = metadata
|
||||
.accessed()
|
||||
.map_err(|e| DatabaseError::IoError {
|
||||
path: path.to_string_lossy().to_string(),
|
||||
reason: format!("Zugriffszeit konnte nicht gelesen werden: {}", e),
|
||||
})?
|
||||
.duration_since(UNIX_EPOCH)
|
||||
.unwrap_or_default() // Fallback für den seltenen Fall einer Zeit vor 1970
|
||||
.as_secs();
|
||||
|
||||
let vault_name = filename.trim_end_matches(VAULT_EXTENSION).to_string();
|
||||
|
||||
vaults.push(VaultInfo {
|
||||
name: vault_name,
|
||||
last_access: last_access_timestamp,
|
||||
path: path.to_string_lossy().to_string(),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(vaults)
|
||||
}
|
||||
|
||||
/// Checks if a vault with the given name exists
|
||||
#[tauri::command]
|
||||
pub fn vault_exists(app_handle: AppHandle, db_name: String) -> Result<bool, DatabaseError> {
|
||||
let vault_path = get_vault_path(&app_handle, &db_name)?;
|
||||
Ok(Path::new(&vault_path).exists())
|
||||
}
|
||||
|
||||
/// Deletes a vault database file
|
||||
#[tauri::command]
|
||||
pub fn delete_vault(app_handle: AppHandle, db_name: String) -> Result<String, DatabaseError> {
|
||||
let vault_path = get_vault_path(&app_handle, &db_name)?;
|
||||
|
||||
if !Path::new(&vault_path).exists() {
|
||||
return Err(DatabaseError::IoError {
|
||||
path: vault_path,
|
||||
reason: "Vault does not exist".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
fs::remove_file(&vault_path).map_err(|e| DatabaseError::IoError {
|
||||
path: vault_path.clone(),
|
||||
reason: format!("Failed to delete vault: {}", e),
|
||||
})?;
|
||||
|
||||
Ok(format!("Vault '{}' successfully deleted", db_name))
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn create_encrypted_database(
|
||||
app_handle: AppHandle,
|
||||
path: String,
|
||||
vault_name: String,
|
||||
key: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<String, DatabaseError> {
|
||||
// Ressourcenpfad zur eingebundenen Datenbank auflösen
|
||||
println!("Creating encrypted vault with name: {}", vault_name);
|
||||
|
||||
println!("Arbeitsverzeichnis: {:?}", std::env::current_dir());
|
||||
println!(
|
||||
"Ressourcenverzeichnis: {:?}",
|
||||
app_handle.path().resource_dir()
|
||||
);
|
||||
let vault_path = get_vault_path(&app_handle, &vault_name)?;
|
||||
println!("Resolved vault path: {}", vault_path);
|
||||
|
||||
// Prüfen, ob bereits eine Vault mit diesem Namen existiert
|
||||
if Path::new(&vault_path).exists() {
|
||||
return Err(DatabaseError::IoError {
|
||||
path: vault_path,
|
||||
reason: format!("A vault with the name '{}' already exists", vault_name),
|
||||
});
|
||||
}
|
||||
/* let resource_path = app_handle
|
||||
.path()
|
||||
.resolve("database/vault.db", BaseDirectory::Resource)
|
||||
.map_err(|e| format!("Fehler beim Auflösen des Ressourcenpfads: {}", e))?; */
|
||||
|
||||
let resource_path = app_handle
|
||||
let template_path = app_handle
|
||||
.path()
|
||||
.resolve("database/vault.db", BaseDirectory::Resource)
|
||||
.map_err(|e| DatabaseError::PathResolutionError {
|
||||
reason: format!("Failed to resolve template database: {}", e),
|
||||
})?;
|
||||
|
||||
let template_content =
|
||||
app_handle
|
||||
.fs()
|
||||
.read(&template_path)
|
||||
.map_err(|e| DatabaseError::IoError {
|
||||
path: template_path.display().to_string(),
|
||||
reason: format!("Failed to read template database from resources: {}", e),
|
||||
})?;
|
||||
|
||||
let temp_path = app_handle
|
||||
.path()
|
||||
.resolve("temp_vault.db", BaseDirectory::AppLocalData)
|
||||
.map_err(|e| DatabaseError::PathResolutionError {
|
||||
reason: e.to_string(),
|
||||
reason: format!("Failed to resolve temp database: {}", e),
|
||||
})?;
|
||||
|
||||
// Prüfen, ob die Ressourcendatei existiert
|
||||
if !resource_path.exists() {
|
||||
let temp_path_clone = temp_path.to_owned();
|
||||
fs::write(temp_path, template_content).map_err(|e| DatabaseError::IoError {
|
||||
path: vault_path.to_string(),
|
||||
reason: format!("Failed to write temporary template database: {}", e),
|
||||
})?;
|
||||
/* if !template_path.exists() {
|
||||
return Err(DatabaseError::IoError {
|
||||
path: resource_path.display().to_string(),
|
||||
reason: "Ressourcendatenbank wurde nicht gefunden.".to_string(),
|
||||
path: template_path.display().to_string(),
|
||||
reason: "Template database not found in resources".to_string(),
|
||||
});
|
||||
}
|
||||
|
||||
// Sicherstellen, dass das Zielverzeichnis existiert
|
||||
/* if let Some(parent) = Path::new(&path).parent() {
|
||||
if !parent.exists() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| {
|
||||
format!(
|
||||
"Fehler beim Erstellen des Zielverzeichnisses: {}\n mit Fehler {}",
|
||||
path, e
|
||||
)
|
||||
})?;
|
||||
}
|
||||
} */
|
||||
|
||||
let target = Path::new(&path);
|
||||
if target.exists() & target.is_file() {
|
||||
fs::remove_file(target).map_err(|e| DatabaseError::IoError {
|
||||
path: target.display().to_string(),
|
||||
reason: format!("Bestehende Zieldatei konnte nicht gelöscht werden: {}", e),
|
||||
})?;
|
||||
}
|
||||
|
||||
println!(
|
||||
"Öffne unverschlüsselte Datenbank: {}",
|
||||
resource_path.as_path().display()
|
||||
);
|
||||
|
||||
let conn = Connection::open(&resource_path).map_err(|e| DatabaseError::ConnectionFailed {
|
||||
path: resource_path.display().to_string(),
|
||||
println!("Öffne Temp-Datenbank direkt: {}", temp_path_clone.display());
|
||||
let conn = Connection::open(&temp_path_clone).map_err(|e| DatabaseError::ConnectionFailed {
|
||||
path: temp_path_clone.display().to_string(),
|
||||
reason: format!(
|
||||
"Fehler beim Öffnen der unverschlüsselten Quelldatenbank: {}",
|
||||
e
|
||||
),
|
||||
})?;
|
||||
|
||||
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| DatabaseError::ExecutionError {
|
||||
sql: "ATTACH DATABASE ...".to_string(),
|
||||
reason: e.to_string(),
|
||||
table: None,
|
||||
})?;
|
||||
|
||||
println!(
|
||||
"Exportiere Daten von 'main' nach 'encrypted' mit password {} ...",
|
||||
&key
|
||||
"Hänge neue, verschlüsselte Datenbank an unter '{}'",
|
||||
&vault_path
|
||||
);
|
||||
// ATTACH DATABASE 'Dateiname' AS Alias KEY 'Passwort';
|
||||
conn.execute(
|
||||
"ATTACH DATABASE ?1 AS encrypted KEY ?2;",
|
||||
[&vault_path, &key],
|
||||
)
|
||||
.map_err(|e| DatabaseError::ExecutionError {
|
||||
sql: "ATTACH DATABASE ...".to_string(),
|
||||
reason: e.to_string(),
|
||||
table: None,
|
||||
})?;
|
||||
|
||||
println!("Exportiere Daten von 'main' nach 'encrypted' ...");
|
||||
|
||||
if let Err(e) = conn.query_row("SELECT sqlcipher_export('encrypted');", [], |_| Ok(())) {
|
||||
// Versuche aufzuräumen, ignoriere Fehler dabei
|
||||
let _ = conn.execute("DETACH DATABASE encrypted;", []);
|
||||
// Lösche auch die eventuell teilweise erstellte Datei
|
||||
let _ = fs::remove_file(&vault_path);
|
||||
let _ = fs::remove_file(&temp_path_clone);
|
||||
return Err(DatabaseError::QueryError {
|
||||
reason: format!("Fehler während sqlcipher_export: {}", e),
|
||||
});
|
||||
}
|
||||
|
||||
println!("Löse die verschlüsselte Datenbank vom Handle...");
|
||||
|
||||
conn.execute("DETACH DATABASE encrypted;", [])
|
||||
.map_err(|e| DatabaseError::ExecutionError {
|
||||
sql: "DETACH DATABASE ...".to_string(),
|
||||
@ -133,13 +295,12 @@ pub fn create_encrypted_database(
|
||||
table: None,
|
||||
})?;
|
||||
|
||||
println!("Datenbank erfolgreich nach '{}' verschlüsselt.", &path);
|
||||
println!(
|
||||
"Die Originaldatei '{}' ist unverändert.",
|
||||
resource_path.as_path().display()
|
||||
"Datenbank erfolgreich nach '{}' verschlüsselt.",
|
||||
&vault_path
|
||||
);
|
||||
|
||||
// 2. VERSUCHEN, EINE SQLCIPHER-SPEZIFISCHE OPERATION AUSZUFÜHREN
|
||||
// SQLCipher-Verifizierung
|
||||
println!("Prüfe SQLCipher-Aktivität mit 'PRAGMA cipher_version;'...");
|
||||
match conn.query_row("PRAGMA cipher_version;", [], |row| {
|
||||
let version: String = row.get(0)?;
|
||||
@ -155,62 +316,42 @@ pub fn create_encrypted_database(
|
||||
}
|
||||
}
|
||||
|
||||
println!("resource_path: {}", resource_path.display());
|
||||
|
||||
conn.close()
|
||||
.map_err(|(_, e)| DatabaseError::ConnectionFailed {
|
||||
path: resource_path.display().to_string(),
|
||||
path: template_path.display().to_string(),
|
||||
reason: format!("Fehler beim Schließen der Quelldatenbank: {}", e),
|
||||
})?;
|
||||
|
||||
initialize_session(&app_handle, &path, &key, &state)?;
|
||||
let _ = fs::remove_file(&temp_path_clone);
|
||||
|
||||
/* let new_conn = core::open_and_init_db(&path, &key, false)?;
|
||||
initialize_session(&app_handle, &vault_path, &key, &state)?;
|
||||
|
||||
// Aktualisieren der Datenbankverbindung im State
|
||||
let mut db = state.db.0.lock().map_err(|e| DatabaseError::LockError {
|
||||
reason: e.to_string(),
|
||||
})?;
|
||||
|
||||
*db = Some(new_conn); */
|
||||
|
||||
Ok(format!("Verschlüsselte CRDT-Datenbank erstellt",))
|
||||
Ok(vault_path)
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn open_encrypted_database(
|
||||
app_handle: AppHandle,
|
||||
path: String,
|
||||
vault_path: String,
|
||||
key: String,
|
||||
state: State<'_, AppState>,
|
||||
) -> Result<String, DatabaseError> {
|
||||
if !Path::new(&path).exists() {
|
||||
println!("Opening encrypted database vault_path: {}", vault_path);
|
||||
|
||||
// Vault-Pfad aus dem Namen ableiten
|
||||
//let vault_path = get_vault_path(&app_handle, &vault_name)?;
|
||||
println!("Resolved vault path: {}", vault_path);
|
||||
|
||||
if !Path::new(&vault_path).exists() {
|
||||
return Err(DatabaseError::IoError {
|
||||
path,
|
||||
reason: "Database file not found.".to_string(),
|
||||
path: vault_path.to_string(),
|
||||
reason: format!("Vault '{}' does not exist", vault_path),
|
||||
});
|
||||
}
|
||||
/* let vault_path = app_handle
|
||||
.path()
|
||||
.resolve(format!("vaults/{}", path), BaseDirectory::AppLocalData)
|
||||
.map_err(|e| format!("Fehler {}", e))?
|
||||
.into_os_string()
|
||||
.into_string()
|
||||
.unwrap(); */
|
||||
/* if !std::path::Path::new(&path).exists() {
|
||||
return Err(format!("File not found {}", path).into());
|
||||
} */
|
||||
|
||||
/* let conn = core::open_and_init_db(&path, &key, false)
|
||||
.map_err(|e| format!("Error during open: {}", e))?;
|
||||
initialize_session(&app_handle, &vault_path, &key, &state)?;
|
||||
|
||||
let mut db = state.db.0.lock().map_err(|e| e.to_string())?;
|
||||
|
||||
*db = Some(conn); */
|
||||
|
||||
initialize_session(&app_handle, &path, &key, &state)?;
|
||||
|
||||
Ok(format!("success"))
|
||||
Ok(format!("Vault '{}' opened successfully", vault_path))
|
||||
}
|
||||
|
||||
/// Opens the DB, initializes the HLC service, and stores both in the AppState.
|
||||
|
||||
@ -78,11 +78,14 @@ pub fn run() {
|
||||
//.plugin(tauri_plugin_android_fs::init())
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
database::create_encrypted_database,
|
||||
database::delete_vault,
|
||||
database::list_vaults,
|
||||
database::open_encrypted_database,
|
||||
database::sql_execute,
|
||||
database::sql_select,
|
||||
extension::database::extension_sql_select,
|
||||
database::vault_exists,
|
||||
extension::database::extension_sql_execute,
|
||||
extension::database::extension_sql_select,
|
||||
//database::update_hlc_from_remote,
|
||||
/* extension::copy_directory,
|
||||
extension::database::extension_sql_select, */
|
||||
|
||||
Reference in New Issue
Block a user