#[cfg(desktop)]
mod db;
mod ipfs;
mod metrics;
mod mining;
#[cfg(desktop)]
mod server;
#[cfg(desktop)]
mod utils;
use std::sync::Arc;
#[cfg(desktop)]
use db::DbState;
use ipfs::{get_ipfs_mode, init_ipfs, is_ipfs_initialized, is_ipfs_running, start_ipfs, stop_ipfs};
use mining::MiningState;
#[cfg(desktop)]
use server::start_server;
use tauri::generate_handler;
use tauri::Manager;
use tauri::RunEvent;
#[cfg(desktop)]
use tauri::WebviewWindow;
#[cfg(desktop)]
use utils::update_splash_message;
#[cfg(desktop)]
async fn init_ipfs_with_progress(splash: &WebviewWindow) {
// Step 1: Initialize IPFS repo if needed
update_splash_message(splash, "Initializing IPFS...");
println!("[CYB.AI] Step 1: Checking if IPFS is initialized...");
let is_init = is_ipfs_initialized().unwrap_or(false);
if !is_init {
println!("[CYB.AI] Initializing IPFS repo...");
if let Err(e) = init_ipfs() {
eprintln!("[CYB.AI] Failed to init IPFS: {}", e);
update_splash_message(splash, &format!("IPFS init failed: {}", e));
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
return;
}
}
// Step 2: Start the daemon (handles CORS config internally)
update_splash_message(splash, "Starting IPFS daemon...");
println!("[CYB.AI] Step 2: Starting IPFS daemon...");
match start_ipfs().await {
Ok(_) => println!("[CYB.AI] IPFS daemon started"),
Err(e) => {
eprintln!("[CYB.AI] Failed to start IPFS: {:?}", e);
update_splash_message(splash, &format!("IPFS start failed: {:?}", e));
tokio::time::sleep(tokio::time::Duration::from_secs(2)).await;
return;
}
}
// Step 3: Wait for daemon API to be ready
update_splash_message(splash, "Waiting for IPFS daemon...");
println!("[CYB.AI] Step 3: Waiting for daemon API...");
let client = reqwest::Client::builder()
.timeout(std::time::Duration::from_secs(3))
.build()
.unwrap();
let max_attempts = 15;
for i in 0..max_attempts {
match client.post("http://127.0.0.1:5001/api/v0/id").send().await {
Ok(resp) if resp.status().is_success() => {
println!("[CYB.AI] IPFS API is ready!");
break;
}
_ => {
if i == max_attempts - 1 {
eprintln!("[CYB.AI] IPFS API did not become ready in time");
update_splash_message(splash, "IPFS daemon slow to start, continuing...");
} else {
println!(
"[CYB.AI] Waiting for IPFS API... ({}/{})",
i + 1,
max_attempts
);
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
}
}
}
}
update_splash_message(splash, "IPFS started successfully!");
println!("[CYB.AI] IPFS initialization complete!");
}
/// Read and delete bootstrap.json written by cyb-boot installer.
/// Returns {mnemonic, referrer} if found, null otherwise.
#[tauri::command]
fn read_bootstrap() -> Option<serde_json::Value> {
let data_dir = dirs::data_dir()?.join("ai.cyb.app");
let path = data_dir.join("bootstrap.json");
let content = std::fs::read_to_string(&path).ok()?;
let value: serde_json::Value = serde_json::from_str(&content).ok()?;
// Delete after reading โ one-time import
let _ = std::fs::remove_file(&path);
println!("[CYB.AI] Imported bootstrap.json from cyb-boot installer");
Some(value)
}
#[tauri::command]
fn toggle_devtools(window: tauri::WebviewWindow) {
if window.is_devtools_open() {
window.close_devtools();
} else {
window.open_devtools();
}
}
#[tauri::command]
fn reveal_in_finder(path: String) -> Result<(), String> {
#[cfg(target_os = "macos")]
{
std::process::Command::new("open")
.args(["-R", &path])
.spawn()
.map_err(|e| e.to_string())?;
}
#[cfg(target_os = "linux")]
{
std::process::Command::new("xdg-open")
.arg(std::path::Path::new(&path).parent().unwrap_or(std::path::Path::new(&path)))
.spawn()
.map_err(|e| e.to_string())?;
}
#[cfg(target_os = "windows")]
{
std::process::Command::new("explorer")
.args(["/select,", &path])
.spawn()
.map_err(|e| e.to_string())?;
}
Ok(())
}
#[cfg(desktop)]
fn build_tauri_app() -> tauri::Builder<tauri::Wry> {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.manage(Arc::new(MiningState::new()))
.invoke_handler(generate_handler![
toggle_devtools,
read_bootstrap,
reveal_in_finder,
get_ipfs_mode,
start_ipfs,
stop_ipfs,
is_ipfs_running,
is_ipfs_initialized,
init_ipfs,
mining::start_mining,
mining::stop_mining,
mining::update_challenge,
mining::get_mining_status,
mining::take_proofs,
mining::mining_benchmark,
mining::get_mining_params,
mining::report_proof_submitted,
mining::report_proof_failed,
])
}
#[cfg(not(desktop))]
fn build_tauri_app() -> tauri::Builder<tauri::Wry> {
tauri::Builder::default()
.plugin(tauri_plugin_shell::init())
.plugin(tauri_plugin_deep_link::init())
.plugin(tauri_plugin_dialog::init())
.plugin(tauri_plugin_fs::init())
.manage(Arc::new(MiningState::new()))
.invoke_handler(generate_handler![
toggle_devtools,
read_bootstrap,
reveal_in_finder,
get_ipfs_mode,
start_ipfs,
stop_ipfs,
is_ipfs_running,
is_ipfs_initialized,
init_ipfs,
mining::start_mining,
mining::stop_mining,
mining::update_challenge,
mining::get_mining_status,
mining::take_proofs,
mining::mining_benchmark,
mining::get_mining_params,
mining::report_proof_submitted,
mining::report_proof_failed,
])
}
#[cfg_attr(mobile, tauri::mobile_entry_point)]
pub fn run() {
#[cfg(desktop)]
{
let app_state = Arc::new(DbState::new());
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()
.unwrap()
.block_on(async {
let server_state = app_state.clone();
tokio::spawn(async move {
println!("[CYB.AI] Starting server...");
start_server(server_state).await;
println!("[CYB.AI] Server is started!");
});
let app = build_tauri_app()
.setup(|app| {
println!("[CYB.AI] Starting setup...");
let splashscreen_window =
app.get_webview_window("splashscreen").unwrap();
splashscreen_window.show().unwrap();
let main_window = app.get_webview_window("main").unwrap();
tauri::async_runtime::spawn(async move {
init_ipfs_with_progress(&splashscreen_window).await;
tokio::time::sleep(tokio::time::Duration::from_secs(1)).await;
println!("[CYB.AI] Showing main window...");
main_window.show().unwrap();
if cfg!(debug_assertions) {
main_window.open_devtools();
}
let _ = splashscreen_window.close();
println!("[CYB.AI] App ready!");
});
Ok(())
})
.build(tauri::generate_context!())
.expect("error while building tauri application");
app.run(|_app_handle, event| {
if let RunEvent::Exit = event {
println!("[CYB.AI] App exiting โ cleaning up...");
// Stop IPFS daemon
if let Err(e) = stop_ipfs() {
eprintln!("[CYB.AI] Failed to stop IPFS: {}", e);
}
// Kill IPFS child process directly if shutdown command didn't work
if let Ok(mut guard) = ipfs::IPFS_CHILD.lock() {
if let Some(ref mut child) = *guard {
let _ = child.kill();
let _ = child.wait();
println!("[CYB.AI] IPFS child process killed");
}
*guard = None;
}
// Stop mining (releases GPU/CPU resources)
let state = _app_handle.state::<Arc<MiningState>>();
state.mining.store(false, std::sync::atomic::Ordering::SeqCst);
println!("[CYB.AI] Mining stopped");
}
});
});
}
#[cfg(not(desktop))]
{
use tauri::Emitter;
println!("[CYB.AI] Mobile platform startup");
build_tauri_app()
.setup(|_app| {
println!("[CYB.AI] App ready!");
Ok(())
})
.build(tauri::generate_context!())
.expect("error while building tauri application")
.run(|app_handle, event| {
if let RunEvent::Resumed { .. } = event {
println!("[CYB.AI] App resumed (foregrounded)");
let _ = app_handle.emit("app-resumed", ());
}
});
}
}
cyb/src-tauri/src/lib.rs
ฯ 0.0%
use Arc;
use DbState;
use ;
use MiningState;
use start_server;
use generate_handler;
use Manager;
use RunEvent;
use WebviewWindow;
use update_splash_message;
async
/// Read and delete bootstrap.json written by cyb-boot installer.
/// Returns {mnemonic, referrer} if found, null otherwise.