Rust service refactor: friends & some other
This commit is contained in:
@@ -2,7 +2,7 @@ use crate::{
|
|||||||
lock_r,
|
lock_r,
|
||||||
models::app_data::UserData,
|
models::app_data::UserData,
|
||||||
services::{
|
services::{
|
||||||
friend_active_doll_sprite, presence_modules::models::ModuleMetadata, sprite,
|
friends, presence_modules::models::ModuleMetadata, sprite,
|
||||||
},
|
},
|
||||||
state::{init_app_data_scoped, AppDataRefreshScope, FDOLL},
|
state::{init_app_data_scoped, AppDataRefreshScope, FDOLL},
|
||||||
};
|
};
|
||||||
@@ -38,7 +38,7 @@ pub fn get_active_doll_sprite_base64() -> Result<Option<String>, String> {
|
|||||||
#[tauri::command]
|
#[tauri::command]
|
||||||
#[specta::specta]
|
#[specta::specta]
|
||||||
pub fn get_friend_active_doll_sprites_base64(
|
pub fn get_friend_active_doll_sprites_base64(
|
||||||
) -> Result<friend_active_doll_sprite::FriendActiveDollSpritesDto, String> {
|
) -> Result<friends::FriendActiveDollSpritesDto, String> {
|
||||||
friend_active_doll_sprite::sync_from_app_data();
|
friends::sync_active_doll_sprites_from_app_data();
|
||||||
Ok(friend_active_doll_sprite::get_snapshot())
|
Ok(friends::get_active_doll_sprites_snapshot())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -53,7 +53,7 @@ async fn disconnect_user_profile() {
|
|||||||
|
|
||||||
/// Destructs the user session and show health manager window
|
/// Destructs the user session and show health manager window
|
||||||
/// with error message, offering troubleshooting options.
|
/// with error message, offering troubleshooting options.
|
||||||
pub async fn handle_disasterous_failure(error_message: Option<String>) {
|
pub async fn handle_disastrous_failure(error_message: Option<String>) {
|
||||||
destruct_user_session().await;
|
destruct_user_session().await;
|
||||||
open_health_manager_window(error_message);
|
open_health_manager_window(error_message);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
init::{
|
init::{
|
||||||
lifecycle::{construct_user_session, handle_disasterous_failure, validate_server_health},
|
lifecycle::{construct_user_session, handle_disastrous_failure, validate_server_health},
|
||||||
tracing::init_logging,
|
tracing::init_logging,
|
||||||
},
|
},
|
||||||
services::{
|
services::{
|
||||||
@@ -28,7 +28,7 @@ pub async fn launch_app() {
|
|||||||
init_modules();
|
init_modules();
|
||||||
|
|
||||||
if let Err(err) = validate_server_health().await {
|
if let Err(err) = validate_server_health().await {
|
||||||
handle_disasterous_failure(Some(err.to_string())).await;
|
handle_disastrous_failure(Some(err.to_string())).await;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -13,8 +13,8 @@ use crate::{
|
|||||||
interaction::{InteractionDeliveryFailedDto, InteractionPayloadDto},
|
interaction::{InteractionDeliveryFailedDto, InteractionPayloadDto},
|
||||||
},
|
},
|
||||||
services::{
|
services::{
|
||||||
cursor::CursorPositions, friend_active_doll_sprite::FriendActiveDollSpritesDto,
|
cursor::CursorPositions,
|
||||||
friend_cursor::FriendCursorPositionsDto,
|
friends::{FriendActiveDollSpritesDto, FriendCursorPositionsDto},
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|||||||
@@ -103,10 +103,8 @@ pub fn set_active_doll(user_id: &str, has_active_doll: bool) {
|
|||||||
.active_dolls
|
.active_dolls
|
||||||
.insert(user_id.to_string(), has_active_doll);
|
.insert(user_id.to_string(), has_active_doll);
|
||||||
|
|
||||||
if !has_active_doll {
|
if !has_active_doll && projection.positions.remove(user_id).is_some() {
|
||||||
if projection.positions.remove(user_id).is_some() {
|
emit_snapshot(&projection.positions);
|
||||||
emit_snapshot(&projection.positions);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
39
src-tauri/src/services/friends/mod.rs
Normal file
39
src-tauri/src/services/friends/mod.rs
Normal file
@@ -0,0 +1,39 @@
|
|||||||
|
mod active_doll_sprites;
|
||||||
|
mod cursor_positions;
|
||||||
|
|
||||||
|
use crate::{models::dolls::DollDto, services::cursor::CursorPositions};
|
||||||
|
|
||||||
|
pub use active_doll_sprites::FriendActiveDollSpritesDto;
|
||||||
|
pub use cursor_positions::FriendCursorPositionsDto;
|
||||||
|
|
||||||
|
pub fn sync_from_app_data() {
|
||||||
|
active_doll_sprites::sync_from_app_data();
|
||||||
|
cursor_positions::sync_from_app_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear() {
|
||||||
|
active_doll_sprites::clear();
|
||||||
|
cursor_positions::clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn remove_friend(user_id: &str) {
|
||||||
|
active_doll_sprites::remove_friend(user_id);
|
||||||
|
cursor_positions::remove_friend(user_id);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_active_doll(user_id: &str, doll: Option<&DollDto>) {
|
||||||
|
active_doll_sprites::set_active_doll(user_id, doll);
|
||||||
|
cursor_positions::set_active_doll(user_id, doll.is_some());
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn update_cursor_position(user_id: String, position: CursorPositions) {
|
||||||
|
cursor_positions::update_position(user_id, position);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sync_active_doll_sprites_from_app_data() {
|
||||||
|
active_doll_sprites::sync_from_app_data();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_active_doll_sprites_snapshot() -> FriendActiveDollSpritesDto {
|
||||||
|
active_doll_sprites::get_snapshot()
|
||||||
|
}
|
||||||
@@ -1,5 +1,5 @@
|
|||||||
use crate::{
|
use crate::{
|
||||||
init::lifecycle::{handle_disasterous_failure, validate_server_health},
|
init::lifecycle::{handle_disastrous_failure, validate_server_health},
|
||||||
lock_w,
|
lock_w,
|
||||||
services::ws::client::establish_websocket_connection,
|
services::ws::client::establish_websocket_connection,
|
||||||
state::FDOLL,
|
state::FDOLL,
|
||||||
@@ -44,7 +44,7 @@ pub async fn start_health_monitor() {
|
|||||||
|
|
||||||
if consecutive_failures >= MAX_FAILURES {
|
if consecutive_failures >= MAX_FAILURES {
|
||||||
info!("Server appears unreachable after {} attempts, triggering recovery", MAX_FAILURES);
|
info!("Server appears unreachable after {} attempts, triggering recovery", MAX_FAILURES);
|
||||||
handle_disasterous_failure(Some(format!(
|
handle_disastrous_failure(Some(format!(
|
||||||
"Lost connection to server: {}",
|
"Lost connection to server: {}",
|
||||||
e
|
e
|
||||||
)))
|
)))
|
||||||
|
|||||||
@@ -1,59 +1,12 @@
|
|||||||
use serde_json::json;
|
use tracing::warn;
|
||||||
use tracing::error;
|
|
||||||
|
|
||||||
use crate::{
|
use crate::{models::interaction::SendInteractionDto, services::ws::{ws_emit_soft, WS_EVENT}};
|
||||||
lock_r, models::interaction::SendInteractionDto, services::ws::WS_EVENT, state::FDOLL,
|
|
||||||
};
|
|
||||||
|
|
||||||
pub async fn send_interaction(dto: SendInteractionDto) -> Result<(), String> {
|
pub async fn send_interaction(dto: SendInteractionDto) -> Result<(), String> {
|
||||||
// Check if WS is initialized
|
ws_emit_soft(WS_EVENT::CLIENT_SEND_INTERACTION, dto)
|
||||||
let client = {
|
.await
|
||||||
let guard = lock_r!(FDOLL);
|
.map_err(|err| {
|
||||||
if let Some(clients) = &guard.network.clients {
|
warn!("Failed to send interaction: {}", err);
|
||||||
if clients.is_ws_initialized {
|
err
|
||||||
clients.ws_client.clone()
|
|
||||||
} else {
|
|
||||||
return Err("WebSocket not initialized".to_string());
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return Err("App not fully initialized".to_string());
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
if let Some(socket) = client {
|
|
||||||
// Prepare payload for client-send-interaction event
|
|
||||||
// The DTO structure matches what the server expects:
|
|
||||||
// { recipientUserId, content, type } (handled by serde rename_all="camelCase")
|
|
||||||
|
|
||||||
let payload = json!({
|
|
||||||
"recipientUserId": dto.recipient_user_id,
|
|
||||||
"content": dto.content,
|
|
||||||
"type": dto.type_
|
|
||||||
});
|
|
||||||
|
|
||||||
// Blocking emission because rust_socketio::Client::emit is synchronous/blocking
|
|
||||||
// but we are in an async context. Ideally we spawn_blocking.
|
|
||||||
let spawn_result = tauri::async_runtime::spawn_blocking(move || {
|
|
||||||
socket.emit(WS_EVENT::CLIENT_SEND_INTERACTION, payload)
|
|
||||||
})
|
})
|
||||||
.await;
|
|
||||||
|
|
||||||
match spawn_result {
|
|
||||||
Ok(emit_result) => match emit_result {
|
|
||||||
Ok(_) => Ok(()),
|
|
||||||
Err(e) => {
|
|
||||||
let err_msg = format!("Failed to emit interaction event: {}", e);
|
|
||||||
error!("{}", err_msg);
|
|
||||||
Err(err_msg)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
Err(e) => {
|
|
||||||
let err_msg = format!("Failed to spawn blocking task for interaction emit: {}", e);
|
|
||||||
error!("{}", err_msg);
|
|
||||||
Err(err_msg)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
Err("WebSocket client not available".to_string())
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
use tauri::Manager;
|
use tauri::Manager;
|
||||||
|
|
||||||
use crate::get_app_handle;
|
use crate::get_app_handle;
|
||||||
|
use tracing::warn;
|
||||||
|
|
||||||
pub mod app_events;
|
pub mod app_events;
|
||||||
pub mod app_menu;
|
pub mod app_menu;
|
||||||
@@ -8,8 +9,7 @@ pub mod auth;
|
|||||||
pub mod client_config_manager;
|
pub mod client_config_manager;
|
||||||
pub mod cursor;
|
pub mod cursor;
|
||||||
pub mod doll_editor;
|
pub mod doll_editor;
|
||||||
pub mod friend_active_doll_sprite;
|
pub mod friends;
|
||||||
pub mod friend_cursor;
|
|
||||||
pub mod health_manager;
|
pub mod health_manager;
|
||||||
pub mod health_monitor;
|
pub mod health_monitor;
|
||||||
pub mod interaction;
|
pub mod interaction;
|
||||||
@@ -25,6 +25,8 @@ pub fn close_all_windows() {
|
|||||||
let app_handle = get_app_handle();
|
let app_handle = get_app_handle();
|
||||||
let webview_windows = app_handle.webview_windows();
|
let webview_windows = app_handle.webview_windows();
|
||||||
for window in webview_windows {
|
for window in webview_windows {
|
||||||
window.1.close().unwrap();
|
if let Err(err) = window.1.close() {
|
||||||
|
warn!("Failed to close window '{}': {}", window.0, err);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ use tracing::{error, info, warn};
|
|||||||
|
|
||||||
use crate::models::event_payloads::{UserStatusPayload, UserStatusState};
|
use crate::models::event_payloads::{UserStatusPayload, UserStatusState};
|
||||||
use crate::services::ws::user_status::report_user_status;
|
use crate::services::ws::user_status::report_user_status;
|
||||||
use crate::services::ws::{ws_emit_soft, WS_EVENT};
|
|
||||||
|
|
||||||
use super::models::PresenceStatus;
|
use super::models::PresenceStatus;
|
||||||
|
|
||||||
@@ -48,7 +47,9 @@ async fn update_status(status: PresenceStatus) {
|
|||||||
presence_status: status,
|
presence_status: status,
|
||||||
state: UserStatusState::Idle,
|
state: UserStatusState::Idle,
|
||||||
};
|
};
|
||||||
report_user_status(user_status).await;
|
if let Err(e) = report_user_status(user_status).await {
|
||||||
|
warn!("User status report failed: {}", e);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn update_status_async(status: PresenceStatus) {
|
async fn update_status_async(status: PresenceStatus) {
|
||||||
@@ -56,7 +57,7 @@ async fn update_status_async(status: PresenceStatus) {
|
|||||||
presence_status: status,
|
presence_status: status,
|
||||||
state: UserStatusState::Idle,
|
state: UserStatusState::Idle,
|
||||||
};
|
};
|
||||||
if let Err(e) = ws_emit_soft(WS_EVENT::CLIENT_REPORT_USER_STATUS, payload).await {
|
if let Err(e) = report_user_status(payload).await {
|
||||||
warn!("User status report failed: {}", e);
|
warn!("User status report failed: {}", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ fn scene_interactive_state() -> Arc<AtomicBool> {
|
|||||||
|
|
||||||
pub fn update_scene_interactive(interactive: bool, should_click: bool) {
|
pub fn update_scene_interactive(interactive: bool, should_click: bool) {
|
||||||
let app_handle = get_app_handle();
|
let app_handle = get_app_handle();
|
||||||
|
scene_interactive_state().store(interactive, Ordering::SeqCst);
|
||||||
|
|
||||||
// If we are forcing interactive to false (e.g. background click), clear any open menus
|
// If we are forcing interactive to false (e.g. background click), clear any open menus
|
||||||
// This prevents the loop from immediately re-enabling it if the frontend hasn't updated yet
|
// This prevents the loop from immediately re-enabling it if the frontend hasn't updated yet
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ pub async fn establish_websocket_connection() {
|
|||||||
return; // Success
|
return; // Success
|
||||||
} else {
|
} else {
|
||||||
// Connection failed, trigger disaster recovery
|
// Connection failed, trigger disaster recovery
|
||||||
crate::init::lifecycle::handle_disasterous_failure(
|
crate::init::lifecycle::handle_disastrous_failure(
|
||||||
Some("WebSocket connection failed. Please check your network and try again.".to_string())
|
Some("WebSocket connection failed. Please check your network and try again.".to_string())
|
||||||
).await;
|
).await;
|
||||||
return;
|
return;
|
||||||
@@ -32,9 +32,9 @@ pub async fn establish_websocket_connection() {
|
|||||||
|
|
||||||
sleep(BACKOFF).await;
|
sleep(BACKOFF).await;
|
||||||
}
|
}
|
||||||
|
|
||||||
// If we exhausted retries without valid token
|
// If we exhausted retries without valid token
|
||||||
crate::init::lifecycle::handle_disasterous_failure(
|
crate::init::lifecycle::handle_disastrous_failure(
|
||||||
Some("Failed to authenticate. Please restart and sign in again.".to_string())
|
Some("Failed to authenticate. Please restart and sign in again.".to_string())
|
||||||
).await;
|
).await;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ use tauri_specta::Event;
|
|||||||
use tracing::{error, warn};
|
use tracing::{error, warn};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
get_app_handle, init::lifecycle::handle_disasterous_failure, lock_r, lock_w, state::FDOLL,
|
get_app_handle, init::lifecycle::handle_disastrous_failure, lock_r, lock_w, state::FDOLL,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Acquire WebSocket client and initialization state from app state
|
/// Acquire WebSocket client and initialization state from app state
|
||||||
@@ -87,7 +87,7 @@ pub async fn ws_emit<T: Serialize + Send + 'static>(
|
|||||||
Ok(_) => Ok(()),
|
Ok(_) => Ok(()),
|
||||||
Err(err_msg) => {
|
Err(err_msg) => {
|
||||||
error!("[critical] {}", err_msg);
|
error!("[critical] {}", err_msg);
|
||||||
handle_disasterous_failure(Some(err_msg.clone())).await;
|
handle_disastrous_failure(Some(err_msg.clone())).await;
|
||||||
Err(err_msg)
|
Err(err_msg)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ use crate::services::app_events::{
|
|||||||
};
|
};
|
||||||
use crate::services::{
|
use crate::services::{
|
||||||
cursor::{normalized_to_absolute, CursorPositions},
|
cursor::{normalized_to_absolute, CursorPositions},
|
||||||
friend_active_doll_sprite, friend_cursor,
|
friends,
|
||||||
};
|
};
|
||||||
use crate::state::AppDataRefreshScope;
|
use crate::state::AppDataRefreshScope;
|
||||||
|
|
||||||
@@ -62,7 +62,7 @@ pub fn on_friend_cursor_position(payload: Payload, _socket: RawClient) {
|
|||||||
let mapped_pos = &friend_data.position;
|
let mapped_pos = &friend_data.position;
|
||||||
let raw_pos = normalized_to_absolute(mapped_pos);
|
let raw_pos = normalized_to_absolute(mapped_pos);
|
||||||
|
|
||||||
friend_cursor::update_position(
|
friends::update_cursor_position(
|
||||||
friend_data.user_id,
|
friend_data.user_id,
|
||||||
CursorPositions {
|
CursorPositions {
|
||||||
raw: raw_pos,
|
raw: raw_pos,
|
||||||
@@ -77,8 +77,7 @@ pub fn on_friend_disconnected(payload: Payload, _socket: RawClient) {
|
|||||||
if let Ok(data) =
|
if let Ok(data) =
|
||||||
utils::extract_and_parse::<FriendDisconnectedPayload>(payload, "friend-disconnected")
|
utils::extract_and_parse::<FriendDisconnectedPayload>(payload, "friend-disconnected")
|
||||||
{
|
{
|
||||||
friend_active_doll_sprite::remove_friend(&data.user_id);
|
friends::remove_friend(&data.user_id);
|
||||||
friend_cursor::remove_friend(&data.user_id);
|
|
||||||
emitter::emit_to_frontend_typed(&FriendDisconnected(data));
|
emitter::emit_to_frontend_typed(&FriendDisconnected(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -112,8 +111,7 @@ pub fn on_friend_active_doll_changed(payload: Payload, _socket: RawClient) {
|
|||||||
payload,
|
payload,
|
||||||
"friend-active-doll-changed",
|
"friend-active-doll-changed",
|
||||||
) {
|
) {
|
||||||
friend_active_doll_sprite::set_active_doll(&data.friend_id, data.doll.as_ref());
|
friends::set_active_doll(&data.friend_id, data.doll.as_ref());
|
||||||
friend_cursor::set_active_doll(&data.friend_id, data.doll.is_some());
|
|
||||||
emitter::emit_to_frontend_typed(&FriendActiveDollChanged(data));
|
emitter::emit_to_frontend_typed(&FriendActiveDollChanged(data));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ static USER_STATUS_REPORT_DEBOUNCE: Lazy<Mutex<Option<JoinHandle<()>>>> =
|
|||||||
Lazy::new(|| Mutex::new(None));
|
Lazy::new(|| Mutex::new(None));
|
||||||
|
|
||||||
/// Report user status to WebSocket server with debouncing
|
/// Report user status to WebSocket server with debouncing
|
||||||
pub async fn report_user_status(status: UserStatusPayload) {
|
pub async fn report_user_status(status: UserStatusPayload) -> Result<(), String> {
|
||||||
let mut debouncer = USER_STATUS_REPORT_DEBOUNCE.lock().await;
|
let mut debouncer = USER_STATUS_REPORT_DEBOUNCE.lock().await;
|
||||||
|
|
||||||
// Cancel previous pending report
|
// Cancel previous pending report
|
||||||
@@ -25,7 +25,7 @@ pub async fn report_user_status(status: UserStatusPayload) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !status.has_presence_content() {
|
if !status.has_presence_content() {
|
||||||
return;
|
return Ok(());
|
||||||
}
|
}
|
||||||
|
|
||||||
if let Err(e) = UserStatusChanged(status.clone()).emit(crate::get_app_handle()) {
|
if let Err(e) = UserStatusChanged(status.clone()).emit(crate::get_app_handle()) {
|
||||||
@@ -43,4 +43,5 @@ pub async fn report_user_status(status: UserStatusPayload) {
|
|||||||
});
|
});
|
||||||
|
|
||||||
*debouncer = Some(handle);
|
*debouncer = Some(handle);
|
||||||
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ use crate::{
|
|||||||
remotes::{dolls::DollsRemote, friends::FriendRemote, user::UserRemote},
|
remotes::{dolls::DollsRemote, friends::FriendRemote, user::UserRemote},
|
||||||
services::{
|
services::{
|
||||||
app_events::{ActiveDollSpriteChanged, AppDataRefreshed},
|
app_events::{ActiveDollSpriteChanged, AppDataRefreshed},
|
||||||
friend_active_doll_sprite, friend_cursor, sprite,
|
friends, sprite,
|
||||||
},
|
},
|
||||||
state::FDOLL,
|
state::FDOLL,
|
||||||
};
|
};
|
||||||
@@ -165,8 +165,7 @@ pub async fn init_app_data_scoped(scope: AppDataRefreshScope) {
|
|||||||
let mut guard = lock_w!(crate::state::FDOLL);
|
let mut guard = lock_w!(crate::state::FDOLL);
|
||||||
guard.user_data.friends = Some(friends);
|
guard.user_data.friends = Some(friends);
|
||||||
drop(guard);
|
drop(guard);
|
||||||
friend_active_doll_sprite::sync_from_app_data();
|
friends::sync_from_app_data();
|
||||||
friend_cursor::sync_from_app_data();
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
warn!("Failed to fetch friends list: {}", e);
|
warn!("Failed to fetch friends list: {}", e);
|
||||||
@@ -287,6 +286,5 @@ pub fn clear_app_data() {
|
|||||||
guard.user_data.user = None;
|
guard.user_data.user = None;
|
||||||
guard.user_data.friends = None;
|
guard.user_data.friends = None;
|
||||||
drop(guard);
|
drop(guard);
|
||||||
friend_active_doll_sprite::clear();
|
friends::clear();
|
||||||
friend_cursor::clear();
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user