From 765446392d02519f1a6bcc00ae8514136741dcd1 Mon Sep 17 00:00:00 2001 From: Builder Date: Mon, 30 Mar 2026 02:14:03 +0000 Subject: [PATCH] Add ServerEntry model and server list persistence to platform trait Introduces a ServerEntry struct in common with name, address, port, username, and optional password fields. Extends PlatformInterface with load_servers/save_servers methods, implemented across all platforms (desktop persists to JSON config, web uses localStorage, mobile/stub are stubs). Co-Authored-By: Claude Opus 4.6 --- common/src/lib.rs | 10 ++++++++++ gui/src/imp/desktop.rs | 18 +++++++++++++++++- gui/src/imp/mobile.rs | 10 ++++++++-- gui/src/imp/mod.rs | 8 +++++++- gui/src/imp/stub.rs | 10 +++++++++- gui/src/imp/web.rs | 20 +++++++++++++++++++- 6 files changed, 70 insertions(+), 6 deletions(-) diff --git a/common/src/lib.rs b/common/src/lib.rs index 0d520a5..5845f5d 100644 --- a/common/src/lib.rs +++ b/common/src/lib.rs @@ -16,3 +16,13 @@ pub struct ServerStatus { pub max_users: Option, pub bandwidth: Option, } + +#[derive(Debug, Clone, Deserialize, Serialize, Default, PartialEq)] +pub struct ServerEntry { + pub name: String, + pub address: String, + pub port: u16, + pub username: String, + #[serde(default, skip_serializing_if = "Option::is_none")] + pub password: Option, +} diff --git a/gui/src/imp/desktop.rs b/gui/src/imp/desktop.rs index 00c2404..5220dea 100644 --- a/gui/src/imp/desktop.rs +++ b/gui/src/imp/desktop.rs @@ -2,7 +2,7 @@ use crate::app::Command; use color_eyre::eyre::Error; use dioxus::hooks::UnboundedReceiver; use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs}; -use mumble_web2_common::{ClientConfig, ServerStatus}; +use mumble_web2_common::{ClientConfig, ServerEntry, ServerStatus}; use std::collections::HashMap; use std::time::Duration; @@ -46,6 +46,22 @@ impl super::PlatformInterface for DesktopPlatform { save_config_map(&config).ok() } + fn load_servers() -> Vec { + let config = load_config_map(); + config + .get("servers") + .and_then(|s| serde_json::from_str(s).ok()) + .unwrap_or_default() + } + + fn save_servers(servers: &[ServerEntry]) { + let mut config = load_config_map(); + if let Ok(json) = serde_json::to_string(servers) { + config.insert("servers".to_string(), json); + let _ = save_config_map(&config); + } + } + async fn network_connect( address: String, username: String, diff --git a/gui/src/imp/mobile.rs b/gui/src/imp/mobile.rs index cac7b86..7837bf3 100644 --- a/gui/src/imp/mobile.rs +++ b/gui/src/imp/mobile.rs @@ -1,7 +1,7 @@ use crate::app::Command; use color_eyre::eyre::Error; use dioxus::hooks::UnboundedReceiver; -use mumble_web2_common::{ClientConfig, ServerStatus}; +use mumble_web2_common::{ClientConfig, ServerEntry, ServerStatus}; use std::future::Future; use std::time::Duration; @@ -31,10 +31,16 @@ impl super::PlatformInterface for MobilePlatform { None } - fn set_default_server(server: &str) -> Option<()> { + fn set_default_server(_server: &str) -> Option<()> { None } + fn load_servers() -> Vec { + Vec::new() + } + + fn save_servers(_servers: &[ServerEntry]) {} + async fn network_connect( address: String, username: String, diff --git a/gui/src/imp/mod.rs b/gui/src/imp/mod.rs index a657d31..cd36926 100644 --- a/gui/src/imp/mod.rs +++ b/gui/src/imp/mod.rs @@ -7,7 +7,7 @@ use crate::{app::Command, effects::AudioProcessor}; use color_eyre::eyre::Error; use dioxus::hooks::UnboundedReceiver; -use mumble_web2_common::{ClientConfig, ServerStatus}; +use mumble_web2_common::{ClientConfig, ServerEntry, ServerStatus}; use std::future::Future; use std::time::Duration; @@ -90,6 +90,12 @@ pub trait PlatformInterface { /// Save the default server URL. fn set_default_server(server: &str) -> Option<()>; + /// Load the saved server list. + fn load_servers() -> Vec; + + /// Save the server list. + fn save_servers(servers: &[ServerEntry]); + /// Async sleep for the given duration. fn sleep(duration: Duration) -> impl Future; } diff --git a/gui/src/imp/stub.rs b/gui/src/imp/stub.rs index d03cd5e..0c0c67a 100644 --- a/gui/src/imp/stub.rs +++ b/gui/src/imp/stub.rs @@ -3,7 +3,7 @@ use crate::effects::AudioProcessor; use color_eyre::eyre::Error; use dioxus::hooks::UnboundedReceiver; -use mumble_web2_common::{ClientConfig, ServerStatus}; +use mumble_web2_common::{ClientConfig, ServerEntry, ServerStatus}; use std::future::Future; pub struct StubPlatform; @@ -54,6 +54,14 @@ impl super::PlatformInterface for StubPlatform { panic!("stubbed platform") } + fn load_servers() -> Vec { + panic!("stubbed platform") + } + + fn save_servers(_servers: &[ServerEntry]) { + panic!("stubbed platform") + } + fn sleep(_duration: std::time::Duration) -> impl Future { async { panic!("stubbed platform") } } diff --git a/gui/src/imp/web.rs b/gui/src/imp/web.rs index 1a55ec4..814b36b 100644 --- a/gui/src/imp/web.rs +++ b/gui/src/imp/web.rs @@ -6,7 +6,7 @@ use dioxus::prelude::*; use gloo_timers::future::TimeoutFuture; use js_sys::Float32Array; use mumble_protocol::control::ClientControlCodec; -use mumble_web2_common::{ClientConfig, ServerStatus}; +use mumble_web2_common::{ClientConfig, ServerEntry, ServerStatus}; use reqwest::Url; use std::future::Future; use std::sync::Arc; @@ -129,6 +129,24 @@ impl super::PlatformInterface for WebPlatform { None } + fn load_servers() -> Vec { + web_sys::window() + .and_then(|w| w.local_storage().ok()?) + .and_then(|s| s.get_item("servers").ok()?) + .and_then(|json| serde_json::from_str(&json).ok()) + .unwrap_or_default() + } + + fn save_servers(servers: &[ServerEntry]) { + if let Ok(json) = serde_json::to_string(servers) { + if let Some(storage) = web_sys::window() + .and_then(|w| w.local_storage().ok()?) + { + let _ = storage.set_item("servers", &json); + } + } + } + async fn network_connect( address: String, username: String,