fix: wire SessionHandle, event subscription, and message sending
- Store SessionHandle in Arc<Mutex> instead of dropping it - Implement iced subscription with stream::channel to forward SessionEvent - SendChannelMessage now sends via SessionHandle - Disconnect sends clientdisconnect command then clears state - ServerQuery panel now executes 'help' command via QueryClient - Remove tracing_subscriber to avoid console window on Windows - Request channel/client lists on connect - Show connection errors and query responses in UI
This commit is contained in:
+141
-23
@@ -1,14 +1,14 @@
|
||||
use iced::widget::{button, column, container, horizontal_rule, horizontal_space, row, scrollable, text, text_input, vertical_space};
|
||||
use iced::{Element, Length, Subscription, Task};
|
||||
use iced::futures::sink::SinkExt;
|
||||
|
||||
use std::sync::Arc;
|
||||
use tokio::sync::Mutex;
|
||||
|
||||
use tscore::{ClientConfig, IdentityKey, Session, SessionEvent};
|
||||
use tscore::{ClientConfig, IdentityKey, Session, SessionEvent, SessionHandle};
|
||||
use tsdb::DatabaseManager;
|
||||
|
||||
fn main() -> iced::Result {
|
||||
tracing_subscriber::fmt::init();
|
||||
iced::application("ReTeamSpeak", App::update, App::view)
|
||||
.subscription(App::subscription)
|
||||
.run_with(App::new)
|
||||
@@ -24,8 +24,6 @@ enum Page {
|
||||
#[derive(Debug, Clone)]
|
||||
enum Message {
|
||||
PageChanged(Page),
|
||||
LoadBookmarks,
|
||||
BookmarksLoaded(Vec<BookmarkInfo>),
|
||||
SelectBookmark(Option<usize>),
|
||||
NicknameChanged(String),
|
||||
PasswordChanged(String),
|
||||
@@ -34,19 +32,19 @@ enum Message {
|
||||
Disconnect,
|
||||
Disconnected,
|
||||
ConnectionError(String),
|
||||
PollEvents,
|
||||
SessionEvent(SessionEvent),
|
||||
MessageInputChanged(String),
|
||||
SendChannelMessage,
|
||||
QueryAddressChanged(String),
|
||||
QueryPortChanged(String),
|
||||
RunServerQuery,
|
||||
QueryResponse(String),
|
||||
QueryError(String),
|
||||
Noop,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct BookmarkInfo {
|
||||
id: String,
|
||||
name: String,
|
||||
address: String,
|
||||
port: u16,
|
||||
@@ -55,8 +53,10 @@ struct BookmarkInfo {
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
struct ChannelEntry {
|
||||
#[allow(dead_code)]
|
||||
cid: u64,
|
||||
channel_name: String,
|
||||
#[allow(dead_code)]
|
||||
total_clients: u16,
|
||||
}
|
||||
|
||||
@@ -83,12 +83,15 @@ struct ChatMessage {
|
||||
|
||||
struct App {
|
||||
page: Page,
|
||||
#[allow(dead_code)]
|
||||
db: Arc<Mutex<DatabaseManager>>,
|
||||
bookmarks: Vec<BookmarkInfo>,
|
||||
selected_bookmark: Option<usize>,
|
||||
nickname: String,
|
||||
password: String,
|
||||
connected: bool,
|
||||
handle: Arc<Mutex<Option<SessionHandle>>>,
|
||||
session_id: u64,
|
||||
server_info: Option<ServerInfo>,
|
||||
channels: Vec<ChannelEntry>,
|
||||
clients: Vec<ClientEntry>,
|
||||
@@ -97,6 +100,7 @@ struct App {
|
||||
query_address: String,
|
||||
query_port: u16,
|
||||
query_loading: bool,
|
||||
query_response: Option<String>,
|
||||
query_error: Option<String>,
|
||||
error: Option<String>,
|
||||
}
|
||||
@@ -112,14 +116,12 @@ impl App {
|
||||
db: Arc::new(Mutex::new(db)),
|
||||
bookmarks: vec![
|
||||
BookmarkInfo {
|
||||
id: "1".to_string(),
|
||||
name: "Local Server".to_string(),
|
||||
address: "127.0.0.1".to_string(),
|
||||
port: 9987,
|
||||
nickname: Some("User".to_string()),
|
||||
},
|
||||
BookmarkInfo {
|
||||
id: "2".to_string(),
|
||||
name: "KR TeamSpeak".to_string(),
|
||||
address: "kr.teamspeak.app".to_string(),
|
||||
port: 9987,
|
||||
@@ -130,6 +132,8 @@ impl App {
|
||||
nickname: String::new(),
|
||||
password: String::new(),
|
||||
connected: false,
|
||||
handle: Arc::new(Mutex::new(None)),
|
||||
session_id: 0,
|
||||
server_info: None,
|
||||
channels: Vec::new(),
|
||||
clients: Vec::new(),
|
||||
@@ -138,6 +142,7 @@ impl App {
|
||||
query_address: String::new(),
|
||||
query_port: 10011,
|
||||
query_loading: false,
|
||||
query_response: None,
|
||||
query_error: None,
|
||||
error: None,
|
||||
};
|
||||
@@ -151,8 +156,6 @@ impl App {
|
||||
self.page = page;
|
||||
Task::none()
|
||||
}
|
||||
Message::LoadBookmarks => Task::none(),
|
||||
Message::BookmarksLoaded(_) => Task::none(),
|
||||
Message::SelectBookmark(idx) => {
|
||||
self.selected_bookmark = idx;
|
||||
if let Some(i) = idx {
|
||||
@@ -187,6 +190,10 @@ impl App {
|
||||
} else {
|
||||
Some(self.password.clone())
|
||||
};
|
||||
let handle_store = self.handle.clone();
|
||||
|
||||
self.session_id += 1;
|
||||
self.error = None;
|
||||
|
||||
Task::perform(
|
||||
async move {
|
||||
@@ -214,9 +221,13 @@ impl App {
|
||||
.await
|
||||
.map_err(|e| e.to_string())
|
||||
},
|
||||
|result| match result {
|
||||
Ok((session, _handle)) => {
|
||||
move |result| match result {
|
||||
Ok((session, handle)) => {
|
||||
let client_id = session.client_id().unwrap_or(0);
|
||||
let handle_arc = handle_store.clone();
|
||||
tokio::spawn(async move {
|
||||
*handle_arc.lock().await = Some(handle);
|
||||
});
|
||||
tokio::spawn(async move {
|
||||
let mut session = session;
|
||||
if let Err(e) = session.run().await {
|
||||
@@ -229,18 +240,32 @@ impl App {
|
||||
},
|
||||
)
|
||||
}
|
||||
Message::Connected(client_id) => {
|
||||
Message::Connected(_client_id) => {
|
||||
self.connected = true;
|
||||
tracing::info!("Connected as client {client_id}");
|
||||
Task::none()
|
||||
let h = self.handle.clone();
|
||||
Task::perform(async move {
|
||||
let guard = h.lock().await;
|
||||
if let Some(ref handle) = *guard {
|
||||
let _ = handle.request_channel_list().await;
|
||||
let _ = handle.request_client_list().await;
|
||||
}
|
||||
}, |_| Message::Noop)
|
||||
}
|
||||
Message::Disconnect => {
|
||||
let h = self.handle.clone();
|
||||
self.connected = false;
|
||||
self.server_info = None;
|
||||
self.channels.clear();
|
||||
self.clients.clear();
|
||||
self.messages.clear();
|
||||
Task::none()
|
||||
self.session_id += 1;
|
||||
Task::perform(async move {
|
||||
let mut guard = h.lock().await;
|
||||
if let Some(ref handle) = *guard {
|
||||
let _ = handle.disconnect().await;
|
||||
}
|
||||
*guard = None;
|
||||
}, |_| Message::Disconnected)
|
||||
}
|
||||
Message::Disconnected => {
|
||||
self.connected = false;
|
||||
@@ -254,9 +279,11 @@ impl App {
|
||||
self.error = Some(err);
|
||||
Task::none()
|
||||
}
|
||||
Message::PollEvents => Task::none(),
|
||||
Message::SessionEvent(event) => {
|
||||
match event {
|
||||
SessionEvent::Connected { client_id } => {
|
||||
tracing::info!("Connected as client {client_id}");
|
||||
}
|
||||
SessionEvent::ServerInfo { name, platform, version, max_clients, clients_online, .. } => {
|
||||
self.server_info = Some(ServerInfo { name, platform, version, max_clients, clients_online });
|
||||
}
|
||||
@@ -297,8 +324,21 @@ impl App {
|
||||
Task::none()
|
||||
}
|
||||
Message::SendChannelMessage => {
|
||||
let msg = self.message_input.clone();
|
||||
self.message_input.clear();
|
||||
Task::none()
|
||||
if msg.is_empty() {
|
||||
return Task::none();
|
||||
}
|
||||
let h = self.handle.clone();
|
||||
Task::perform(async move {
|
||||
let guard = h.lock().await;
|
||||
if let Some(ref handle) = *guard {
|
||||
if let Err(e) = handle.send_channel_message(&msg).await {
|
||||
return Message::ConnectionError(e.to_string());
|
||||
}
|
||||
}
|
||||
Message::Noop
|
||||
}, |m| m)
|
||||
}
|
||||
Message::QueryAddressChanged(addr) => {
|
||||
self.query_address = addr;
|
||||
@@ -309,8 +349,37 @@ impl App {
|
||||
Task::none()
|
||||
}
|
||||
Message::RunServerQuery => {
|
||||
if self.query_address.is_empty() {
|
||||
self.query_error = Some("Address required".to_string());
|
||||
return Task::none();
|
||||
}
|
||||
self.query_loading = true;
|
||||
self.query_error = None;
|
||||
self.query_response = None;
|
||||
let addr = format!("{}:{}", self.query_address, self.query_port);
|
||||
Task::perform(
|
||||
async move {
|
||||
use tscore::QueryClient;
|
||||
let mut client = QueryClient::connect(&addr).await
|
||||
.map_err(|e| format!("Connect: {e}"))?;
|
||||
let resp = client.execute("help").await
|
||||
.map_err(|e| format!("Query: {e}"))?;
|
||||
Ok::<String, String>(resp.raw)
|
||||
},
|
||||
|result| match result {
|
||||
Ok(raw) => Message::QueryResponse(raw),
|
||||
Err(e) => Message::QueryError(e),
|
||||
},
|
||||
)
|
||||
}
|
||||
Message::QueryResponse(raw) => {
|
||||
self.query_loading = false;
|
||||
self.query_response = Some(raw);
|
||||
Task::none()
|
||||
}
|
||||
Message::QueryError(e) => {
|
||||
self.query_loading = false;
|
||||
self.query_error = Some(e);
|
||||
Task::none()
|
||||
}
|
||||
Message::Noop => Task::none(),
|
||||
@@ -318,7 +387,37 @@ impl App {
|
||||
}
|
||||
|
||||
fn subscription(&self) -> Subscription<Message> {
|
||||
Subscription::none()
|
||||
if !self.connected {
|
||||
return Subscription::none();
|
||||
}
|
||||
let handle = self.handle.clone();
|
||||
let session_id = self.session_id;
|
||||
Subscription::run_with_id(
|
||||
session_id,
|
||||
iced::stream::channel(100, move |mut sender| async move {
|
||||
loop {
|
||||
let event = {
|
||||
let mut guard = handle.lock().await;
|
||||
match guard.as_mut() {
|
||||
Some(h) => h.recv_event().await,
|
||||
None => break,
|
||||
}
|
||||
};
|
||||
match event {
|
||||
Some(event) => {
|
||||
let is_disconnect = matches!(event, SessionEvent::Disconnected);
|
||||
if sender.send(Message::SessionEvent(event)).await.is_err() {
|
||||
break;
|
||||
}
|
||||
if is_disconnect {
|
||||
break;
|
||||
}
|
||||
}
|
||||
None => break,
|
||||
}
|
||||
}
|
||||
}),
|
||||
)
|
||||
}
|
||||
|
||||
fn view(&self) -> Element<'_, Message> {
|
||||
@@ -398,7 +497,7 @@ impl App {
|
||||
fn view_server_content(&self) -> Element<'_, Message> {
|
||||
if let Some(idx) = self.selected_bookmark {
|
||||
if let Some(bookmark) = self.bookmarks.get(idx) {
|
||||
let connect_form = column![
|
||||
let mut connect_form = column![
|
||||
text(format!("Connect to {}", bookmark.name)).size(20),
|
||||
vertical_space().height(12),
|
||||
row![
|
||||
@@ -435,6 +534,11 @@ impl App {
|
||||
]
|
||||
.spacing(4)
|
||||
.padding(16);
|
||||
if let Some(err) = &self.error {
|
||||
connect_form = connect_form.push(
|
||||
text(format!("Error: {err}")).size(12)
|
||||
);
|
||||
}
|
||||
|
||||
if self.connected {
|
||||
let server_info = if let Some(info) = &self.server_info {
|
||||
@@ -516,7 +620,7 @@ impl App {
|
||||
}
|
||||
|
||||
fn view_query_content(&self) -> Element<'_, Message> {
|
||||
column![
|
||||
let mut content = column![
|
||||
text("ServerQuery").size(20),
|
||||
vertical_space().height(12),
|
||||
row![
|
||||
@@ -545,8 +649,22 @@ impl App {
|
||||
],
|
||||
]
|
||||
.spacing(4)
|
||||
.padding(16)
|
||||
.into()
|
||||
.padding(16);
|
||||
|
||||
if let Some(resp) = &self.query_response {
|
||||
content = content.push(
|
||||
column![
|
||||
text("Response:").size(14),
|
||||
scrollable(text(resp).size(12)).height(300),
|
||||
]
|
||||
.spacing(4),
|
||||
);
|
||||
}
|
||||
if let Some(err) = &self.query_error {
|
||||
content = content.push(text(format!("Error: {err}")).size(12));
|
||||
}
|
||||
|
||||
content.into()
|
||||
}
|
||||
|
||||
fn view_settings_content(&self) -> Element<'_, Message> {
|
||||
|
||||
Reference in New Issue
Block a user