use std::{ fs::File, io::{Error, Read}, path::Path, str::FromStr, }; use crate::{config::_get_client_id, get_basepath}; use futures::FutureExt; use serde::{Deserialize, Serialize}; use sqlx::{migrate::MigrateDatabase, sqlite::SqliteConnectOptions, Sqlite, SqlitePool}; use tauri::{ipc::RuntimeCapability, App, AssetResolver, Manager, Url}; struct DBFileEntry { id: u64, relative_file_path: String, file_name: String, } #[derive(Debug, Serialize, Deserialize)] pub enum DBError { DatabaseLocationError(String), DatabaseConnectionError(String), DatabaseQueryError(String), } fn get_database_path(app_handle: &tauri::AppHandle) -> Result { if let Some(basepath) = get_basepath(app_handle.clone()) { let db_path = Path::new(&basepath).join("db.sqlite"); let resolved_db_path = match app_handle .path() .resolve(db_path, tauri::path::BaseDirectory::Home) { Ok(resolved_knowledgebase_path) => resolved_knowledgebase_path, Err(e) => return Err(DBError::DatabaseLocationError(e.to_string())), }; let unicode_db_path = resolved_db_path.to_string_lossy().as_ref().to_string(); return Ok(unicode_db_path); } Err(DBError::DatabaseLocationError( "No knowledgebase defined".to_string(), )) } #[tauri::command] pub async fn initialize_database(app_handle: tauri::AppHandle) -> Result<(), DBError> { let unicode_db_path = get_database_path(&app_handle)?; if !Sqlite::database_exists(&unicode_db_path) .await .unwrap_or(false) { match Sqlite::create_database(&unicode_db_path).await { Ok(_) => println!("Create db success"), Err(error) => panic!("error: {}", error), } } let db = SqlitePool::connect(&unicode_db_path) .await .map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?; let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS files ( id INTEGER PRIMARY KEY NOT NULL, relative_path TEXT NOT NULL, file_name TEXT NOT NULL );", ) .execute(&db) .await .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?; let _ = sqlx::query( "CREATE TABLE IF NOT EXISTS file_diffs ( id INTEGER NOT NULL, client_id TEXT NOT NULL, diff_text TEXT NOT NULL, current_diff BOOLEAN, parent_diff_id INTEGER, file_id INTEGER, FOREIGN KEY (parent_diff_id) REFERENCES file_diffs(id), FOREIGN KEY (file_id) REFERENCES files(id), PRIMARY KEY (id, client_id) ) ;", ) .execute(&db) .await .unwrap(); // .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?; db.close().await; Ok(()) } async fn get_file_id( app_handle: &tauri::AppHandle, relative_file_path: &str, file_name: &str, ) -> Result { let unicode_db_path = get_database_path(&app_handle)?; let db = SqlitePool::connect(&unicode_db_path) .await .map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?; let (id, _, _): (i32, String, String) = sqlx::query_as(&format!( " SELECT * FROM files WHERE relative_path IS '{relative_file_path}' AND file_name IS '{file_name}' " )) .fetch_one(&db) .await .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?; db.close().await; Ok(id) } pub async fn add_file( app_handle: &tauri::AppHandle, relative_file_path: &str, file_name: &str, ) -> Result<(), DBError> { if get_file_id(app_handle, relative_file_path, file_name) .await .is_ok() { return Ok(()); } let unicode_db_path = get_database_path(&app_handle)?; let db = SqlitePool::connect(&unicode_db_path) .await .map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?; let id = sqlx::query(&format!( " INSERT INTO files (relative_path, file_name) VALUES ('{relative_file_path}', '{file_name}');" )) .execute(&db) .await .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?; db.close().await; Ok(()) } async fn get_last_file_diff( app_handle: &tauri::AppHandle, relative_file_path: &str, file_name: &str, ) -> Result { let unicode_db_path = get_database_path(&app_handle)?; let db = SqlitePool::connect(&unicode_db_path) .await .map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?; let (parent_diff_id, _, _, _, _, _): (i32, String, String, bool, i32, i32) = sqlx::query_as(&format!( " SELECT * FROM file_diffs WHERE relative_path IS '{relative_file_path}' AND file_name IS '{file_name}' AND working_diff is 1 " )) .fetch_one(&db) .await .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?; db.close().await; Ok(parent_diff_id) } #[tauri::command] pub async fn store_diff( app_handle: &tauri::AppHandle, relative_file_path: &str, file_name: &str, diff: String, ) -> Result<(), DBError> { let unicode_db_path = get_database_path(&app_handle)?; let client_id = _get_client_id(&app_handle); let db = SqlitePool::connect(&unicode_db_path) .await .map_err(|e| DBError::DatabaseConnectionError(e.to_string()))?; // 1 insert into file_diffs with parent // 2 make last diff not currennt diff // id INTEGER NOT NULL, // client_id TEXT NOT NULL, // diff_text TEXT NOT NULL, // current_diff BOOLEAN, // parent_diff_id INTEGER, // file_id INTEGER, // FOREIGN KEY (parent_diff_id) REFERENCES file_diffs(id), // FOREIGN KEY (file_id) REFERENCES files(id), // PRIMARY KEY (id, client_id) let file_id = get_file_id(app_handle, relative_file_path, file_name).await?; let parent_diff_id: i32 = sqlx::query_scalar(&format!( " SELECT id from file_diffs WHERE relative_path IS '{relative_file_path}' AND file_name IS '{file_name}' AND working_diff is 1 " )) .fetch_one(&db) .await .unwrap_or(0); let _ = sqlx::query(&format!( " UPDATE file_diffs SET current_diff = 0 WHERE relative_path IS '{relative_file_path}' AND file_name IS '{file_name}' AND working_diff is 1 " )) .execute(&db) .await .map_err(|e| DBError::DatabaseQueryError(e.to_string())); let _ = sqlx::query(&format!( " INSERT INTO file_diffs (client_id, diff_text, current_diff, file_id) VALUES ('{client_id}', '{diff}', 1, {parent_diff_id});" )) .execute(&db) .await .map_err(|e| DBError::DatabaseQueryError(e.to_string()))?; db.close().await; Ok(()) }