From cefcb147499e2fc8da3d4d0eb583a2eb89092652 Mon Sep 17 00:00:00 2001 From: iAmir Date: Mon, 12 Aug 2024 05:05:29 +0330 Subject: [PATCH] implement sync RPC calls, move storage api to synced --- src-tauri/Cargo.lock | 1 + src-tauri/Cargo.toml | 1 + src-tauri/src/commands.rs | 69 --------- src-tauri/src/main.rs | 28 ++-- src-tauri/src/rpcs.rs | 195 +++++++++++++++++++++++- src/api/rpc.ts | 20 +++ src/containers/Settings/Tab/General.tsx | 2 + src/main.tsx | 3 + src/utils/nativeStorage.ts | 58 ++++++- src/utils/query.ts | 2 - 10 files changed, 283 insertions(+), 96 deletions(-) diff --git a/src-tauri/Cargo.lock b/src-tauri/Cargo.lock index 98d36884..d518deb3 100644 --- a/src-tauri/Cargo.lock +++ b/src-tauri/Cargo.lock @@ -2846,6 +2846,7 @@ dependencies = [ "dll-syringe", "encoding_rs", "gumdrop", + "lazy_static", "log", "md5", "regex", diff --git a/src-tauri/Cargo.toml b/src-tauri/Cargo.toml index c0141824..3a3e9e0b 100644 --- a/src-tauri/Cargo.toml +++ b/src-tauri/Cargo.toml @@ -38,6 +38,7 @@ actix-cors = "0.7" winapi = "0.3" tauri-plugin-deep-link = "0.1.2" gumdrop = "0.8.1" +lazy_static = "1.4" [target.'cfg(target_os = "windows")'.dependencies] dll-syringe = "0.15" diff --git a/src-tauri/src/commands.rs b/src-tauri/src/commands.rs index 22de9465..6ea83494 100644 --- a/src-tauri/src/commands.rs +++ b/src-tauri/src/commands.rs @@ -1,74 +1,5 @@ -use std::fs; -use std::path::PathBuf; - -use serde_json::{json, Value}; -use tauri::State; - use crate::{injector, samp}; -#[derive(Default)] -pub struct AppState { - pub storage_file: PathBuf, -} - -fn ensure_storage_file(storage_file: &PathBuf) -> Result<(), String> { - if !storage_file.exists() { - // Create an empty JSON file - fs::write(storage_file, "{}").map_err(|e| e.to_string())?; - } - Ok(()) -} - -#[tauri::command] -pub fn get_item(state: State, key: String) -> Result, String> { - let storage_file = state.storage_file.clone(); - ensure_storage_file(&storage_file)?; - - let data = fs::read_to_string(&storage_file).map_err(|e| e.to_string())?; - let json_data: Value = serde_json::from_str(&data).map_err(|e| e.to_string())?; - - Ok(json_data - .get(&key) - .and_then(|v| v.as_str().map(|s| s.to_string()))) -} - -#[tauri::command] -pub fn set_item(state: State, key: String, value: String) -> Result<(), String> { - let storage_file = state.storage_file.clone(); - ensure_storage_file(&storage_file)?; - - let data = fs::read_to_string(&storage_file).map_err(|e| e.to_string())?; - let mut json_data: Value = serde_json::from_str(&data).map_err(|e| e.to_string())?; - - json_data[key] = json!(value); - - fs::write(storage_file, json_data.to_string()).map_err(|e| e.to_string())?; - Ok(()) -} - -#[tauri::command] -pub fn remove_item(state: State, key: String) -> Result<(), String> { - let storage_file = state.storage_file.clone(); - ensure_storage_file(&storage_file)?; - - let data = fs::read_to_string(&storage_file).map_err(|e| e.to_string())?; - let mut json_data: Value = serde_json::from_str(&data).map_err(|e| e.to_string())?; - - json_data.as_object_mut().map(|map| map.remove(&key)); - - fs::write(storage_file, json_data.to_string()).map_err(|e| e.to_string())?; - Ok(()) -} - -#[tauri::command] -pub fn get_all_items(state: State) -> Result { - let storage_file = state.storage_file.clone(); - ensure_storage_file(&storage_file)?; - - let data = fs::read_to_string(&storage_file).unwrap_or_else(|_| "{}".to_string()); - Ok(data) -} - #[tauri::command] pub async fn inject( name: &str, diff --git a/src-tauri/src/main.rs b/src-tauri/src/main.rs index 5f1e7463..c13018b5 100644 --- a/src-tauri/src/main.rs +++ b/src-tauri/src/main.rs @@ -10,11 +10,9 @@ mod rpcs; mod samp; use std::env; -use std::path::PathBuf; use std::process::exit; use std::sync::Mutex; -use commands::AppState; use gumdrop::Options; use injector::run_samp; use log::{error, LevelFilter}; @@ -53,8 +51,11 @@ async fn main() { #[cfg(windows)] { - use windows::Win32::System::Console::{AttachConsole, ATTACH_PARENT_PROCESS}; - let _ = unsafe { AttachConsole(ATTACH_PARENT_PROCESS) }; + #[cfg(not(debug_assertions))] + { + use windows::Win32::System::Console::{AttachConsole, ATTACH_PARENT_PROCESS}; + let _ = unsafe { AttachConsole(ATTACH_PARENT_PROCESS) }; + } } let raw_args: Vec = env::args().collect(); @@ -109,8 +110,11 @@ Options: #[cfg(windows)] { - use windows::Win32::System::Console::FreeConsole; - let _ = unsafe { FreeConsole() }; + #[cfg(not(debug_assertions))] + { + use windows::Win32::System::Console::FreeConsole; + let _ = unsafe { FreeConsole() }; + } } std::thread::spawn(move || { @@ -119,12 +123,6 @@ Options: }); match tauri::Builder::default() - .manage(AppState { - storage_file: PathBuf::from(format!( - "{}/com.open.mp/storage.json", - dirs_next::data_local_dir().unwrap().to_str().unwrap() - )), - }) .plugin(tauri_plugin_upload::init()) .setup(|app| { let handle = app.handle(); @@ -160,11 +158,7 @@ Options: commands::get_gtasa_path_from_samp, commands::get_nickname_from_samp, commands::rerun_as_admin, - commands::get_samp_favorite_list, - commands::get_item, - commands::set_item, - commands::remove_item, - commands::get_all_items + commands::get_samp_favorite_list ]) .run(tauri::generate_context!()) { diff --git a/src-tauri/src/rpcs.rs b/src-tauri/src/rpcs.rs index cdf40391..0c4c3ccc 100644 --- a/src-tauri/src/rpcs.rs +++ b/src-tauri/src/rpcs.rs @@ -1,17 +1,28 @@ use actix_cors::Cors; use actix_web::{web, App, HttpResponse, HttpServer, Responder}; +use lazy_static::lazy_static; use md5::compute; use serde::Deserialize; -use serde_json::json; +use serde_json::{json, Value}; use sevenz_rust::decompress_file; use std::error::Error; +use std::fs; use std::fs::File; use std::io::Read; +use std::path::PathBuf; +use std::sync::Mutex; use std::time::Instant; +use tokio::sync::Semaphore; use crate::query; use crate::{discord, helpers}; +static STORAGE_FILE: Mutex> = Mutex::new(None); + +lazy_static! { + static ref SEMAPHORE: Semaphore = Semaphore::new(1); +} + #[derive(Deserialize)] struct RpcParams { params: serde_json::Value, @@ -50,6 +61,17 @@ struct CopyFilesToGtaSaParams { gtasa_dir: String, } +#[derive(Deserialize)] +struct StorageGetOrRemoveItemParams { + key: String, +} + +#[derive(Deserialize)] +struct StorageSetItemParams { + key: String, + value: String, +} + async fn request_server_info(ip: &str, port: i32) -> Result { match query::Query::new(ip, port).await { Ok(q) => { @@ -148,6 +170,65 @@ fn copy_files_to_gtasa(src: &str, gtasa_dir: &str) -> Result<(), String> { helpers::copy_files(src, gtasa_dir) } +fn storage_get_item(key: String) -> Result, String> { + init_storage_file(); + let storage_file = STORAGE_FILE.lock().unwrap().to_owned().unwrap(); + ensure_storage_file(&storage_file)?; + + let data = fs::read_to_string(&storage_file).map_err(|e| e.to_string())?; + let json_data: Value = serde_json::from_str(&data).map_err(|e| e.to_string())?; + + Ok(json_data + .get(&key) + .and_then(|v| v.as_str().map(|s| s.to_string()))) +} + +fn storage_set_item(key: String, value: String) -> Result<(), String> { + init_storage_file(); + let storage_file = STORAGE_FILE.lock().unwrap().to_owned().unwrap(); + ensure_storage_file(&storage_file)?; + + let data = fs::read_to_string(&storage_file).map_err(|e| e.to_string())?; + let mut json_data: Value = serde_json::from_str(&data).map_err(|e| e.to_string())?; + + json_data[key] = json!(value); + + fs::write(storage_file, json_data.to_string()).map_err(|e| e.to_string())?; + Ok(()) +} + +fn storage_remove_item(key: String) -> Result<(), String> { + init_storage_file(); + let storage_file = STORAGE_FILE.lock().unwrap().to_owned().unwrap(); + ensure_storage_file(&storage_file)?; + + let data = fs::read_to_string(&storage_file).map_err(|e| e.to_string())?; + let mut json_data: Value = serde_json::from_str(&data).map_err(|e| e.to_string())?; + + json_data.as_object_mut().map(|map| map.remove(&key)); + + fs::write(storage_file, json_data.to_string()).map_err(|e| e.to_string())?; + Ok(()) +} + +fn storage_get_all_items() -> Result { + init_storage_file(); + let storage_file = STORAGE_FILE.lock().unwrap().to_owned().unwrap(); + ensure_storage_file(&storage_file)?; + + let data = fs::read_to_string(&storage_file).unwrap_or_else(|_| "{}".to_string()); + Ok(data) +} + +fn storage_clear() -> Result<(), String> { + init_storage_file(); + let storage_file = STORAGE_FILE.lock().unwrap().to_owned().unwrap(); + ensure_storage_file(&storage_file)?; + + fs::write(storage_file, "{}").map_err(|e| e.to_string())?; + Ok(()) +} + async fn rpc_handler( path: web::Path, payload: web::Json, @@ -252,14 +333,124 @@ async fn rpc_handler( Ok(HttpResponse::Ok().body(response)) } +async fn sync_rpc_handler( + path: web::Path, + payload: web::Json, +) -> Result> { + let _permit = SEMAPHORE.acquire().await.unwrap(); // Acquire a permit to ensure only one request is processed at a time + let params_str = serde_json::to_string(&payload.params)?; + + /* + method: storage_get_item + */ + if path.method == "storage_get_item" { + let params: StorageGetOrRemoveItemParams = serde_json::from_str(params_str.as_str())?; + let result = storage_get_item(params.key); + if result.is_err() { + return Ok( + HttpResponse::Ok().body(format!("storage_error|sep|{}", result.err().unwrap())) + ); + } + + return resolve_option_for_http_response(result.unwrap()); + } + /* + method: storage_remove_item + */ + else if path.method == "storage_remove_item" { + let params: StorageGetOrRemoveItemParams = serde_json::from_str(params_str.as_str())?; + let result = storage_remove_item(params.key); + if result.is_err() { + return Ok( + HttpResponse::Ok().body(format!("storage_error|sep|{}", result.err().unwrap())) + ); + } + + return Ok(HttpResponse::Ok().body("{}")); + } + /* + method: storage_set_item + */ + else if path.method == "storage_set_item" { + let params: StorageSetItemParams = serde_json::from_str(params_str.as_str())?; + let result = storage_set_item(params.key, params.value); + if result.is_err() { + return Ok( + HttpResponse::Ok().body(format!("storage_error|sep|{}", result.err().unwrap())) + ); + } + + return Ok(HttpResponse::Ok().body("{}")); + } + /* + method: storage_get_all_items + */ + else if path.method == "storage_get_all_items" { + let result = storage_get_all_items(); + if result.is_err() { + return Ok( + HttpResponse::Ok().body(format!("storage_error|sep|{}", result.err().unwrap())) + ); + } + + return Ok(HttpResponse::Ok().body(result.unwrap())); + } + /* + method: storage_clear + */ + else if path.method == "storage_clear" { + let result = storage_clear(); + if result.is_err() { + return Ok( + HttpResponse::Ok().body(format!("storage_error|sep|{}", result.err().unwrap())) + ); + } + + return Ok(HttpResponse::Ok().body("{}")); + } + + let response = format!( + "Received RPC request: {} with params: {:?}", + path.method, payload.params + ); + Ok(HttpResponse::Ok().body(response)) +} + pub async fn initialize_rpc() -> Result<(), std::io::Error> { HttpServer::new(|| { App::new() .wrap(Cors::permissive()) + .service(web::resource("/sync_rpc/{method}").route(web::post().to(sync_rpc_handler))) .service(web::resource("/rpc/{method}").route(web::post().to(rpc_handler))) }) .bind("127.0.0.1:46290")? .run() .await - // Ok(()) +} + +fn resolve_option_for_http_response( + option: Option, +) -> Result> { + match option { + Some(res) => Ok(HttpResponse::Ok().body(res)), + None => Ok(HttpResponse::Ok().body("null")), + } +} + +fn ensure_storage_file(storage_file: &PathBuf) -> Result<(), String> { + if !storage_file.exists() { + // Create an empty JSON file + fs::write(storage_file, "{}").map_err(|e| e.to_string())?; + } + Ok(()) +} + +fn init_storage_file() { + let mut storage_file_guard = STORAGE_FILE.lock().unwrap(); + if storage_file_guard.is_none() { + *storage_file_guard = Some(PathBuf::from(format!( + "{}/com.open.mp/storage.json", + dirs_next::data_local_dir().unwrap().to_str().unwrap() + ))); + } } diff --git a/src/api/rpc.ts b/src/api/rpc.ts index 90158662..39148672 100644 --- a/src/api/rpc.ts +++ b/src/api/rpc.ts @@ -21,3 +21,23 @@ export const invoke_rpc = async (method: string, params: object) => { const text = await response.text(); return text; }; + +export const invoke_sync_rpc = async (method: string, params: object) => { + const response = await fetch(`${RPC_ENDPOINT}/sync_rpc/${method}`, { + method: "POST", + headers: { + "Content-Type": "application/json", + "Access-Control-Allow-Origin": "*", + }, + body: JSON.stringify({ + params: params, + }), + }); + + if (!response.ok) { + Log.debug(`RPC request failed with status ${response.status}`); + } + + const text = await response.text(); + return text; +}; diff --git a/src/containers/Settings/Tab/General.tsx b/src/containers/Settings/Tab/General.tsx index 4926d32f..0e952b4e 100644 --- a/src/containers/Settings/Tab/General.tsx +++ b/src/containers/Settings/Tab/General.tsx @@ -9,6 +9,7 @@ import { useSettings } from "../../../states/settings"; import { useTheme } from "../../../states/theme"; import { checkDirectoryValidity } from "../../../utils/game"; import { Log } from "../../../utils/logger"; +import nativeStorage from "../../../utils/nativeStorage"; import { sc } from "../../../utils/sizeScaler"; import { Server } from "../../../utils/types"; @@ -181,6 +182,7 @@ const General = () => { }, ]} onPress={() => { + nativeStorage.clear(); localStorage.clear(); window.location.reload(); }} diff --git a/src/main.tsx b/src/main.tsx index 7a1bae86..be81e4d6 100644 --- a/src/main.tsx +++ b/src/main.tsx @@ -16,6 +16,7 @@ async function runner() { if (favsStr) { const favs = JSON.parse(favsStr); const settingsNewStr = await nativeStorage.getItem("settings-storage"); + console.log(typeof settingsNewStr, settingsNewStr); const hasNativeData = settingsNewStr === null @@ -43,6 +44,8 @@ async function runner() { const theme = JSON.parse(themeStr); useTheme.setState(theme.state); } + } else { + useSettings.setState({ dataMerged: true }); } } diff --git a/src/utils/nativeStorage.ts b/src/utils/nativeStorage.ts index bbb9e365..60c43de4 100644 --- a/src/utils/nativeStorage.ts +++ b/src/utils/nativeStorage.ts @@ -1,10 +1,17 @@ -import { invoke } from "@tauri-apps/api/tauri"; +import { invoke_sync_rpc } from "../api/rpc"; class NativeStorage { async getItem(key: string) { try { - const value = await invoke("get_item", { key }); - return value; + const value = await invoke_sync_rpc("storage_get_item", { key }); + if (value.startsWith("storage_error|sep|")) { + console.error( + "Error getting item from storage:", + value.replace("storage_error|sep|", "") + ); + return null; + } + return value === "null" ? null : value; } catch (error) { console.error("Error getting item from storage:", error); return null; @@ -13,7 +20,14 @@ class NativeStorage { async setItem(key: string, value: string) { try { - await invoke("set_item", { key, value }); + const result = await invoke_sync_rpc("storage_set_item", { key, value }); + if (result.startsWith("storage_error|sep|")) { + console.error( + "Error setting item from storage:", + result.replace("storage_error|sep|", "") + ); + return; + } } catch (error) { console.error("Error setting item in storage:", error); } @@ -21,7 +35,14 @@ class NativeStorage { async removeItem(key: string) { try { - await invoke("remove_item", { key }); + const result = await invoke_sync_rpc("storage_remove_item", { key }); + if (result.startsWith("storage_error|sep|")) { + console.error( + "Error removing item from storage:", + result.replace("storage_error|sep|", "") + ); + return; + } } catch (error) { console.error("Error removing item from storage:", error); } @@ -29,13 +50,35 @@ class NativeStorage { async getAllItems(): Promise> { try { - const items = await invoke("get_all_items"); + const items = await invoke_sync_rpc("storage_get_all_items", {}); + if (items.startsWith("storage_error|sep|")) { + console.error( + "Error getting all items from storage:", + items.replace("storage_error|sep|", "") + ); + return {}; + } return JSON.parse(items); } catch (error) { console.error("Error getting all items from storage:", error); return {}; } } + + async clear() { + try { + const result = await invoke_sync_rpc("storage_clear", {}); + if (result.startsWith("storage_error|sep|")) { + console.error( + "Error clearing all from storage:", + result.replace("storage_error|sep|", "") + ); + return null; + } + } catch (error) { + console.error("Error clearing all items from storage:", error); + } + } } const nativeStorage = new NativeStorage(); @@ -50,6 +93,9 @@ export const nativeStateStorage: any = { removeItem: async (key: string) => { await nativeStorage.removeItem(key); }, + clear: async () => { + await nativeStorage.clear(); + }, }; export default nativeStorage; diff --git a/src/utils/query.ts b/src/utils/query.ts index e79c7e7e..af538949 100644 --- a/src/utils/query.ts +++ b/src/utils/query.ts @@ -164,8 +164,6 @@ const getServerOmpExtraInfo = async ( } ); - console.log(serverOmpExtraInfo); - let server = getServerFromList(ip, port, listType); if (server) { if (serverOmpExtraInfo === "no_data") {