Fix DB mutex deadlock

This commit is contained in:
Gregory Schier
2024-02-04 21:17:05 -08:00
parent acb01cf086
commit 7bb620e6d5
9 changed files with 312 additions and 348 deletions

View File

@@ -1,12 +0,0 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO grpc_connections (\n id, workspace_id, request_id, service, method\n )\n VALUES ( ?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n service = excluded.service,\n method = excluded.method\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "1ad8a2581417acb2d1b06fc2727a452230ff4ecbdfb5be54a034ec7c4fb1cabe"
}

View File

@@ -0,0 +1,12 @@
{
"db_name": "SQLite",
"query": "\n INSERT INTO grpc_connections (\n id, workspace_id, request_id, service, method\n )\n VALUES (?, ?, ?, ?, ?)\n ON CONFLICT (id) DO UPDATE SET\n updated_at = CURRENT_TIMESTAMP,\n service = excluded.service,\n method = excluded.method\n ",
"describe": {
"columns": [],
"parameters": {
"Right": 5
},
"nullable": []
},
"hash": "f7df06213eff80e2ce5100b77ec244c83de39048e77c5af0b0b5d188d3279ca4"
}

View File

@@ -1,8 +1,6 @@
use prost_reflect::SerializeOptions; use prost_reflect::SerializeOptions;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use tokio_stream::Stream;
use tonic::transport::Uri; use tonic::transport::Uri;
use tonic::IntoRequest;
use crate::proto::fill_pool; use crate::proto::fill_pool;

View File

@@ -1,6 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use hyper::client::connect::Connect;
use hyper::client::HttpConnector; use hyper::client::HttpConnector;
use hyper::Client; use hyper::Client;
use hyper_rustls::HttpsConnector; use hyper_rustls::HttpsConnector;

View File

