Files
gridline/src-tauri/src/lib.rs
T
adrianbonpin 32a7b852ec v0.7.8: MySQL/SQLite backup-sync, Excel export, query cancel, settings import/export, Windows title-bar fix, SQLite table editor (#16)
* [P1-T1] feat(backup): MySQL/SQLite backup models + db_type on SyncOptions (Task 1.1)

* [P1-T2] feat: enable tools(mysql,sqlite) + tableManagement(sqlite) + add fflate (Task 1.2)

* [P1-T3] feat(cancel): CancelHandle enum + CancelRegistry (Task 1.3)

* [P2-T1] feat(export): hand-rolled XLSX writer with inline-string cells (Task 2.1)

* [P2-T2] feat(backup): SQLite .dump/restore/sync core, fail-closed virtual tables (Task 2.2)

* [P2-T3] feat(backup): MySQL dump/restore/sync arg builders + tool resolution (Task 2.3)

* [P2-T4] feat(settings): pure import validator + SettingsExport envelope + Store.apply_settings (Task 2.4)

* [P2-T5] feat(table-editor): SQLite create/diff/rebuild SQL generation, fail-closed AUTOINCREMENT (Task 2.5)

* [P3-T1] feat(commands): MySQL/SQLite backup + settings export/import commands + wrappers (Task 3.1)

* [P3-T2] feat(cancel): capture cancel primitives at connect; cancel_query command; SQLite interrupt test (Task 3.2)

* [P3-T3] feat(table-editor): SQLite object-change dispatch + execute_change Ddl/RebuildTable (Task 3.3)

* [P4-T1] feat(tools): DB-aware backup/restore/sync pages (Task 4.1)

* [P4-T2] feat(export): xlsx export in grid toolbar + overflow menu (Task 4.2)

* [P4-T3] feat(query): cancel button wired to cancelQuery (Task 4.3)

* [P4-T4] feat(settings): export/import buttons + validation gate (Task 4.4)

* [P4-T5] fix(ui): gate macOS overlay drag strip to macOS only (Task 4.5)

* [P4-T6] feat(table-editor): SQLite Create/Edit Table mode (Task 4.6)

* [P5-T1] chore: bump 0.7.7 -> 0.7.8 + README/AGENTS/ROADMAP status (Task 5.1)

* [P5-T2] build(release): bundle mariadb-dump + mariadb client (system-first fallback) (Task 5.2)

* fix(cancel): propagate cancellations past wrapped->raw fallback (SQLite/PG/MySQL) + MySQL CONNECTION_ID cast

* fix(export): Excel export from overflow menu did nothing + add export success/error toasts

* fix(export): tree kebab export fetches table data when rows not loaded

* docs(readme): surface v0.7.8 features (MySQL/SQLite backup-sync, Excel export, query cancel, SQLite table editor, settings import/export)
2026-08-08 00:09:36 +08:00

187 lines
7.1 KiB
Rust

// Infrastructure modules: types, introspection, and DB viewer commands are built ahead
// of runtime usage, producing expected dead_code/unused warnings during development.
#![allow(dead_code)]
mod cancel;
mod commands;
mod db;
mod models;
mod store;
use commands::ssh::{Ssh2Backend, SshTunnelManager};
use db::pool::ConnectionPoolManager;
use std::sync::{Arc, Mutex as StdMutex};
use store::Store;
use tauri::Manager;
pub struct AppState {
pub db_store: StdMutex<Store>,
pub pool_manager: tokio::sync::Mutex<ConnectionPoolManager>,
pub ssh_manager: StdMutex<SshTunnelManager>,
pub cancel_registry: crate::cancel::CancelRegistry,
}
use commands::{
backup, connections, db_viewer, demo, folders, import_export, keychain, maintenance, objects, query,
schema_graph, settings, tags,
};
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
#[tauri::command]
fn greet(name: &str) -> String {
format!("Hello, {}! You've been greeted from Rust!", name)
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
// Install the ring crypto provider so rustls `ClientConfig::builder()` works (no-op if
// another provider is already installed).
let _ = rustls::crypto::ring::default_provider().install_default();
let store = Store::open("gridline.db").expect("failed to open db");
let store_ref = StdMutex::new(store);
tauri::Builder::default()
.plugin(tauri_plugin_opener::init())
.plugin(tauri_plugin_fs::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_keyring_store::init())
.manage(AppState {
db_store: store_ref,
pool_manager: tokio::sync::Mutex::new(ConnectionPoolManager::new()),
ssh_manager: StdMutex::new(SshTunnelManager::new(Arc::new(Ssh2Backend))),
cancel_registry: crate::cancel::CancelRegistry::default(),
})
.setup(move |app| {
let state = app.state::<AppState>();
demo::ensure_demo_db(app.handle(), &state.db_store)
.map_err(|e| {
eprintln!("Failed to set up demo DB: {e}");
})
.ok();
// Close the SSH tunnel for a connection when its pool is evicted
// (LRU overflow or max-pool shrink). The hook captures a clone of
// the app handle and resolves AppState through the manager.
let handle = app.handle().clone();
state
.pool_manager
.blocking_lock()
.set_on_evict(Box::new(move |id: &str| {
if let Some(s) = handle.try_state::<AppState>() {
if let Ok(mut mgr) = s.ssh_manager.lock() {
mgr.close_tunnel(id);
}
// Drop the cancel handles for the evicted connection
// (tokens/interrupts outlive the pool otherwise).
s.cancel_registry.remove(id);
}
}));
Ok(())
})
.invoke_handler(tauri::generate_handler![
greet,
connections::get_connections,
connections::create_connection,
connections::update_connection,
connections::delete_connection,
connections::add_connection_tags,
connections::set_connection_favorite,
connections::record_recent_connection,
connections::get_recent_connections,
connections::clear_recent_connections,
folders::get_folders,
folders::create_folder,
folders::delete_folder,
folders::update_folder,
folders::add_folder_tags,
tags::get_tags,
tags::create_tag,
tags::delete_tag,
tags::update_tag,
settings::get_settings,
settings::update_setting,
import_export::import_connections,
import_export::export_connections,
commands::test_connection::test_connection,
db_viewer::db_connect,
db_viewer::db_disconnect,
db_viewer::get_databases,
db_viewer::get_schemas,
db_viewer::get_tables,
db_viewer::get_table_data,
db_viewer::get_table_columns,
db_viewer::get_fk_preview,
db_viewer::execute_change,
db_viewer::get_table_ddl,
db_viewer::refresh_connection,
db_viewer::get_functions,
db_viewer::get_triggers,
db_viewer::get_sequences,
db_viewer::get_enums,
db_viewer::get_extensions,
db_viewer::get_indexes,
db_viewer::get_constraints,
objects::create_schema,
objects::rename_schema,
objects::drop_schema,
objects::search_objects,
objects::get_object_ddl,
objects::get_object_dependencies,
objects::build_object_ddl,
objects::build_rebuild_script,
objects::get_available_extensions,
objects::get_roles, objects::get_role_privileges, objects::get_table_rebuild_readiness, objects::get_tablespaces,
maintenance::run_maintenance,
keychain::save_connection_password,
keychain::get_connection_password,
keychain::delete_connection_password,
keychain::save_connection_ssh_password,
keychain::get_connection_ssh_password,
keychain::delete_connection_ssh_password,
keychain::save_connection_ssh_passphrase,
keychain::get_connection_ssh_passphrase,
keychain::delete_connection_ssh_passphrase,
demo::recreate_demo_db,
demo::regenerate_demo_db,
backup::detect_pg_tools,
backup::pg_dump,
backup::pg_restore,
backup::db_sync,
backup::detect_mysql_tools,
backup::mysql_dump,
backup::mysql_restore,
backup::mysql_sync,
backup::sqlite_dump,
backup::sqlite_restore,
backup::sqlite_sync,
settings::export_settings,
settings::import_settings,
schema_graph::get_schema_graph,
query::execute_query,
query::cancel_query,
query::get_query_history,
query::clear_query_history,
query::set_history_favorite,
query::save_query,
query::get_saved_queries,
query::update_saved_query,
query::delete_saved_query,
])
.build(tauri::generate_context!())
.expect("error while building tauri application")
.run(|app_handle, event| {
// Close all SSH tunnels on exit: ExitRequested fires before the
// event loop ends, Exit fires after it has.
if matches!(
event,
tauri::RunEvent::ExitRequested { .. } | tauri::RunEvent::Exit
) {
if let Ok(mut mgr) = app_handle.state::<AppState>().ssh_manager.lock() {
mgr.close_all();
}
}
});
}