319 lines
14 KiB
Rust
319 lines
14 KiB
Rust
// file: crates/ksp-app-raw-transaction-ingest-desk/src/tauri.rs
|
|
// version: 9
|
|
|
|
//! Tauri runtime assembly for the KSP Raw Transaction Ingest desktop application.
|
|
|
|
use tauri::Emitter; // rust-rules: trait-import
|
|
use tauri::Manager; // rust-rules: trait-import
|
|
|
|
/// Runs the Raw Transaction Ingest desktop application.
|
|
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
|
pub fn run(arguments: &[std::ffi::OsString]) -> ksp_core_lib::Result<()> {
|
|
let context = tauri::generate_context!();
|
|
let runtime_layout = configure_packaged_runtime(&context);
|
|
if let std::result::Result::Err(error) = runtime_layout {
|
|
return std::result::Result::Err(error);
|
|
}
|
|
let app_state = crate::AppState::initialize(arguments);
|
|
let app_state = match app_state {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let mut builder = tauri::Builder::default();
|
|
builder = builder.manage(app_state);
|
|
builder = builder.plugin(tauri_plugin_tracing::Builder::new().build::<tauri::Wry>());
|
|
builder = configure_commands(builder);
|
|
builder = configure_window_events(builder);
|
|
builder = builder.setup(|app| {
|
|
let splash = crate::require_splash_window(app);
|
|
if let std::result::Result::Err(error) = splash {
|
|
return std::result::Result::Err(std::boxed::Box::new(error));
|
|
}
|
|
let main = crate::require_main_window(app);
|
|
if let std::result::Result::Err(error) = main {
|
|
return std::result::Result::Err(std::boxed::Box::new(error));
|
|
}
|
|
ksp_logging_lib::debug!(target: crate::TRACING_TARGET, domain = crate::TRACING_DOMAIN_SHELL, "Raw Transaction Ingest Desk Tauri shell setup completed");
|
|
return std::result::Result::Ok(());
|
|
});
|
|
let run_result = builder.run(context);
|
|
return match run_result {
|
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
|
std::result::Result::Err(error) => std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot run KSP Raw Transaction Ingest Desk Tauri runtime")
|
|
.with_context("tauri_error", error.to_string()),
|
|
),
|
|
};
|
|
}
|
|
|
|
fn configure_packaged_runtime(context: &tauri::Context<tauri::Wry>) -> ksp_core_lib::Result<()> {
|
|
if cfg!(debug_assertions) {
|
|
return std::result::Result::Ok(());
|
|
}
|
|
let resource_root = tauri::utils::platform::resource_dir(context.package_info(), &tauri::Env::default());
|
|
let resource_root = match resource_root {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot resolve packaged Tauri resource directory")
|
|
.with_context("tauri_error", error.to_string()),
|
|
);
|
|
},
|
|
};
|
|
let layout = ksp_config_lib::prepare_packaged_runtime(resource_root.as_path());
|
|
let layout = match layout {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => return std::result::Result::Err(error),
|
|
};
|
|
let working_directory = std::env::set_current_dir(layout.runtime_root());
|
|
return match working_directory {
|
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
|
std::result::Result::Err(error) => std::result::Result::Err(
|
|
ksp_core_lib::Error::new(crate::ERROR_CODE_TAURI_RUNTIME_FAILED, "Cannot activate packaged KSP runtime directory")
|
|
.with_context("runtime_root", layout.runtime_root().to_string_lossy().into_owned())
|
|
.with_source(error),
|
|
),
|
|
};
|
|
}
|
|
|
|
#[allow(clippy::question_mark_used)] // Tauri generates the question-mark operator internally for async command dispatch.
|
|
fn configure_commands(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
|
return builder.invoke_handler(tauri::generate_handler![
|
|
emit_frontend_log,
|
|
get_route_foundation,
|
|
get_route_inventory,
|
|
get_route_monitoring,
|
|
get_runtime_status,
|
|
splash_frontend_ready,
|
|
start_route,
|
|
stop_route,
|
|
validate_route_start
|
|
]);
|
|
}
|
|
|
|
fn configure_window_events(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<tauri::Wry> {
|
|
return builder.on_window_event(|window, event| {
|
|
if window.label() != "main" {
|
|
return;
|
|
}
|
|
if let tauri::WindowEvent::CloseRequested { api, .. } = event {
|
|
api.prevent_close();
|
|
let app_handle = window.app_handle().clone();
|
|
let state = app_handle.state::<crate::AppState>();
|
|
let begin = state.begin_shutdown();
|
|
let begin = match begin {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
ksp_logging_lib::warn!(
|
|
target: crate::TRACING_TARGET,
|
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
|
error_domain = error.code().domain(),
|
|
error_code = error.code().code(),
|
|
"Raw Transaction Ingest Desk could not enter graceful shutdown; exiting after safe error projection"
|
|
);
|
|
app_handle.exit(1);
|
|
return;
|
|
},
|
|
};
|
|
if !begin {
|
|
return;
|
|
}
|
|
ksp_logging_lib::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
|
"Raw Transaction Ingest Desk main-window close requested; route Start admission closed and cooperative shutdown requested"
|
|
);
|
|
tauri::async_runtime::spawn(async move {
|
|
let state = app_handle.state::<crate::AppState>();
|
|
let shutdown = state.shutdown_routes().await;
|
|
match shutdown {
|
|
std::result::Result::Ok(()) => {
|
|
ksp_logging_lib::debug!(
|
|
target: crate::TRACING_TARGET,
|
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
|
"Raw Transaction Ingest Desk route and shared Store shutdown completed; exiting application"
|
|
);
|
|
app_handle.exit(0);
|
|
},
|
|
std::result::Result::Err(error) => {
|
|
ksp_logging_lib::warn!(
|
|
target: crate::TRACING_TARGET,
|
|
domain = crate::TRACING_DOMAIN_WINDOWS,
|
|
error_domain = error.code().domain(),
|
|
error_code = error.code().code(),
|
|
"Raw Transaction Ingest Desk bounded route shutdown failed; exiting application after bounded cleanup attempt"
|
|
);
|
|
app_handle.exit(1);
|
|
},
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
fn project_command_error(command: &'static str, domain: &'static str, error: &ksp_core_lib::Error) -> crate::CommandErrorDto {
|
|
ksp_logging_lib::warn!(
|
|
target: crate::TRACING_TARGET,
|
|
domain = domain,
|
|
command = command,
|
|
error_domain = error.code().domain(),
|
|
error_code = error.code().code(),
|
|
"Raw Transaction Ingest Desk Tauri command failed"
|
|
);
|
|
return crate::CommandErrorDto::from_error(error);
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn emit_frontend_log(payload: crate::FrontendLogPayloadDto) -> std::result::Result<(), crate::CommandErrorDto> {
|
|
let result = crate::emit_frontend_log_event(payload);
|
|
return match result {
|
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
|
std::result::Result::Err(error) => std::result::Result::Err(project_command_error("emit_frontend_log", crate::TRACING_DOMAIN_FRONTEND, &error)),
|
|
};
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn get_route_foundation(state: tauri::State<'_, crate::AppState>) -> crate::RawIngestRouteFoundationDto {
|
|
return state.route_foundation();
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn get_route_inventory(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::RawIngestRouteInventoryDto, crate::CommandErrorDto> {
|
|
let result = state.route_inventory();
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(project_command_error("get_route_inventory", crate::TRACING_DOMAIN_INVENTORY, &error)),
|
|
};
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn get_route_monitoring(
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<std::vec::Vec<crate::RawIngestRouteMonitoringDto>, crate::CommandErrorDto> {
|
|
let result = state.route_monitoring();
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(project_command_error("get_route_monitoring", crate::TRACING_DOMAIN_ROUTE_RUNTIME, &error)),
|
|
};
|
|
}
|
|
|
|
#[tauri::command]
|
|
fn get_runtime_status(state: tauri::State<'_, crate::AppState>) -> std::result::Result<crate::ShellStatusDto, crate::CommandErrorDto> {
|
|
let result = state.shell_status();
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(project_command_error("get_runtime_status", crate::TRACING_DOMAIN_SHELL, &error)),
|
|
};
|
|
}
|
|
|
|
#[tauri::command]
|
|
async fn splash_frontend_ready(
|
|
app: tauri::AppHandle,
|
|
webview_window: tauri::WebviewWindow,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<(), crate::CommandErrorDto> {
|
|
let result = crate::splash_frontend_ready_service(app, webview_window, &state).await;
|
|
return match result {
|
|
std::result::Result::Ok(()) => std::result::Result::Ok(()),
|
|
std::result::Result::Err(error) => std::result::Result::Err(project_command_error("splash_frontend_ready", crate::TRACING_DOMAIN_WINDOWS, &error)),
|
|
};
|
|
}
|
|
|
|
#[tauri::command]
|
|
async fn start_route(
|
|
app: tauri::AppHandle,
|
|
request: crate::RawIngestRouteStartRequestDto,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<crate::RawIngestRouteRuntimeDto, crate::CommandErrorDto> {
|
|
let launch = state.start_route(&request).await;
|
|
let launch = match launch {
|
|
std::result::Result::Ok(value) => value,
|
|
std::result::Result::Err(error) => {
|
|
return std::result::Result::Err(project_command_error("start_route", crate::TRACING_DOMAIN_ROUTE_RUNTIME, &error));
|
|
},
|
|
};
|
|
let acknowledgement = launch.acknowledgement();
|
|
let monitoring_identity = launch.monitoring_identity();
|
|
let monitoring_source = launch.monitoring_source();
|
|
let monitoring_app = app.clone();
|
|
let monitoring_task = tauri::async_runtime::spawn(async move {
|
|
monitor_route_status(monitoring_app, monitoring_identity, monitoring_source).await;
|
|
});
|
|
std::mem::drop(monitoring_task);
|
|
let terminal_task = tauri::async_runtime::spawn(async move {
|
|
launch.monitor().await;
|
|
});
|
|
std::mem::drop(terminal_task);
|
|
return std::result::Result::Ok(acknowledgement);
|
|
}
|
|
|
|
const ROUTE_MONITORING_MIN_EMIT_INTERVAL_MS: u64 = 500;
|
|
|
|
async fn monitor_route_status(
|
|
app: tauri::AppHandle,
|
|
runtime: crate::RawIngestRouteRuntimeDto,
|
|
source: ksp_worker_raw_transaction_ingest_lib::RawTransactionIngestSnapshotSource,
|
|
) {
|
|
let mut snapshot = source.current();
|
|
loop {
|
|
let projected = crate::project_route_monitoring(&runtime, &snapshot);
|
|
match projected {
|
|
std::result::Result::Ok(status) => {
|
|
let main = app.get_webview_window("main");
|
|
if let std::option::Option::Some(window) = main {
|
|
let emitted = window.emit(crate::RAW_INGEST_ROUTE_STATUS_EVENT_NAME, status);
|
|
if emitted.is_err() {
|
|
ksp_logging_lib::warn!(
|
|
target: crate::TRACING_TARGET,
|
|
domain = crate::TRACING_DOMAIN_ROUTE_RUNTIME,
|
|
"Raw Transaction Ingest Desk could not emit latest-value route monitoring status"
|
|
);
|
|
}
|
|
}
|
|
},
|
|
std::result::Result::Err(error) => {
|
|
ksp_logging_lib::warn!(
|
|
target: crate::TRACING_TARGET,
|
|
domain = crate::TRACING_DOMAIN_ROUTE_RUNTIME,
|
|
error_domain = error.code().domain(),
|
|
error_code = error.code().code(),
|
|
"Raw Transaction Ingest Desk could not project latest-value route monitoring status"
|
|
);
|
|
},
|
|
}
|
|
if snapshot.worker_snapshot().state().is_terminal() {
|
|
return;
|
|
}
|
|
let observed = snapshot.worker_snapshot().sequence();
|
|
snapshot = source.wait_for_change(observed).await;
|
|
if !snapshot.worker_snapshot().state().is_terminal() {
|
|
tokio::time::sleep(std::time::Duration::from_millis(ROUTE_MONITORING_MIN_EMIT_INTERVAL_MS)).await;
|
|
snapshot = source.current();
|
|
}
|
|
}
|
|
}
|
|
|
|
#[tauri::command]
|
|
async fn stop_route(
|
|
request: crate::RawIngestRouteStopRequestDto,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<crate::RawIngestRouteRuntimeDto, crate::CommandErrorDto> {
|
|
let result = state.stop_route(&request).await;
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(project_command_error("stop_route", crate::TRACING_DOMAIN_ROUTE_RUNTIME, &error)),
|
|
};
|
|
}
|
|
|
|
#[tauri::command]
|
|
async fn validate_route_start(
|
|
request: crate::RawIngestRouteStartRequestDto,
|
|
state: tauri::State<'_, crate::AppState>,
|
|
) -> std::result::Result<crate::RawIngestRouteStartValidationDto, crate::CommandErrorDto> {
|
|
let result = state.validate_route_start(&request);
|
|
return match result {
|
|
std::result::Result::Ok(value) => std::result::Result::Ok(value),
|
|
std::result::Result::Err(error) => std::result::Result::Err(project_command_error("validate_route_start", crate::TRACING_DOMAIN_ROUTE_START, &error)),
|
|
};
|
|
}
|