@@ -366,7 +366,7 @@ pub async fn send_http_request(
.await .await
.expect("Failed to update response"); .expect("Failed to update response");
if !request.id.is_empty() { if !request.id.is_empty() {
emit_side_effect(app_handle.clone(), "updated_model", &response); emit_side_effect(app_handle.clone(), "upserted_model", &response);
} }
// Copy response to download path, if specified // Copy response to download path, if specified
@@ -399,7 +399,7 @@ pub async fn send_http_request(
cookie_jar.cookies = json_cookies; cookie_jar.cookies = json_cookies;
match models::upsert_cookie_jar(db, &cookie_jar).await { match models::upsert_cookie_jar(db, &cookie_jar).await {
Ok(updated_jar) => { Ok(updated_jar) => {
emit_side_effect(app_handle, "updated_model", &updated_jar); emit_side_effect(app_handle, "upserted_model", &updated_jar);
} }
Err(e) => { Err(e) => {
error!("Failed to update cookie jar: {}", e); error!("Failed to update cookie jar: {}", e);

View File

@@ -102,16 +102,14 @@ async fn cmd_grpc_call_unary(
request_id: &str, request_id: &str,
app_handle: AppHandle<Wry>, app_handle: AppHandle<Wry>,
grpc_handle: State<'_, Mutex<GrpcManager>>, grpc_handle: State<'_, Mutex<GrpcManager>>,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<GrpcMessage, String> { ) -> Result<GrpcMessage, String> {
let db = &*db_state.lock().await; let req = get_grpc_request(&app_handle, request_id)
let req = get_grpc_request(db, request_id)
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let conn = { let conn = {
let req = req.clone(); let req = req.clone();
upsert_grpc_connection( upsert_grpc_connection(
db, &app_handle,
&GrpcConnection { &GrpcConnection {
workspace_id: req.workspace_id, workspace_id: req.workspace_id,
request_id: req.id, request_id: req.id,
@@ -127,7 +125,7 @@ async fn cmd_grpc_call_unary(
let req = req.clone(); let req = req.clone();
let conn = conn.clone(); let conn = conn.clone();
upsert_grpc_message( upsert_grpc_message(
db, &app_handle,
&GrpcMessage { &GrpcMessage {
workspace_id: req.workspace_id, workspace_id: req.workspace_id,
request_id: req.id, request_id: req.id,
@@ -157,7 +155,7 @@ async fn cmd_grpc_call_unary(
{ {
Ok(msg) => { Ok(msg) => {
upsert_grpc_message( upsert_grpc_message(
db, &app_handle,
&GrpcMessage { &GrpcMessage {
message: msg, message: msg,
workspace_id: req.workspace_id, workspace_id: req.workspace_id,
@@ -178,18 +176,15 @@ async fn cmd_grpc_call_unary(
#[tauri::command] #[tauri::command]
async fn cmd_grpc_client_streaming( async fn cmd_grpc_client_streaming(
request_id: &str, request_id: &str,
grpc_handle: State<'_, Mutex<GrpcManager>>,
app_handle: AppHandle<Wry>, app_handle: AppHandle<Wry>,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<GrpcConnection, String> { ) -> Result<GrpcConnection, String> {
let db = &*db_state.lock().await; let req = get_grpc_request(&app_handle, request_id)
let req = get_grpc_request(db, request_id)
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let conn = { let conn = {
let req = req.clone(); let req = req.clone();
upsert_grpc_connection( upsert_grpc_connection(
db, &app_handle,
&GrpcConnection { &GrpcConnection {
workspace_id: req.workspace_id, workspace_id: req.workspace_id,
request_id: req.id, request_id: req.id,
@@ -204,24 +199,19 @@ async fn cmd_grpc_client_streaming(
{ {
let conn = conn.clone(); let conn = conn.clone();
let req = req.clone(); let req = req.clone();
let db = db.clone(); upsert_grpc_message(
emit_side_effect( &app_handle,
app_handle.clone(), &GrpcMessage {
"created_model", message: "Initiating connection".to_string(),
upsert_grpc_message( workspace_id: req.workspace_id,
&db, request_id: req.id,
&GrpcMessage { connection_id: conn.id,
message: "Initiating connection".to_string(), is_info: true,
workspace_id: req.workspace_id, ..Default::default()
request_id: req.id, },
connection_id: conn.id, )
is_info: true, .await
..Default::default() .unwrap();
},
)
.await
.expect("Failed to upsert message"),
);
}; };
let (in_msg_tx, in_msg_rx) = tauri::async_runtime::channel::<String>(16); let (in_msg_tx, in_msg_rx) = tauri::async_runtime::channel::<String>(16);
@@ -277,24 +267,18 @@ async fn cmd_grpc_client_streaming(
let req = req.clone(); let req = req.clone();
let conn = conn.clone(); let conn = conn.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>(); upsert_grpc_message(
let db = &*db_state.lock().await; &app_handle,
emit_side_effect( &GrpcMessage {
app_handle.clone(), message: msg,
"created_model", workspace_id: req.workspace_id,
upsert_grpc_message( request_id: req.id,
&db, connection_id: conn.id,
&GrpcMessage { ..Default::default()
message: msg, },
workspace_id: req.workspace_id, )
request_id: req.id, .await
connection_id: conn.id, .unwrap();
..Default::default()
},
)
.await
.expect("Failed to upsert message"),
);
}); });
} }
Ok(IncomingMsg::Commit) => { Ok(IncomingMsg::Commit) => {
@@ -318,79 +302,61 @@ async fn cmd_grpc_client_streaming(
let req = req.clone(); let req = req.clone();
async move { async move {
let grpc_handle = app_handle.state::<Mutex<GrpcManager>>(); let grpc_handle = app_handle.state::<Mutex<GrpcManager>>();
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>();
let msg = grpc_handle let msg = grpc_handle
.lock() .lock()
.await .await
.client_streaming(&conn.id, uri, &service, &method, in_msg_stream) .client_streaming(&conn.id, uri, &service, &method, in_msg_stream)
.await .await
.unwrap(); .unwrap();
let db = &*db_state.lock().await; upsert_grpc_message(
emit_side_effect( &app_handle,
app_handle.clone(), &GrpcMessage {
"created_model", message: msg.to_string(),
upsert_grpc_message( workspace_id: req.workspace_id,
db, request_id: req.id,
&GrpcMessage { connection_id: conn.id,
message: msg.to_string(), is_server: true,
workspace_id: req.workspace_id, ..Default::default()
request_id: req.id, },
connection_id: conn.id, )
is_server: true, .await
..Default::default() .unwrap();
},
)
.await
.expect("Failed to upsert message"),
);
} }
}; };
{ {
let conn = conn.clone(); let conn = conn.clone();
let app_handle = app_handle.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
tokio::select! { tokio::select! {
_ = grpc_listen => { _ = grpc_listen => {
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>(); upsert_grpc_message(
let db = &*db_state.lock().await; &app_handle,
emit_side_effect( &GrpcMessage {
app_handle.clone(), message: "Connection completed".to_string(),
"created_model", workspace_id: req.workspace_id,
upsert_grpc_message( request_id: req.id,
&db, connection_id: conn.id,
&GrpcMessage { is_info: true,
message: "Connection completed".to_string(), ..Default::default()
workspace_id: req.workspace_id, },
request_id: req.id, )
connection_id: conn.id, .await.map_err(|e| e.to_string()).unwrap();
is_info: true,
..Default::default()
},
)
.await
.expect("Failed to upsert message"),
);
}, },
_ = cancelled_rx.changed() => { _ = cancelled_rx.changed() => {
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>(); upsert_grpc_message(
let db = &*db_state.lock().await; &app_handle,
emit_side_effect( &GrpcMessage {
app_handle.clone(), message: "Connection cancelled".to_string(),
"created_model", workspace_id: req.workspace_id,
upsert_grpc_message( request_id: req.id,
&db, connection_id: conn.id,
&GrpcMessage { is_info: true,
message: "Connection cancelled".to_string(), ..Default::default()
workspace_id: req.workspace_id, },
request_id: req.id, )
connection_id: conn.id, .await
is_info: true, .map_err(|e| e.to_string()).unwrap();
..Default::default()
},
)
.await
.expect("Failed to upsert message"),
);
}, },
} }
app_handle.unlisten(event_handler); app_handle.unlisten(event_handler);
@@ -405,16 +371,14 @@ async fn cmd_grpc_streaming(
request_id: &str, request_id: &str,
app_handle: AppHandle<Wry>, app_handle: AppHandle<Wry>,
grpc_handle: State<'_, Mutex<GrpcManager>>, grpc_handle: State<'_, Mutex<GrpcManager>>,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<String, String> { ) -> Result<String, String> {
let db = &*db_state.lock().await; let req = get_grpc_request(&app_handle, request_id)
let req = get_grpc_request(db, request_id)
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let conn = { let conn = {
let req = req.clone(); let req = req.clone();
upsert_grpc_connection( upsert_grpc_connection(
db, &app_handle,
&GrpcConnection { &GrpcConnection {
workspace_id: req.workspace_id, workspace_id: req.workspace_id,
request_id: req.id, request_id: req.id,
@@ -429,24 +393,19 @@ async fn cmd_grpc_streaming(
{ {
let conn = conn.clone(); let conn = conn.clone();
let req = req.clone(); let req = req.clone();
let db = db.clone(); upsert_grpc_message(
emit_side_effect( &app_handle,
app_handle.clone(), &GrpcMessage {
"created_model", message: "Initiating connection".to_string(),
upsert_grpc_message( workspace_id: req.workspace_id,
&db, request_id: req.id,
&GrpcMessage { connection_id: conn.id,
message: "Initiating connection".to_string(), is_info: true,
workspace_id: req.workspace_id, ..Default::default()
request_id: req.id, },
connection_id: conn.id, )
is_info: true, .await
..Default::default() .expect("Failed to upsert message");
},
)
.await
.expect("Failed to upsert message"),
);
}; };
let (in_msg_tx, in_msg_rx) = tauri::async_runtime::channel::<String>(16); let (in_msg_tx, in_msg_rx) = tauri::async_runtime::channel::<String>(16);
@@ -496,24 +455,19 @@ async fn cmd_grpc_streaming(
let req = req.clone(); let req = req.clone();
let conn = conn.clone(); let conn = conn.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>(); upsert_grpc_message(
let db = &*db_state.lock().await; &app_handle,
emit_side_effect( &GrpcMessage {
app_handle.clone(), message: msg,
"created_model", workspace_id: req.workspace_id,
upsert_grpc_message( request_id: req.id,
&db, connection_id: conn.id,
&GrpcMessage { ..Default::default()
message: msg, },
workspace_id: req.workspace_id, )
request_id: req.id, .await
connection_id: conn.id, .map_err(|e| e.to_string())
..Default::default() .unwrap();
},
)
.await
.expect("Failed to upsert message"),
);
}); });
} }
Ok(IncomingMsg::Cancel) => { Ok(IncomingMsg::Cancel) => {
@@ -539,25 +493,20 @@ async fn cmd_grpc_streaming(
let item = serde_json::to_string_pretty(&item).unwrap(); let item = serde_json::to_string_pretty(&item).unwrap();
let req = req.clone(); let req = req.clone();
let conn = conn.clone(); let conn = conn.clone();
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>(); upsert_grpc_message(
let db = &*db_state.lock().await; &app_handle,
emit_side_effect( &GrpcMessage {
app_handle.clone(), message: item,
"created_model", workspace_id: req.workspace_id,
upsert_grpc_message( request_id: req.id,
&db, connection_id: conn.id,
&GrpcMessage { is_server: true,
message: item, ..Default::default()
workspace_id: req.workspace_id, },
request_id: req.id, )
connection_id: conn.id, .await
is_server: true, .map_err(|e| e.to_string())
..Default::default() .unwrap();
},
)
.await
.expect("Failed to upsert message"),
);
} }
Some(Err(e)) => { Some(Err(e)) => {
error!("gRPC stream error: {:?}", e); error!("gRPC stream error: {:?}", e);
@@ -575,36 +524,25 @@ async fn cmd_grpc_streaming(
{ {
let conn = conn.clone(); let conn = conn.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
let app_handle = app_handle.clone();
tokio::select! { tokio::select! {
_ = grpc_listen => { _ = grpc_listen => {
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>(); upsert_grpc_message(
let db = &*db_state.lock().await; &app_handle,
emit_side_effect( &GrpcMessage {
app_handle.clone(), message: "Connection completed".to_string(),
"created_model", workspace_id: req.workspace_id,
upsert_grpc_message( request_id: req.id,
&db, connection_id: conn.id,
&GrpcMessage { is_info: true,
message: "Connection completed".to_string(), ..Default::default()
workspace_id: req.workspace_id, },
request_id: req.id, )
connection_id: conn.id, .await.map_err(|e| e.to_string()).unwrap();
is_info: true,
..Default::default()
},
)
.await
.expect("Failed to upsert message"),
);
}, },
_ = cancelled_rx.changed() => { _ = cancelled_rx.changed() => {
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>();
let db = &*db_state.lock().await;
emit_side_effect(
app_handle.clone(),
"created_model",
upsert_grpc_message( upsert_grpc_message(
&db, &app_handle,
&GrpcMessage { &GrpcMessage {
message: "Connection cancelled".to_string(), message: "Connection cancelled".to_string(),
workspace_id: req.workspace_id, workspace_id: req.workspace_id,
@@ -614,9 +552,7 @@ async fn cmd_grpc_streaming(
..Default::default() ..Default::default()
}, },
) )
.await .await.map_err(|e| e.to_string()).unwrap();
.expect("Failed to upsert message"),
);
}, },
} }
app_handle.unlisten(event_handler); app_handle.unlisten(event_handler);
@@ -631,17 +567,15 @@ async fn cmd_grpc_server_streaming(
request_id: &str, request_id: &str,
app_handle: AppHandle<Wry>, app_handle: AppHandle<Wry>,
grpc_handle: State<'_, Mutex<GrpcManager>>, grpc_handle: State<'_, Mutex<GrpcManager>>,
db_state: State<'_, Mutex<Pool<Sqlite>>>,
) -> Result<GrpcConnection, String> { ) -> Result<GrpcConnection, String> {
let db = &*db_state.lock().await; let req = get_grpc_request(&app_handle, request_id)
let req = get_grpc_request(db, request_id)
.await .await
.map_err(|e| e.to_string())?; .map_err(|e| e.to_string())?;
let conn = { let conn = {
let req = req.clone(); let req = req.clone();
upsert_grpc_connection( upsert_grpc_connection(
db, &app_handle,
&GrpcConnection { &GrpcConnection {
workspace_id: req.workspace_id, workspace_id: req.workspace_id,
request_id: req.id, request_id: req.id,
@@ -656,23 +590,19 @@ async fn cmd_grpc_server_streaming(
{ {
let req = req.clone(); let req = req.clone();
let conn = conn.clone(); let conn = conn.clone();
emit_side_effect( upsert_grpc_message(
app_handle.clone(), &app_handle,
"created_model", &GrpcMessage {
upsert_grpc_message( message: "Initiating connection".to_string(),
&db, workspace_id: req.workspace_id,
&GrpcMessage { request_id: req.id,
message: "Initiating connection".to_string(), connection_id: conn.id,
workspace_id: req.workspace_id, is_info: true,
request_id: req.id, ..Default::default()
connection_id: conn.id, },
is_info: true, )
..Default::default() .await
}, .unwrap();
)
.await
.expect("Failed to upsert message"),
);
} }
let (cancelled_tx, mut cancelled_rx) = tokio::sync::watch::channel(false); let (cancelled_tx, mut cancelled_rx) = tokio::sync::watch::channel(false);
@@ -718,7 +648,6 @@ async fn cmd_grpc_server_streaming(
app_handle.listen_global(format!("grpc_client_msg_{}", conn.id).as_str(), cb); app_handle.listen_global(format!("grpc_client_msg_{}", conn.id).as_str(), cb);
let grpc_listen = { let grpc_listen = {
let db = db.clone();
let conn_id = conn.clone().id; let conn_id = conn.clone().id;
let app_handle = app_handle.clone(); let app_handle = app_handle.clone();
let req = req.clone(); let req = req.clone();
@@ -726,11 +655,12 @@ async fn cmd_grpc_server_streaming(
loop { loop {
let req = req.clone(); let req = req.clone();
let conn_id = conn_id.clone(); let conn_id = conn_id.clone();
let app_handle = app_handle.clone();
match stream.next().await { match stream.next().await {
Some(Ok(item)) => { Some(Ok(item)) => {
let item = serde_json::to_string_pretty(&item).unwrap(); let item = serde_json::to_string_pretty(&item).unwrap();
let msg = upsert_grpc_message( upsert_grpc_message(
&db, &app_handle,
&GrpcMessage { &GrpcMessage {
message: item, message: item,
workspace_id: req.workspace_id, workspace_id: req.workspace_id,
@@ -743,7 +673,6 @@ async fn cmd_grpc_server_streaming(
.await .await
.map_err(|e| e.to_string()) .map_err(|e| e.to_string())
.expect("Failed to upsert message"); .expect("Failed to upsert message");
emit_side_effect(app_handle.clone(), "created_model", msg);
} }
Some(Err(e)) => { Some(Err(e)) => {
error!("gRPC stream error: {:?}", e); error!("gRPC stream error: {:?}", e);
@@ -761,16 +690,12 @@ async fn cmd_grpc_server_streaming(
{ {
let conn = conn.clone(); let conn = conn.clone();
let req = req.clone(); let req = req.clone();
let app_handle = app_handle.clone();
tauri::async_runtime::spawn(async move { tauri::async_runtime::spawn(async move {
tokio::select! { tokio::select! {
_ = grpc_listen => { _ = grpc_listen => {
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>();
let db = &*db_state.lock().await;
emit_side_effect(
app_handle.clone(),
"created_model",
upsert_grpc_message( upsert_grpc_message(
&db, &app_handle,
&GrpcMessage { &GrpcMessage {
message: "Connection completed".to_string(), message: "Connection completed".to_string(),
workspace_id: req.workspace_id, workspace_id: req.workspace_id,
@@ -780,18 +705,11 @@ async fn cmd_grpc_server_streaming(
..Default::default() ..Default::default()
}, },
) )
.await .await.unwrap();
.expect("Failed to upsert message"),
);
}, },
_ = cancelled_rx.changed() => { _ = cancelled_rx.changed() => {
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>();
let db = &*db_state.lock().await;
emit_side_effect(
app_handle.clone(),
"created_model",
upsert_grpc_message( upsert_grpc_message(
&db, &app_handle,
&GrpcMessage { &GrpcMessage {
message: "Connection cancelled".to_string(), message: "Connection cancelled".to_string(),
workspace_id: req.workspace_id, workspace_id: req.workspace_id,
@@ -801,9 +719,7 @@ async fn cmd_grpc_server_streaming(
..Default::default() ..Default::default()
}, },
) )
.await .await.unwrap();
.expect("Failed to upsert message"),
);
}, },
} }
app_handle.unlisten(event_handler); app_handle.unlisten(event_handler);
@@ -1076,7 +992,7 @@ async fn response_err(
response = update_response_if_id(db, &response) response = update_response_if_id(db, &response)
.await .await
.expect("Failed to update response"); .expect("Failed to update response");
emit_side_effect(app_handle, "updated_model", &response); emit_side_effect(app_handle, "upserted_model", &response);
Ok(response) Ok(response)
} }
@@ -1136,7 +1052,7 @@ async fn cmd_set_key_value(
if created { if created {
emit_and_return(&window, "created_model", key_value) emit_and_return(&window, "created_model", key_value)
} else { } else {
emit_and_return(&window, "updated_model", key_value) emit_and_return(&window, "upserted_model", key_value)
} }
} }
@@ -1166,7 +1082,7 @@ async fn cmd_update_cookie_jar(
.await .await
.expect("Failed to update cookie jar"); .expect("Failed to update cookie jar");
emit_and_return(&window, "updated_model", updated) emit_and_return(&window, "upserted_model", updated)
} }
#[tauri::command] #[tauri::command]
@@ -1235,11 +1151,10 @@ async fn cmd_create_grpc_request(
sort_priority: f64, sort_priority: f64,
folder_id: Option<&str>, folder_id: Option<&str>,
window: Window<Wry>, window: Window<Wry>,
db_state: State<'_, Mutex<Pool<Sqlite>>>, app_handle: AppHandle,
) -> Result<GrpcRequest, String> { ) -> Result<GrpcRequest, String> {
let db = &*db_state.lock().await;
let created = upsert_grpc_request( let created = upsert_grpc_request(
db, &app_handle,
&GrpcRequest { &GrpcRequest {
workspace_id: workspace_id.to_string(), workspace_id: workspace_id.to_string(),
name: name.to_string(), name: name.to_string(),
@@ -1258,10 +1173,9 @@ async fn cmd_create_grpc_request(
async fn cmd_duplicate_grpc_request( async fn cmd_duplicate_grpc_request(
id: &str, id: &str,
window: Window<Wry>, window: Window<Wry>,
db_state: State<'_, Mutex<Pool<Sqlite>>>, app_handle: AppHandle,
) -> Result<GrpcRequest, String> { ) -> Result<GrpcRequest, String> {
let db = &*db_state.lock().await; let request = duplicate_grpc_request(&app_handle, id)
let request = duplicate_grpc_request(db, id)
.await .await
.expect("Failed to duplicate grpc request"); .expect("Failed to duplicate grpc request");
emit_and_return(&window, "created_model", request) emit_and_return(&window, "created_model", request)
@@ -1318,7 +1232,7 @@ async fn cmd_update_workspace(
.await .await
.expect("Failed to update request"); .expect("Failed to update request");
emit_and_return(&window, "updated_model", updated_workspace) emit_and_return(&window, "upserted_model", updated_workspace)
} }
#[tauri::command] #[tauri::command]
@@ -1332,20 +1246,19 @@ async fn cmd_update_environment(
.await .await
.expect("Failed to update environment"); .expect("Failed to update environment");
emit_and_return(&window, "updated_model", updated_environment) emit_and_return(&window, "upserted_model", updated_environment)
} }
#[tauri::command] #[tauri::command]
async fn cmd_update_grpc_request( async fn cmd_update_grpc_request(
request: GrpcRequest, request: GrpcRequest,
window: Window<Wry>, window: Window<Wry>,
db_state: State<'_, Mutex<Pool<Sqlite>>>, app_handle: AppHandle,
) -> Result<GrpcRequest, String> { ) -> Result<GrpcRequest, String> {
let db = &*db_state.lock().await; let updated_request = upsert_grpc_request(&app_handle, &request)
let updated_request = upsert_grpc_request(db, &request)
.await .await
.expect("Failed to update grpc request"); .expect("Failed to update grpc request");
emit_and_return(&window, "updated_model", updated_request) emit_and_return(&window, "upserted_model", updated_request)
} }
#[tauri::command] #[tauri::command]
@@ -1358,7 +1271,7 @@ async fn cmd_update_http_request(
let updated_request = upsert_http_request(db, request) let updated_request = upsert_http_request(db, request)
.await .await
.expect("Failed to update request"); .expect("Failed to update request");
emit_and_return(&window, "updated_model", updated_request) emit_and_return(&window, "upserted_model", updated_request)
} }
#[tauri::command] #[tauri::command]
@@ -1434,7 +1347,7 @@ async fn cmd_update_folder(
let updated_folder = upsert_folder(db, folder) let updated_folder = upsert_folder(db, folder)
.await .await
.expect("Failed to update request"); .expect("Failed to update request");
emit_and_return(&window, "updated_model", updated_folder) emit_and_return(&window, "upserted_model", updated_folder)
} }
#[tauri::command] #[tauri::command]
@@ -1540,7 +1453,7 @@ async fn cmd_update_settings(
.await .await
.expect("Failed to update settings"); .expect("Failed to update settings");
emit_and_return(&window, "updated_model", updated_settings) emit_and_return(&window, "upserted_model", updated_settings)
} }
#[tauri::command] #[tauri::command]
@@ -1553,12 +1466,10 @@ async fn cmd_get_folder(
} }
#[tauri::command] #[tauri::command]
async fn cmd_get_grpc_request( async fn cmd_get_grpc_request(id: &str, app_handle: AppHandle<Wry>) -> Result<GrpcRequest, String> {
id: &str, get_grpc_request(&app_handle, id)
db_state: State<'_, Mutex<Pool<Sqlite>>>, .await
) -> Result<GrpcRequest, String> { .map_err(|e| e.to_string())
let db = &*db_state.lock().await;
get_grpc_request(db, id).await.map_err(|e| e.to_string())
} }
#[tauri::command] #[tauri::command]

View File

@@ -7,7 +7,8 @@ use serde::{Deserialize, Serialize};
use sqlx::types::chrono::NaiveDateTime; use sqlx::types::chrono::NaiveDateTime;
use sqlx::types::{Json, JsonValue}; use sqlx::types::{Json, JsonValue};
use sqlx::{Pool, Sqlite}; use sqlx::{Pool, Sqlite};
use tauri::AppHandle; use tauri::{AppHandle, Manager};
use tokio::sync::Mutex;
fn default_true() -> bool { fn default_true() -> bool {
true true
@@ -457,18 +458,19 @@ pub async fn delete_cookie_jar(db: &Pool<Sqlite>, id: &str) -> Result<CookieJar,
} }
pub async fn duplicate_grpc_request( pub async fn duplicate_grpc_request(
db: &Pool<Sqlite>, app_handle: &AppHandle,
id: &str, id: &str,
) -> Result<GrpcRequest, sqlx::Error> { ) -> Result<GrpcRequest, sqlx::Error> {
let mut request = get_grpc_request(db, id).await?.clone(); let mut request = get_grpc_request(app_handle, id).await?.clone();
request.id = "".to_string(); request.id = "".to_string();
upsert_grpc_request(db, &request).await upsert_grpc_request(app_handle, &request).await
} }
pub async fn upsert_grpc_request( pub async fn upsert_grpc_request(
db: &Pool<Sqlite>, app_handle: &AppHandle,
request: &GrpcRequest, request: &GrpcRequest,
) -> Result<GrpcRequest, sqlx::Error> { ) -> Result<GrpcRequest, sqlx::Error> {
let db = get_db(app_handle).await;
let id = match request.id.as_str() { let id = match request.id.as_str() {
"" => generate_id(Some("gr")), "" => generate_id(Some("gr")),
_ => request.id.to_string(), _ => request.id.to_string(),
@@ -500,13 +502,19 @@ pub async fn upsert_grpc_request(
request.method, request.method,
request.message, request.message,
) )
.execute(db) .execute(&db)
.await?; .await?;
get_grpc_request(db, &id).await get_grpc_request(app_handle, &id).await
} }
pub async fn get_grpc_request(db: &Pool<Sqlite>, id: &str) -> Result<GrpcRequest, sqlx::Error> { pub async fn get_grpc_request(
app_handle: &AppHandle,
id: &str,
) -> Result<GrpcRequest, sqlx::Error> {
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>();
let db = &*db_state.lock().await;
sqlx::query_as!( sqlx::query_as!(
GrpcRequest, GrpcRequest,
r#" r#"
@@ -542,9 +550,10 @@ pub async fn list_grpc_requests(
} }
pub async fn upsert_grpc_connection( pub async fn upsert_grpc_connection(
db: &Pool<Sqlite>, app_handle: &AppHandle,
connection: &GrpcConnection, connection: &GrpcConnection,
) -> Result<GrpcConnection, sqlx::Error> { ) -> Result<GrpcConnection, sqlx::Error> {
let db = get_db(&app_handle).await;
let id = match connection.id.as_str() { let id = match connection.id.as_str() {
"" => generate_id(Some("gc")), "" => generate_id(Some("gc")),
_ => connection.id.to_string(), _ => connection.id.to_string(),
@@ -554,7 +563,7 @@ pub async fn upsert_grpc_connection(
INSERT INTO grpc_connections ( INSERT INTO grpc_connections (
id, workspace_id, request_id, service, method id, workspace_id, request_id, service, method
) )
VALUES ( ?, ?, ?, ?, ?) VALUES (?, ?, ?, ?, ?)
ON CONFLICT (id) DO UPDATE SET ON CONFLICT (id) DO UPDATE SET
updated_at = CURRENT_TIMESTAMP, updated_at = CURRENT_TIMESTAMP,
service = excluded.service, service = excluded.service,
@@ -566,16 +575,17 @@ pub async fn upsert_grpc_connection(
connection.service, connection.service,
connection.method, connection.method,
) )
.execute(db) .execute(&db)
.await?; .await?;
get_grpc_connection(db, &id).await get_grpc_connection(app_handle, &id).await
} }
pub async fn get_grpc_connection( pub async fn get_grpc_connection(
db: &Pool<Sqlite>, app_handle: &AppHandle,
id: &str, id: &str,
) -> Result<GrpcConnection, sqlx::Error> { ) -> Result<GrpcConnection, sqlx::Error> {
let db = get_db(&app_handle).await;
sqlx::query_as!( sqlx::query_as!(
GrpcConnection, GrpcConnection,
r#" r#"
@@ -585,7 +595,7 @@ pub async fn get_grpc_connection(
"#, "#,
id, id,
) )
.fetch_one(db) .fetch_one(&db)
.await .await
} }
@@ -608,9 +618,10 @@ pub async fn list_grpc_connections(
} }
pub async fn upsert_grpc_message( pub async fn upsert_grpc_message(
db: &Pool<Sqlite>, app_handle: &AppHandle,
message: &GrpcMessage, message: &GrpcMessage,
) -> Result<GrpcMessage, sqlx::Error> { ) -> Result<GrpcMessage, sqlx::Error> {
let db = get_db(app_handle).await;
let id = match message.id.as_str() { let id = match message.id.as_str() {
"" => generate_id(Some("gm")), "" => generate_id(Some("gm")),
_ => message.id.to_string(), _ => message.id.to_string(),
@@ -635,13 +646,21 @@ pub async fn upsert_grpc_message(
message.is_server, message.is_server,
message.is_info, message.is_info,
) )
.execute(db) .execute(&db)
.await?; .await?;
get_grpc_message(db, &id).await let msg = get_grpc_message(app_handle, &id).await;
match msg {
Ok(msg) => Ok(emit_upserted_model(app_handle, msg.clone()).await),
Err(e) => Err(e),
}
} }
pub async fn get_grpc_message(db: &Pool<Sqlite>, id: &str) -> Result<GrpcMessage, sqlx::Error> { pub async fn get_grpc_message(
app_handle: &AppHandle,
id: &str,
) -> Result<GrpcMessage, sqlx::Error> {
let db = get_db(app_handle).await;
sqlx::query_as!( sqlx::query_as!(
GrpcMessage, GrpcMessage,
r#" r#"
@@ -653,7 +672,7 @@ pub async fn get_grpc_message(db: &Pool<Sqlite>, id: &str) -> Result<GrpcMessage
"#, "#,
id, id,
) )
.fetch_one(db) .fetch_one(&db)
.await .await
} }
@@ -1327,3 +1346,16 @@ pub async fn get_workspace_export_resources(
}, },
}; };
} }
async fn emit_upserted_model<S: Serialize + Clone>(app_handle: &AppHandle, model: S) -> S {
app_handle
.emit_all("upserted_model", model.clone())
.unwrap();
model
}
async fn get_db(app_handle: &AppHandle) -> Pool<Sqlite> {
let db_state = app_handle.state::<Mutex<Pool<Sqlite>>>();
let db = &*db_state.lock().await;
db.clone()
}

View File

@@ -86,7 +86,7 @@ export function GlobalHooks() {
} }
}); });
useListenToTauriEvent<Model>('updated_model', ({ payload, windowLabel }) => { useListenToTauriEvent<Model>('upserted_model', ({ payload, windowLabel }) => {
if (shouldIgnoreEvent(payload, windowLabel)) return; if (shouldIgnoreEvent(payload, windowLabel)) return;
const queryKey = const queryKey =
@@ -119,12 +119,21 @@ export function GlobalHooks() {
wasUpdatedExternally(payload.id); wasUpdatedExternally(payload.id);
} }
const pushToFront = (['http_response', 'grpc_connection'] as Model['model'][]).includes(
payload.model,
);
if (!shouldIgnoreModel(payload)) { if (!shouldIgnoreModel(payload)) {
console.time('set query date'); queryClient.setQueryData<Model[]>(queryKey, (values = []) => {
queryClient.setQueryData<Model[]>(queryKey, (values) => const index = values.findIndex((v) => modelsEq(v, payload)) ?? -1;
values?.map((v) => (modelsEq(v, payload) ? payload : v)), if (index >= 0) {
); console.log('UPDATED MODEL', payload);
console.timeEnd('set query date'); return [...values.slice(0, index), payload, ...values.slice(index + 1)];
} else {
console.log('INSERTED MODEL', payload);
return pushToFront ? [payload, ...(values ?? [])] : [...(values ?? []), payload];
}
});
} }
}); });

View File

@@ -19,6 +19,7 @@ import { RadioDropdown } from './core/RadioDropdown';
import { Separator } from './core/Separator'; import { Separator } from './core/Separator';
import { SplitLayout } from './core/SplitLayout'; import { SplitLayout } from './core/SplitLayout';
import { HStack, VStack } from './core/Stacks'; import { HStack, VStack } from './core/Stacks';
import { StatusTag } from './core/StatusTag';
import { GrpcEditor } from './GrpcEditor'; import { GrpcEditor } from './GrpcEditor';
import { UrlBar } from './UrlBar'; import { UrlBar } from './UrlBar';
@@ -200,22 +201,22 @@ export function GrpcConnectionLayout({ style }: Props) {
{select.options.find((o) => o.value === select.value)?.label} {select.options.find((o) => o.value === select.value)?.label}
</Button> </Button>
</RadioDropdown> </RadioDropdown>
<IconButton {!grpc.isStreaming && (
className="border border-highlight" <IconButton
size="sm" className="border border-highlight"
title={messageType === 'unary' ? 'Send' : 'Connect'} size="sm"
hotkeyAction={grpc.isStreaming ? undefined : 'http_request.send'} title={messageType === 'unary' ? 'Send' : 'Connect'}
onClick={grpc.isStreaming ? () => grpc.cancel.mutateAsync() : handleConnect} hotkeyAction={grpc.isStreaming ? undefined : 'http_request.send'}
disabled={grpc.isStreaming} onClick={handleConnect}
spin={grpc.isStreaming || grpc.unary.isLoading} icon={
icon={ grpc.isStreaming
grpc.isStreaming ? 'refresh'
? 'refresh' : messageType === 'unary'
: messageType === 'unary' ? 'sendHorizontal'
? 'sendHorizontal' : 'arrowUpDown'
: 'arrowUpDown' }
} />
/> )}
{grpc.isStreaming && ( {grpc.isStreaming && (
<IconButton <IconButton
className="border border-highlight" className="border border-highlight"
@@ -285,39 +286,53 @@ export function GrpcConnectionLayout({ style }: Props) {
} }
minHeightPx={20} minHeightPx={20}
firstSlot={() => ( firstSlot={() => (
<div className="overflow-y-auto w-full"> <div className="w-full grid grid-rows-[auto_minmax(0,1fr)]">
{...messages.map((m) => ( <HStack className="px-3 mb-2">
<HStack <div className="font-mono">
key={m.id} {grpc.isStreaming ? (
space={2} <HStack alignItems="center" space={2}>
onClick={() => { <Icon icon="refresh" size="sm" spin />
if (m.id === activeMessageId) setActiveMessageId(null); Connected
else setActiveMessageId(m.id); </HStack>
}} ) : (
alignItems="center" 'Done'
className={classNames(
'px-2 py-1 font-mono',
m === activeMessage && 'bg-highlight',
)} )}
> </div>
<Icon </HStack>
className={ <div className="overflow-y-auto h-full">
m.isInfo {...messages.map((m) => (
? 'text-gray-600' <HStack
: m.isServer key={m.id}
? 'text-blue-600' space={2}
: 'text-green-600' onClick={() => {
} if (m.id === activeMessageId) setActiveMessageId(null);
icon={ else setActiveMessageId(m.id);
m.isInfo ? 'info' : m.isServer ? 'arrowBigDownDash' : 'arrowBigUpDash' }}
} alignItems="center"
/> className={classNames(
<div className="w-full truncate text-gray-800 text-2xs">{m.message}</div> 'px-2 py-1 font-mono',
<div className="text-gray-600 text-2xs"> m === activeMessage && 'bg-highlight',
{format(m.createdAt, 'HH:mm:ss')} )}
</div> >
</HStack> <Icon
))} className={
m.isInfo
? 'text-gray-600'
: m.isServer
? 'text-blue-600'
: 'text-green-600'
}
icon={
m.isInfo ? 'info' : m.isServer ? 'arrowBigDownDash' : 'arrowBigUpDash'
}
/>
<div className="w-full truncate text-gray-800 text-2xs">{m.message}</div>
<div className="text-gray-600 text-2xs">
{format(m.createdAt, 'HH:mm:ss')}
</div>
</HStack>
))}
</div>
</div> </div>
)} )}
secondSlot={ secondSlot={