// Copyright (c) 2022-2024 Niko Bonnieure, Par le Peuple, NextGraph.org developers // All rights reserved. // Licensed under the Apache License, Version 2.0 // // or the MIT license , // at your option. All files in the project carrying such // notice may not be copied, modified, or distributed except // according to those terms. use std::collections::HashMap; use std::fs::write; use async_std::stream::StreamExt; use oxrdf::Triple; use serde::{Deserialize, Serialize}; use serde_json::Value; use sys_locale::get_locales; use tauri::scope::ipc::RemoteDomainAccessScope; use tauri::utils::config::WindowConfig; use tauri::{path::BaseDirectory, App, Manager}; use zeroize::Zeroize; use ng_repo::errors::NgError; use ng_repo::log::*; use ng_repo::types::*; use ng_repo::utils::decode_key; use ng_net::app_protocol::*; use ng_net::types::{ClientInfo, CreateAccountBSP, Invitation}; use ng_net::utils::{decode_invitation_string, spawn_and_log_error, Receiver, ResultSend}; use ng_wallet::types::*; use ng_wallet::*; use nextgraph::local_broker::*; #[cfg(mobile)] mod mobile; #[cfg(mobile)] pub use mobile::*; pub type SetupHook = Box Result<(), Box> + Send>; #[tauri::command(rename_all = "snake_case")] async fn locales() -> Result, ()> { Ok(get_locales() .filter_map(|lang| { if lang == "C" || lang == "c" { None } else { let mut split = lang.split('.'); let code = split.next().unwrap(); let code = code.replace("_", "-"); let mut split = code.rsplitn(2, '-'); let country = split.next().unwrap(); Some(match split.next() { Some(next) => format!("{}-{}", next, country.to_uppercase()), None => country.to_string(), }) } }) .collect()) } #[tauri::command(rename_all = "snake_case")] async fn test(app: tauri::AppHandle) -> Result<(), ()> { let path = app .path() .resolve("", BaseDirectory::AppLocalData) .map_err(|_| NgError::SerializationError) .unwrap(); init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await; //log_debug!("test is {}", BROKER.read().await.test()); // let path = app // .path() // .resolve("storage", BaseDirectory::AppLocalData) // .map_err(|_| ())?; //BROKER.read().await.test_storage(path); Ok(()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_gen_shuffle_for_pazzle_opening(pazzle_length: u8) -> Result { // log_debug!( // "wallet_gen_shuffle_for_pazzle_opening from rust {}", // pazzle_length // ); Ok(gen_shuffle_for_pazzle_opening(pazzle_length)) } #[tauri::command(rename_all = "snake_case")] async fn wallet_gen_shuffle_for_pin() -> Result, ()> { //log_debug!("wallet_gen_shuffle_for_pin from rust"); Ok(gen_shuffle_for_pin()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_open_with_pazzle( wallet: Wallet, pazzle: Vec, pin: [u8; 4], _app: tauri::AppHandle, ) -> Result { //log_debug!("wallet_open_with_pazzle from rust {:?}", pazzle); let wallet = nextgraph::local_broker::wallet_open_with_pazzle(&wallet, pazzle, pin) .map_err(|e| e.to_string())?; Ok(wallet) } #[tauri::command(rename_all = "snake_case")] async fn wallet_open_with_mnemonic( wallet: Wallet, mnemonic: [u16; 12], pin: [u8; 4], _app: tauri::AppHandle, ) -> Result { let wallet = ng_wallet::open_wallet_with_mnemonic(&wallet, mnemonic, pin).map_err(|e| e.to_string())?; Ok(wallet) } #[tauri::command(rename_all = "snake_case")] async fn wallet_open_with_mnemonic_words( wallet: Wallet, mnemonic_words: Vec, pin: [u8; 4], _app: tauri::AppHandle, ) -> Result { let wallet = nextgraph::local_broker::wallet_open_with_mnemonic_words(&wallet, &mnemonic_words, pin) .map_err(|e| e.to_string())?; Ok(wallet) } #[tauri::command(rename_all = "snake_case")] async fn wallet_get_file(wallet_name: String, app: tauri::AppHandle) -> Result<(), String> { let ser = nextgraph::local_broker::wallet_get_file(&wallet_name) .await .map_err(|e| e.to_string())?; // save wallet file to Downloads folder let path = app .path() .resolve( format!("wallet-{}.ngw", wallet_name), BaseDirectory::Download, ) .unwrap(); write(path, &ser).map_err(|e| e.to_string())?; Ok(()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_create( mut params: CreateWalletV0, app: tauri::AppHandle, ) -> Result { //log_debug!("wallet_create from rust {:?}", params); params.result_with_wallet_file = !params.local_save; let local_save = params.local_save; let pdf = params.pdf; let mut cwr = nextgraph::local_broker::wallet_create_v0(params) .await .map_err(|e| e.to_string())?; if !local_save { // save wallet file to Downloads folder let path = app .path() .resolve( format!("wallet-{}.ngw", cwr.wallet_name), BaseDirectory::Download, ) .unwrap(); let _r = write(path, &cwr.wallet_file); cwr.wallet_file.zeroize(); cwr.wallet_file = vec![]; } if pdf { // save pdf file to Downloads folder let path = app .path() .resolve( format!("wallet-{}.pdf", cwr.wallet_name), BaseDirectory::Download, ) .unwrap(); let _r = write(path, &cwr.pdf_file); cwr.pdf_file.zeroize(); cwr.pdf_file = vec![]; } Ok(cwr) } #[tauri::command(rename_all = "snake_case")] async fn wallet_read_file(file: Vec, _app: tauri::AppHandle) -> Result { nextgraph::local_broker::wallet_read_file(file) .await .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_was_opened( opened_wallet: SensitiveWallet, _app: tauri::AppHandle, ) -> Result { nextgraph::local_broker::wallet_was_opened(opened_wallet) .await .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_import( encrypted_wallet: Wallet, opened_wallet: SensitiveWallet, in_memory: bool, _app: tauri::AppHandle, ) -> Result { nextgraph::local_broker::wallet_import(encrypted_wallet, opened_wallet, in_memory) .await .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_export_rendezvous( session_id: u64, code: String, _app: tauri::AppHandle, ) -> Result<(), String> { nextgraph::local_broker::wallet_export_rendezvous(session_id, code) .await .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_export_get_qrcode( session_id: u64, size: u32, _app: tauri::AppHandle, ) -> Result { nextgraph::local_broker::wallet_export_get_qrcode(session_id, size) .await .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_export_get_textcode( session_id: u64, _app: tauri::AppHandle, ) -> Result { nextgraph::local_broker::wallet_export_get_textcode(session_id) .await .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_import_rendezvous( size: u32, _app: tauri::AppHandle, ) -> Result<(String, String), String> { nextgraph::local_broker::wallet_import_rendezvous(size) .await .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_import_from_code(code: String, _app: tauri::AppHandle) -> Result { nextgraph::local_broker::wallet_import_from_code(code) .await .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn get_wallets( app: tauri::AppHandle, ) -> Result>, String> { let path = app .path() .resolve("", BaseDirectory::AppLocalData) .map_err(|_| NgError::SerializationError) .unwrap(); init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await; let res = wallets_get_all().await.map_err(|e| { log_err!("wallets_get_all error {}", e.to_string()); }); if res.is_ok() { return Ok(Some(res.unwrap())); } Ok(None) } #[tauri::command(rename_all = "snake_case")] async fn session_start( wallet_name: String, user: PubKey, _app: tauri::AppHandle, ) -> Result { let config = SessionConfig::new_save(&user, &wallet_name); nextgraph::local_broker::session_start(config) .await .map_err(|e: NgError| e.to_string()) .map(|s| s.into()) } #[tauri::command(rename_all = "snake_case")] async fn session_start_remote( wallet_name: String, user: PubKey, peer_id: Option, _app: tauri::AppHandle, ) -> Result { let config = SessionConfig::new_remote(&user, &wallet_name, peer_id); nextgraph::local_broker::session_start(config) .await .map_err(|e: NgError| e.to_string()) .map(|s| s.into()) } #[tauri::command(rename_all = "snake_case")] async fn encode_create_account(payload: CreateAccountBSP) -> Result { //log_debug!("{:?}", payload); payload.encode().ok_or(()) } #[tauri::command(rename_all = "snake_case")] async fn open_window( url: String, label: String, title: String, app: tauri::AppHandle, ) -> Result<(), ()> { log_debug!("open window url {:?}", url); let _already_exists = app.get_window(&label); #[cfg(desktop)] if _already_exists.is_some() { let _ = _already_exists.unwrap().close(); std::thread::sleep(std::time::Duration::from_secs(1)); } let mut config = WindowConfig::default(); config.label = label; config.url = tauri::WindowUrl::External(url.parse().unwrap()); config.title = title; let _register_window = tauri::WindowBuilder::from_config(&app, config) .build() .unwrap(); Ok(()) } #[tauri::command(rename_all = "snake_case")] async fn decode_invitation(invite: String) -> Option { decode_invitation_string(invite) } #[tauri::command(rename_all = "snake_case")] async fn file_get( session_id: u64, stream_id: &str, reference: BlockRef, branch_nuri: String, app: tauri::AppHandle, ) -> Result<(), String> { let branch_nuri = NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?; let mut nuri = NuriV0::new_from_obj_ref(&reference); nuri.copy_target_from(&branch_nuri); let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None); request.set_session_id(session_id); app_request_stream(request, stream_id, app).await } #[tauri::command(rename_all = "snake_case")] async fn app_request_stream( request: AppRequest, stream_id: &str, app: tauri::AppHandle, ) -> Result<(), String> { //log_debug!("app request stream {} {:?}", stream_id, request); let main_window = app.get_window("main").unwrap(); let reader; { let cancel; (reader, cancel) = nextgraph::local_broker::app_request_stream(request) .await .map_err(|e| e.to_string())?; nextgraph::local_broker::tauri_stream_add(stream_id.to_string(), cancel) .await .map_err(|e| e.to_string())?; } async fn inner_task( mut reader: Receiver, stream_id: String, main_window: tauri::Window, ) -> ResultSend<()> { while let Some(app_response) = reader.next().await { let app_response = nextgraph::verifier::prepare_app_response_for_js(app_response)?; main_window.emit(&stream_id, app_response).unwrap(); } nextgraph::local_broker::tauri_stream_cancel(stream_id) .await .map_err(|e| e.to_string())?; //log_debug!("END OF LOOP"); Ok(()) } spawn_and_log_error(inner_task(reader, stream_id.to_string(), main_window)); Ok(()) } #[tauri::command(rename_all = "snake_case")] async fn discrete_update( session_id: u64, update: serde_bytes::ByteBuf, heads: Vec, crdt: String, nuri: String, ) -> Result<(), String> { let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_update(), nuri, payload: Some( AppRequestPayload::new_discrete_update(heads, crdt, update.into_vec()) .map_err(|e| format!("Deserialization error of heads: {e}"))?, ), session_id, }); let res = nextgraph::local_broker::app_request(request) .await .map_err(|e: NgError| e.to_string())?; if let AppResponse::V0(AppResponseV0::Error(e)) = res { Err(e) } else { Ok(()) } } #[tauri::command(rename_all = "snake_case")] async fn file_save_to_downloads( session_id: u64, reference: ObjectRef, filename: String, branch_nuri: String, app: tauri::AppHandle, ) -> Result<(), String> { let branch_nuri = NuriV0::new_from(&branch_nuri).map_err(|e| format!("branch_nuri: {}", e.to_string()))?; let mut nuri = NuriV0::new_from_obj_ref(&reference); nuri.copy_target_from(&branch_nuri); let mut request = AppRequest::new(AppRequestCommandV0::FileGet, nuri, None); request.set_session_id(session_id); let (mut reader, _cancel) = nextgraph::local_broker::app_request_stream(request) .await .map_err(|e| e.to_string())?; let mut file_vec: Vec = vec![]; while let Some(app_response) = reader.next().await { match app_response { AppResponse::V0(AppResponseV0::FileMeta(filemeta)) => { file_vec = Vec::with_capacity(filemeta.size as usize); } AppResponse::V0(AppResponseV0::FileBinary(mut bin)) => { if !bin.is_empty() { file_vec.append(&mut bin); } } AppResponse::V0(AppResponseV0::EndOfStream) => break, _ => return Err("invalid response".to_string()), } } let mut i: usize = 0; loop { let dest_filename = if i == 0 { filename.clone() } else { filename .rsplit_once(".") .map(|(l, r)| format!("{l} ({}).{r}", i.to_string())) .or_else(|| Some(format!("{filename} ({})", i.to_string()))) .unwrap() }; let path = app .path() .resolve(dest_filename, BaseDirectory::Download) .unwrap(); if path.exists() { i = i + 1; } else { write(path, &file_vec).map_err(|e| e.to_string())?; break; } } Ok(()) } #[tauri::command(rename_all = "snake_case")] async fn doc_fetch_private_subscribe() -> Result { let request = AppRequest::new( AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)), NuriV0::new_private_store_target(), None, ); Ok(request) } #[tauri::command(rename_all = "snake_case")] async fn doc_fetch_repo_subscribe(repo_o: String) -> Result { let request = AppRequest::new( AppRequestCommandV0::Fetch(AppFetchContentV0::get_or_subscribe(true)), NuriV0::new_from(&repo_o).map_err(|e| e.to_string())?, None, ); Ok(request) } #[tauri::command(rename_all = "snake_case")] async fn branch_history(session_id: u64, nuri: String) -> Result { let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_history(), nuri: NuriV0::new_from(&nuri).map_err(|e| e.to_string())?, payload: None, session_id, }); let res = nextgraph::local_broker::app_request(request) .await .map_err(|e: NgError| e.to_string())?; let AppResponse::V0(res) = res; //log_debug!("{:?}", res); match res { AppResponseV0::History(s) => Ok(s.to_js()), _ => Err("invalid response".to_string()), } } #[tauri::command(rename_all = "snake_case")] async fn sparql_update(session_id: u64, sparql: String, nuri: String) -> Result<(), String> { let nuriv0 = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_write_query(), nuri: nuriv0, payload: Some(AppRequestPayload::new_sparql_query(sparql, Some(nuri))), session_id, }); let res = nextgraph::local_broker::app_request(request) .await .map_err(|e: NgError| e.to_string())?; if let AppResponse::V0(AppResponseV0::Error(e)) = res { Err(e) } else { Ok(()) } } #[tauri::command(rename_all = "snake_case")] async fn sparql_query( session_id: u64, sparql: String, base: Option, nuri: Option, ) -> Result { let nuri = if nuri.is_some() { NuriV0::new_from(&nuri.unwrap()).map_err(|e| e.to_string())? } else { NuriV0::new_entire_user_site() }; let request = AppRequest::V0(AppRequestV0 { command: AppRequestCommandV0::new_read_query(), nuri, payload: Some(AppRequestPayload::new_sparql_query(sparql, base)), session_id, }); let response = nextgraph::local_broker::app_request(request) .await .map_err(|e: NgError| e.to_string())?; let AppResponse::V0(res) = response; match res { AppResponseV0::False => return Ok(Value::Bool(false)), AppResponseV0::True => return Ok(Value::Bool(true)), AppResponseV0::Graph(graph) => { let triples: Vec = serde_bare::from_slice(&graph) .map_err(|_| "Deserialization error of graph".to_string())?; Ok(Value::Array( triples .into_iter() .map(|t| Value::String(t.to_string())) .collect(), )) } AppResponseV0::QueryResult(buf) => { let string = String::from_utf8(buf) .map_err(|_| "Deserialization error of JSON QueryResult String".to_string())?; Ok(serde_json::from_str(&string) .map_err(|_| "Parsing error of JSON QueryResult String".to_string())?) } AppResponseV0::Error(e) => Err(e.to_string().into()), _ => Err("invalid AppResponse".to_string().into()), } } #[tauri::command(rename_all = "snake_case")] async fn app_request(request: AppRequest) -> Result { //log_debug!("app request {:?}", request); nextgraph::local_broker::app_request(request) .await .map_err(|e| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn doc_create( session_id: u64, crdt: String, class_name: String, store_repo: StoreRepo, destination: String, ) -> Result { let class = BranchCrdt::from(crdt, class_name).map_err(|e| e.to_string())?; let destination = DocCreateDestination::from(destination).map_err(|e| e.to_string())?; let request = AppRequest::V0(AppRequestV0 { session_id, command: AppRequestCommandV0::new_create(), nuri: NuriV0::new_empty(), payload: Some(AppRequestPayload::V0(AppRequestPayloadV0::Create( DocCreate { store: store_repo, class, destination, }, ))), }); let response = nextgraph::local_broker::app_request(request) .await .map_err(|e: NgError| e.to_string())?; if let AppResponse::V0(AppResponseV0::Nuri(nuri)) = response { Ok(nuri) } else { Err("invalid response".to_string()) } } #[tauri::command(rename_all = "snake_case")] async fn app_request_with_nuri_command( nuri: String, command: AppRequestCommandV0, session_id: u64, payload: Option, ) -> Result { let nuri = NuriV0::new_from(&nuri).map_err(|e| e.to_string())?; let payload = payload.map(|p| AppRequestPayload::V0(p)); let request = AppRequest::V0(AppRequestV0 { session_id, command, nuri, payload, }); app_request(request).await } #[tauri::command(rename_all = "snake_case")] async fn upload_chunk( session_id: u64, upload_id: u32, chunk: serde_bytes::ByteBuf, nuri: String, _app: tauri::AppHandle, ) -> Result { //log_debug!("upload_chunk {:?}", chunk); let mut request = AppRequest::new( AppRequestCommandV0::FilePut, NuriV0::new_from(&nuri).map_err(|e| e.to_string())?, Some(AppRequestPayload::V0( AppRequestPayloadV0::RandomAccessFilePutChunk((upload_id, chunk)), )), ); request.set_session_id(session_id); nextgraph::local_broker::app_request(request) .await .map_err(|e| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn cancel_stream(stream_id: &str) -> Result<(), String> { //log_debug!("cancel stream {}", stream_id); Ok( nextgraph::local_broker::tauri_stream_cancel(stream_id.to_string()) .await .map_err(|e: NgError| e.to_string())?, ) } #[tauri::command(rename_all = "snake_case")] async fn disconnections_subscribe(app: tauri::AppHandle) -> Result<(), String> { let path = app .path() .resolve("", BaseDirectory::AppLocalData) .map_err(|_| NgError::SerializationError) .unwrap(); init_local_broker(Box::new(move || LocalBrokerConfig::BasePath(path.clone()))).await; let main_window = app.get_window("main").unwrap(); let reader = nextgraph::local_broker::take_disconnections_receiver() .await .map_err(|e: NgError| e.to_string())?; async fn inner_task( mut reader: Receiver, main_window: tauri::Window, ) -> ResultSend<()> { while let Some(user_id) = reader.next().await { log_debug!("DISCONNECTION FOR {user_id}"); main_window.emit("disconnections", user_id).unwrap(); } log_debug!("END OF disconnections listener"); Ok(()) } spawn_and_log_error(inner_task(reader, main_window)); Ok(()) } #[tauri::command(rename_all = "snake_case")] async fn session_stop(user_id: String) -> Result<(), String> { let user_id = decode_key(&user_id).map_err(|_| "Invalid user_id")?; nextgraph::local_broker::session_stop(&user_id) .await .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn user_disconnect(user_id: String) -> Result<(), String> { let user_id = decode_key(&user_id).map_err(|_| "Invalid user_id")?; nextgraph::local_broker::user_disconnect(&user_id) .await .map_err(|e: NgError| e.to_string()) } #[tauri::command(rename_all = "snake_case")] async fn wallet_close(wallet_name: String) -> Result<(), String> { nextgraph::local_broker::wallet_close(&wallet_name) .await .map_err(|e: NgError| e.to_string()) } #[derive(Serialize, Deserialize)] struct ConnectionInfo { pub server_id: String, pub server_ip: String, pub error: Option, pub since: u64, } #[tauri::command(rename_all = "snake_case")] async fn user_connect( info: ClientInfo, user_id: String, _location: Option, ) -> Result, String> { let user_id = decode_key(&user_id).map_err(|_| "Invalid user_id")?; let mut opened_connections: HashMap = HashMap::new(); let results = nextgraph::local_broker::user_connect_with_device_info(info, &user_id, None) .await .map_err(|e| e.to_string())?; log_debug!("{:?}", results); for result in results { opened_connections.insert( result.0, ConnectionInfo { server_id: result.1, server_ip: result.2, error: result.3, since: result.4 as u64, }, ); } Ok(opened_connections) } #[tauri::command(rename_all = "snake_case")] fn client_info_rust() -> Result { Ok(ng_repo::os_info::get_os_info()) } #[tauri::command(rename_all = "snake_case")] fn get_device_name() -> Result { Ok(nextgraph::get_device_name()) } #[derive(Default)] pub struct AppBuilder { setup: Option, } #[cfg(debug_assertions)] const ALLOWED_BSP_DOMAINS: [&str; 2] = ["account-dev.nextgraph.eu", "account-dev.nextgraph.net"]; #[cfg(not(debug_assertions))] const ALLOWED_BSP_DOMAINS: [&str; 2] = ["account.nextgraph.eu", "account.nextgraph.net"]; impl AppBuilder { pub fn new() -> Self { Self::default() } #[must_use] pub fn setup(mut self, setup: F) -> Self where F: FnOnce(&mut App) -> Result<(), Box> + Send + 'static, { self.setup.replace(Box::new(setup)); self } pub fn run(self) { let setup = self.setup; #[allow(unused_mut)] let mut builder = tauri::Builder::default() .setup(move |app| { if let Some(setup) = setup { (setup)(app)?; } for domain in ALLOWED_BSP_DOMAINS { app.ipc_scope().configure_remote_access( RemoteDomainAccessScope::new(domain) .add_window("registration") .add_window("main") .add_plugins(["window", "event"]), ); } Ok(()) }) .plugin(tauri_plugin_window::init()); #[cfg(mobile)] { builder = builder.plugin(tauri_plugin_barcode_scanner::init()); } builder .invoke_handler(tauri::generate_handler![ test, locales, wallet_gen_shuffle_for_pazzle_opening, wallet_gen_shuffle_for_pin, wallet_open_with_pazzle, wallet_open_with_mnemonic, wallet_open_with_mnemonic_words, wallet_was_opened, wallet_create, wallet_read_file, wallet_get_file, wallet_import, wallet_export_rendezvous, wallet_export_get_qrcode, wallet_export_get_textcode, wallet_import_rendezvous, wallet_import_from_code, wallet_close, encode_create_account, session_start, session_start_remote, session_stop, get_wallets, open_window, decode_invitation, disconnections_subscribe, user_connect, user_disconnect, client_info_rust, doc_fetch_private_subscribe, doc_fetch_repo_subscribe, doc_create, cancel_stream, discrete_update, app_request_stream, file_get, file_save_to_downloads, app_request, app_request_with_nuri_command, upload_chunk, get_device_name, sparql_query, sparql_update, branch_history, ]) .run(tauri::generate_context!()) .expect("error while running tauri application"); } }