Put model state into an Arc (#28)
Previously the model state was in a `static STATE` to make it accessible to all the various subsystems. This moves it into an Arc and plumbs the reference around via function arguments. That allows us to do non-static initialization, eg based on user config. I also moved some things into dioxus context. Co-authored-by: Sam Sartor <me@samsartor.com> Co-committed-by: Sam Sartor <me@samsartor.com>
This commit was merged in pull request #28.
This commit is contained in:
+69
-32
@@ -4,13 +4,18 @@ use dioxus::prelude::*;
|
|||||||
use mime_guess::Mime;
|
use mime_guess::Mime;
|
||||||
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
||||||
use ordermap::OrderSet;
|
use ordermap::OrderSet;
|
||||||
use std::collections::{HashMap, HashSet};
|
use std::{
|
||||||
|
collections::{HashMap, HashSet},
|
||||||
|
fmt,
|
||||||
|
sync::Arc,
|
||||||
|
};
|
||||||
|
|
||||||
use crate::imp::{ConfigSystem, ConfigSystemInterface as _, Platform, PlatformInterface as _};
|
use crate::imp::{ConfigSystem, ConfigSystemInterface as _, Platform, PlatformInterface as _};
|
||||||
|
|
||||||
pub type ChannelId = u32;
|
pub type ChannelId = u32;
|
||||||
pub type UserId = u32;
|
pub type UserId = u32;
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub enum ConnectionState {
|
pub enum ConnectionState {
|
||||||
Disconnected,
|
Disconnected,
|
||||||
Connecting,
|
Connecting,
|
||||||
@@ -54,7 +59,7 @@ pub enum Command {
|
|||||||
use Command::*;
|
use Command::*;
|
||||||
use ConnectionState::*;
|
use ConnectionState::*;
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Debug)]
|
||||||
pub struct UserState {
|
pub struct UserState {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub channel: ChannelId,
|
pub channel: ChannelId,
|
||||||
@@ -79,13 +84,14 @@ impl UserState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Debug)]
|
||||||
pub struct Chat {
|
pub struct Chat {
|
||||||
pub raw: String,
|
pub raw: String,
|
||||||
pub dangerous_html: String,
|
pub dangerous_html: String,
|
||||||
pub sender: Option<UserId>,
|
pub sender: Option<UserId>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Debug)]
|
||||||
pub struct ChannelState {
|
pub struct ChannelState {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
pub children: OrderSet<ChannelId>,
|
pub children: OrderSet<ChannelId>,
|
||||||
@@ -111,7 +117,7 @@ impl ChannelState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Debug)]
|
||||||
pub struct ChannelsState {
|
pub struct ChannelsState {
|
||||||
pub channels: HashMap<ChannelId, ChannelState>,
|
pub channels: HashMap<ChannelId, ChannelState>,
|
||||||
}
|
}
|
||||||
@@ -198,7 +204,7 @@ impl ChannelsState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default, Debug)]
|
||||||
pub struct ServerState {
|
pub struct ServerState {
|
||||||
pub channels_state: ChannelsState,
|
pub channels_state: ChannelsState,
|
||||||
pub users: HashMap<UserId, UserState>,
|
pub users: HashMap<UserId, UserState>,
|
||||||
@@ -213,14 +219,20 @@ impl ServerState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub struct State {
|
pub struct State {
|
||||||
pub status: GlobalSignal<ConnectionState>,
|
pub status: Signal<ConnectionState>,
|
||||||
pub server: GlobalSignal<ServerState>,
|
pub server: Signal<ServerState>,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub static STATE: State = State {
|
impl fmt::Debug for State {
|
||||||
status: Signal::global(|| Disconnected),
|
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
|
||||||
server: Signal::global(|| Default::default()),
|
f.debug_struct("State")
|
||||||
};
|
.field("status", &self.status.read())
|
||||||
|
.field("server", &self.server.read())
|
||||||
|
.finish()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub type SharedState = Arc<State>;
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum UserIcon {
|
pub enum UserIcon {
|
||||||
@@ -267,7 +279,8 @@ pub fn UserPill(name: String, icon: UserIcon, isself: bool) -> Element {
|
|||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn User(id: UserId) -> Element {
|
pub fn User(id: UserId) -> Element {
|
||||||
let server = STATE.server.read();
|
let state = use_context::<SharedState>();
|
||||||
|
let server = state.server.read();
|
||||||
match server.users.get(&id) {
|
match server.users.get(&id) {
|
||||||
Some(state) => rsx!(UserPill {
|
Some(state) => rsx!(UserPill {
|
||||||
name: state.name.clone(),
|
name: state.name.clone(),
|
||||||
@@ -285,7 +298,8 @@ pub fn User(id: UserId) -> Element {
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn Channel(id: ChannelId) -> Element {
|
pub fn Channel(id: ChannelId) -> Element {
|
||||||
let net: Coroutine<Command> = use_coroutine_handle();
|
let net: Coroutine<Command> = use_coroutine_handle();
|
||||||
let server = STATE.server.read();
|
let state = use_context::<SharedState>();
|
||||||
|
let server = state.server.read();
|
||||||
let user = server.session.unwrap();
|
let user = server.session.unwrap();
|
||||||
let Some(state) = server.channels_state.channels.get(&id) else {
|
let Some(state) = server.channels_state.channels.get(&id) else {
|
||||||
return rsx!("missing channel {id}");
|
return rsx!("missing channel {id}");
|
||||||
@@ -354,7 +368,8 @@ pub fn Channel(id: ChannelId) -> Element {
|
|||||||
|
|
||||||
#[cfg(any(feature = "desktop", feature = "web"))]
|
#[cfg(any(feature = "desktop", feature = "web"))]
|
||||||
pub fn pick_and_send_file(net: &Coroutine<Command>) {
|
pub fn pick_and_send_file(net: &Coroutine<Command>) {
|
||||||
let channels = if let Some(user) = STATE.server.read().this_user() {
|
let state = use_context::<SharedState>();
|
||||||
|
let channels = if let Some(user) = state.server.read().this_user() {
|
||||||
vec![user.channel]
|
vec![user.channel]
|
||||||
} else {
|
} else {
|
||||||
return;
|
return;
|
||||||
@@ -380,11 +395,14 @@ pub fn pick_and_send_file(net: &Coroutine<Command>) {}
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn ChatView() -> Element {
|
pub fn ChatView() -> Element {
|
||||||
let net: Coroutine<Command> = use_coroutine_handle();
|
let net: Coroutine<Command> = use_coroutine_handle();
|
||||||
let server = STATE.server.read();
|
let state = use_context::<SharedState>();
|
||||||
|
let server = state.server.read();
|
||||||
let mut draft = use_signal(|| "".to_string());
|
let mut draft = use_signal(|| "".to_string());
|
||||||
|
|
||||||
let mut do_send = move || {
|
let mut do_send = move || {
|
||||||
if let Some(user) = STATE.server.read().this_user() {
|
let state = use_context::<SharedState>();
|
||||||
|
let server = state.server.read();
|
||||||
|
if let Some(user) = server.this_user() {
|
||||||
net.send(SendChat {
|
net.send(SendChat {
|
||||||
markdown: draft.write().split_off(0),
|
markdown: draft.write().split_off(0),
|
||||||
channels: vec![user.channel],
|
channels: vec![user.channel],
|
||||||
@@ -456,8 +474,9 @@ pub fn ChatView() -> Element {
|
|||||||
#[component]
|
#[component]
|
||||||
pub fn ControlView(overrides: Resource<ProxyOverrides>) -> Element {
|
pub fn ControlView(overrides: Resource<ProxyOverrides>) -> Element {
|
||||||
let net: Coroutine<Command> = use_coroutine_handle();
|
let net: Coroutine<Command> = use_coroutine_handle();
|
||||||
let status = &STATE.status;
|
let state = use_context::<SharedState>();
|
||||||
let server = STATE.server.read();
|
let status = &state.status;
|
||||||
|
let server = state.server.read();
|
||||||
let Some(&UserState {
|
let Some(&UserState {
|
||||||
deaf,
|
deaf,
|
||||||
self_deaf,
|
self_deaf,
|
||||||
@@ -645,9 +664,10 @@ pub fn ControlView(overrides: Resource<ProxyOverrides>) -> Element {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn ServerView(overrides: Resource<ProxyOverrides>, user_config: ConfigSystem) -> Element {
|
pub fn ServerView(overrides: Resource<ProxyOverrides>) -> Element {
|
||||||
let net: Coroutine<Command> = use_coroutine_handle();
|
let net: Coroutine<Command> = use_coroutine_handle();
|
||||||
let server = STATE.server.read();
|
let state = use_context::<SharedState>();
|
||||||
|
let server = state.server.read();
|
||||||
let Some(&UserState {
|
let Some(&UserState {
|
||||||
deaf,
|
deaf,
|
||||||
self_deaf,
|
self_deaf,
|
||||||
@@ -683,7 +703,8 @@ pub fn ServerView(overrides: Resource<ProxyOverrides>, user_config: ConfigSystem
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[component]
|
#[component]
|
||||||
pub fn LoginView(overrides: Resource<ProxyOverrides>, user_config: ConfigSystem) -> Element {
|
pub fn LoginView(overrides: Resource<ProxyOverrides>) -> Element {
|
||||||
|
let user_config = use_context::<ConfigSystem>();
|
||||||
let net: Coroutine<Command> = use_coroutine_handle();
|
let net: Coroutine<Command> = use_coroutine_handle();
|
||||||
|
|
||||||
let last_status = use_signal(|| None::<color_eyre::Result<ServerStatus>>);
|
let last_status = use_signal(|| None::<color_eyre::Result<ServerStatus>>);
|
||||||
@@ -706,8 +727,11 @@ pub fn LoginView(overrides: Resource<ProxyOverrides>, user_config: ConfigSystem)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let previous_username = user_config.config_get::<String>("username");
|
let mut username = use_signal(|| {
|
||||||
let mut username = use_signal(|| previous_username.unwrap_or(String::new()));
|
user_config
|
||||||
|
.config_get::<String>("username")
|
||||||
|
.unwrap_or(String::new())
|
||||||
|
});
|
||||||
|
|
||||||
let do_connect = move |_| {
|
let do_connect = move |_| {
|
||||||
let _ = user_config.config_set::<String>("username", &username.read());
|
let _ = user_config.config_set::<String>("username", &username.read());
|
||||||
@@ -720,7 +744,8 @@ pub fn LoginView(overrides: Resource<ProxyOverrides>, user_config: ConfigSystem)
|
|||||||
config: overrides.read().clone().unwrap_or_default(),
|
config: overrides.read().clone().unwrap_or_default(),
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
let status = &STATE.status;
|
let state = use_context::<SharedState>();
|
||||||
|
let status = &state.status;
|
||||||
let bottom = match &*status.read() {
|
let bottom = match &*status.read() {
|
||||||
Disconnected => rsx! {
|
Disconnected => rsx! {
|
||||||
button {
|
button {
|
||||||
@@ -854,10 +879,26 @@ pub fn LoginView(overrides: Resource<ProxyOverrides>, user_config: ConfigSystem)
|
|||||||
// )
|
// )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[component]
|
||||||
pub fn app() -> Element {
|
pub fn app() -> Element {
|
||||||
static STYLE: Asset = asset!("/assets/main.scss");
|
static STYLE: Asset = asset!("/assets/main.scss");
|
||||||
|
|
||||||
use_coroutine(|rx: UnboundedReceiver<Command>| super::network_entrypoint(rx));
|
use_effect(|| {
|
||||||
|
Platform::request_permissions();
|
||||||
|
});
|
||||||
|
|
||||||
|
use_root_context(|| ConfigSystem::new().unwrap());
|
||||||
|
let state = use_root_context(|| {
|
||||||
|
SharedState::new(State {
|
||||||
|
status: Signal::new(Disconnected),
|
||||||
|
server: Signal::new(Default::default()),
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
let network_state = state.clone();
|
||||||
|
use_coroutine(move |rx: UnboundedReceiver<Command>| {
|
||||||
|
super::network_entrypoint(rx, network_state.clone())
|
||||||
|
});
|
||||||
let overrides = use_resource(|| async move {
|
let overrides = use_resource(|| async move {
|
||||||
match Platform::load_proxy_overrides().await {
|
match Platform::load_proxy_overrides().await {
|
||||||
Ok(overrides) => overrides,
|
Ok(overrides) => overrides,
|
||||||
@@ -865,18 +906,14 @@ pub fn app() -> Element {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
let user_config = ConfigSystem::new().unwrap();
|
|
||||||
|
|
||||||
Platform::request_permissions();
|
|
||||||
|
|
||||||
rsx!(
|
rsx!(
|
||||||
document::Link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap" }
|
document::Link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Nunito:ital,wght@0,200..1000;1,200..1000&display=swap" }
|
||||||
document::Link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" }
|
document::Link{ rel: "stylesheet", href: "https://fonts.googleapis.com/css2?family=Material+Symbols+Outlined:opsz,wght,FILL,GRAD@20..48,100..700,0..1,-50..200" }
|
||||||
document::Link{ rel: "stylesheet", href: STYLE }
|
document::Link{ rel: "stylesheet", href: STYLE }
|
||||||
|
|
||||||
match *STATE.status.read() {
|
match *state.status.read() {
|
||||||
Connected => rsx!(ServerView { overrides, user_config }),
|
Connected => rsx!(ServerView { overrides }),
|
||||||
_ => rsx!(LoginView { overrides, user_config }),
|
_ => rsx!(LoginView { overrides }),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::app::Command;
|
use crate::app::{Command, SharedState};
|
||||||
use color_eyre::eyre::{bail, Error};
|
use color_eyre::eyre::{bail, Error};
|
||||||
use dioxus::hooks::UnboundedReceiver;
|
use dioxus::hooks::UnboundedReceiver;
|
||||||
use mumble_protocol::control::ClientControlCodec;
|
use mumble_protocol::control::ClientControlCodec;
|
||||||
@@ -74,6 +74,7 @@ pub async fn network_connect(
|
|||||||
username: String,
|
username: String,
|
||||||
event_rx: &mut UnboundedReceiver<Command>,
|
event_rx: &mut UnboundedReceiver<Command>,
|
||||||
overrides: &ProxyOverrides,
|
overrides: &ProxyOverrides,
|
||||||
|
state: SharedState,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
info!("connecting");
|
info!("connecting");
|
||||||
|
|
||||||
@@ -102,7 +103,7 @@ pub async fn network_connect(
|
|||||||
let reader = asynchronous_codec::FramedRead::new(read_server.compat(), read_codec);
|
let reader = asynchronous_codec::FramedRead::new(read_server.compat(), read_codec);
|
||||||
let writer = asynchronous_codec::FramedWrite::new(write_server.compat_write(), write_codec);
|
let writer = asynchronous_codec::FramedWrite::new(write_server.compat_write(), write_codec);
|
||||||
|
|
||||||
crate::network_loop(username, event_rx, reader, writer).await
|
crate::network_loop(username, state, event_rx, reader, writer).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
pub async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
||||||
|
|||||||
@@ -1,9 +1,7 @@
|
|||||||
use crate::app::Command;
|
use crate::app::{Command, SharedState};
|
||||||
use color_eyre::eyre::Error;
|
use color_eyre::eyre::Error;
|
||||||
use dioxus::hooks::UnboundedReceiver;
|
use dioxus::hooks::UnboundedReceiver;
|
||||||
use etcetera::{choose_app_strategy, AppStrategy, AppStrategyArgs};
|
|
||||||
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
||||||
use std::collections::HashMap;
|
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
/// Desktop platform implementation using Tokio and native audio.
|
/// Desktop platform implementation using Tokio and native audio.
|
||||||
@@ -30,8 +28,9 @@ impl super::PlatformInterface for DesktopPlatform {
|
|||||||
username: String,
|
username: String,
|
||||||
event_rx: &mut UnboundedReceiver<Command>,
|
event_rx: &mut UnboundedReceiver<Command>,
|
||||||
overrides: &ProxyOverrides,
|
overrides: &ProxyOverrides,
|
||||||
|
state: SharedState,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
super::connect::network_connect(address, username, event_rx, overrides).await
|
super::connect::network_connect(address, username, event_rx, overrides, state).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
use crate::app::Command;
|
use crate::app::{Command, SharedState};
|
||||||
use color_eyre::eyre::Error;
|
use color_eyre::eyre::Error;
|
||||||
use dioxus::hooks::UnboundedReceiver;
|
use dioxus::hooks::UnboundedReceiver;
|
||||||
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
||||||
@@ -24,8 +24,9 @@ impl super::PlatformInterface for MobilePlatform {
|
|||||||
username: String,
|
username: String,
|
||||||
event_rx: &mut UnboundedReceiver<Command>,
|
event_rx: &mut UnboundedReceiver<Command>,
|
||||||
overrides: &ProxyOverrides,
|
overrides: &ProxyOverrides,
|
||||||
|
state: SharedState,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
super::connect::network_connect(address, username, event_rx, overrides).await
|
super::connect::network_connect(address, username, event_rx, overrides, state).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
||||||
|
|||||||
+4
-2
@@ -4,7 +4,8 @@
|
|||||||
//! The traits make the platform boundary explicit and provide compile-time verification.
|
//! The traits make the platform boundary explicit and provide compile-time verification.
|
||||||
#![allow(async_fn_in_trait)]
|
#![allow(async_fn_in_trait)]
|
||||||
|
|
||||||
use crate::{app::Command, effects::AudioProcessor};
|
use crate::app::{Command, SharedState};
|
||||||
|
use crate::effects::AudioProcessor;
|
||||||
use color_eyre::eyre::Error;
|
use color_eyre::eyre::Error;
|
||||||
use dioxus::hooks::UnboundedReceiver;
|
use dioxus::hooks::UnboundedReceiver;
|
||||||
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
||||||
@@ -51,7 +52,7 @@ pub trait AudioPlayerInterface {
|
|||||||
fn play_opus(&mut self, payload: &[u8]);
|
fn play_opus(&mut self, payload: &[u8]);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub trait ConfigSystemInterface: Sized {
|
pub trait ConfigSystemInterface: Sized + Clone {
|
||||||
fn new() -> Result<Self, Error>;
|
fn new() -> Result<Self, Error>;
|
||||||
|
|
||||||
fn config_get<T>(&self, key: &str) -> Option<T>
|
fn config_get<T>(&self, key: &str) -> Option<T>
|
||||||
@@ -82,6 +83,7 @@ pub trait PlatformInterface {
|
|||||||
username: String,
|
username: String,
|
||||||
event_rx: &mut UnboundedReceiver<Command>,
|
event_rx: &mut UnboundedReceiver<Command>,
|
||||||
proxy_overrides: &ProxyOverrides,
|
proxy_overrides: &ProxyOverrides,
|
||||||
|
state: SharedState,
|
||||||
) -> impl Future<Output = Result<(), Error>>;
|
) -> impl Future<Output = Result<(), Error>>;
|
||||||
|
|
||||||
/// Get server status (user count, version, etc.).
|
/// Get server status (user count, version, etc.).
|
||||||
|
|||||||
@@ -1,10 +1,6 @@
|
|||||||
use crate::app::Command;
|
|
||||||
use color_eyre::eyre::Error;
|
use color_eyre::eyre::Error;
|
||||||
use dioxus::hooks::UnboundedReceiver;
|
|
||||||
use mumble_web2_common::ServerStatus;
|
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::time::Duration;
|
use tracing::info;
|
||||||
use tracing::{error, info, warn};
|
|
||||||
|
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct NativeConfigSystem {
|
pub struct NativeConfigSystem {
|
||||||
|
|||||||
+3
-1
@@ -1,6 +1,6 @@
|
|||||||
/// Stub implementation of the platform interface, so that we can
|
/// Stub implementation of the platform interface, so that we can
|
||||||
/// `cargo check` without any --feature flags.
|
/// `cargo check` without any --feature flags.
|
||||||
use crate::effects::AudioProcessor;
|
use crate::{app::SharedState, effects::AudioProcessor};
|
||||||
use color_eyre::eyre::Error;
|
use color_eyre::eyre::Error;
|
||||||
use dioxus::hooks::UnboundedReceiver;
|
use dioxus::hooks::UnboundedReceiver;
|
||||||
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
use mumble_web2_common::{ProxyOverrides, ServerStatus};
|
||||||
@@ -25,6 +25,7 @@ impl super::PlatformInterface for StubPlatform {
|
|||||||
_username: String,
|
_username: String,
|
||||||
_event_rx: &mut UnboundedReceiver<crate::app::Command>,
|
_event_rx: &mut UnboundedReceiver<crate::app::Command>,
|
||||||
_overrides: &ProxyOverrides,
|
_overrides: &ProxyOverrides,
|
||||||
|
_state: SharedState,
|
||||||
) -> impl Future<Output = Result<(), Error>> {
|
) -> impl Future<Output = Result<(), Error>> {
|
||||||
async { panic!("stubbed platform") }
|
async { panic!("stubbed platform") }
|
||||||
}
|
}
|
||||||
@@ -77,6 +78,7 @@ impl super::AudioPlayerInterface for StubAudioPlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
pub struct StubConfigSystem;
|
pub struct StubConfigSystem;
|
||||||
|
|
||||||
impl super::ConfigSystemInterface for StubConfigSystem {
|
impl super::ConfigSystemInterface for StubConfigSystem {
|
||||||
|
|||||||
+5
-3
@@ -1,4 +1,4 @@
|
|||||||
use crate::app::Command;
|
use crate::app::{Command, SharedState};
|
||||||
use crate::effects::{AudioProcessor, AudioProcessorSender, TransmitState};
|
use crate::effects::{AudioProcessor, AudioProcessorSender, TransmitState};
|
||||||
use color_eyre::eyre::{bail, eyre, Error};
|
use color_eyre::eyre::{bail, eyre, Error};
|
||||||
use crossbeam::atomic::AtomicCell;
|
use crossbeam::atomic::AtomicCell;
|
||||||
@@ -111,8 +111,9 @@ impl super::PlatformInterface for WebPlatform {
|
|||||||
username: String,
|
username: String,
|
||||||
event_rx: &mut UnboundedReceiver<Command>,
|
event_rx: &mut UnboundedReceiver<Command>,
|
||||||
overrides: &ProxyOverrides,
|
overrides: &ProxyOverrides,
|
||||||
|
state: SharedState,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
network_connect(address, username, event_rx, overrides).await
|
network_connect(address, username, event_rx, overrides, state).await
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
async fn get_status(client: &reqwest::Client) -> color_eyre::Result<ServerStatus> {
|
||||||
@@ -434,6 +435,7 @@ pub async fn network_connect(
|
|||||||
username: String,
|
username: String,
|
||||||
event_rx: &mut UnboundedReceiver<Command>,
|
event_rx: &mut UnboundedReceiver<Command>,
|
||||||
overrides: &ProxyOverrides,
|
overrides: &ProxyOverrides,
|
||||||
|
state: SharedState,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
info!("connecting");
|
info!("connecting");
|
||||||
|
|
||||||
@@ -492,7 +494,7 @@ pub async fn network_connect(
|
|||||||
let writer =
|
let writer =
|
||||||
asynchronous_codec::FramedWrite::new(wasm_stream_writable.into_async_write(), write_codec);
|
asynchronous_codec::FramedWrite::new(wasm_stream_writable.into_async_write(), write_codec);
|
||||||
|
|
||||||
crate::network_loop(username, event_rx, reader, writer).await
|
crate::network_loop(username, state, event_rx, reader, writer).await
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn absolute_url(path: &str) -> Result<Url, Error> {
|
pub fn absolute_url(path: &str) -> Result<Url, Error> {
|
||||||
|
|||||||
+24
-19
@@ -1,7 +1,6 @@
|
|||||||
use app::Chat;
|
use app::Chat;
|
||||||
use app::Command;
|
use app::Command;
|
||||||
use app::ConnectionState;
|
use app::ConnectionState;
|
||||||
use app::STATE;
|
|
||||||
use asynchronous_codec::FramedRead;
|
use asynchronous_codec::FramedRead;
|
||||||
use asynchronous_codec::FramedWrite;
|
use asynchronous_codec::FramedWrite;
|
||||||
use color_eyre::eyre::{bail, Error};
|
use color_eyre::eyre::{bail, Error};
|
||||||
@@ -27,6 +26,8 @@ use std::time::Duration;
|
|||||||
use tracing::error;
|
use tracing::error;
|
||||||
use tracing::info;
|
use tracing::info;
|
||||||
|
|
||||||
|
use crate::app::SharedState;
|
||||||
|
use crate::app::State;
|
||||||
use crate::effects::AudioProcessor;
|
use crate::effects::AudioProcessor;
|
||||||
use crate::imp::{
|
use crate::imp::{
|
||||||
AudioPlayer, AudioPlayerInterface as _, AudioSystem, AudioSystemInterface as _, Platform,
|
AudioPlayer, AudioPlayerInterface as _, AudioSystem, AudioSystemInterface as _, Platform,
|
||||||
@@ -38,7 +39,7 @@ mod effects;
|
|||||||
pub mod imp;
|
pub mod imp;
|
||||||
mod msghtml;
|
mod msghtml;
|
||||||
|
|
||||||
pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
|
pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>, state: SharedState) {
|
||||||
loop {
|
loop {
|
||||||
let Some(Command::Connect {
|
let Some(Command::Connect {
|
||||||
address,
|
address,
|
||||||
@@ -49,21 +50,23 @@ pub async fn network_entrypoint(mut event_rx: UnboundedReceiver<Command>) {
|
|||||||
panic!("did not receive connect command")
|
panic!("did not receive connect command")
|
||||||
};
|
};
|
||||||
|
|
||||||
*STATE.server.write() = Default::default();
|
*state.server.write_unchecked() = Default::default();
|
||||||
*STATE.status.write() = ConnectionState::Connecting;
|
*state.status.write_unchecked() = ConnectionState::Connecting;
|
||||||
if let Err(error) =
|
if let Err(error) =
|
||||||
Platform::network_connect(address, username, &mut event_rx, &config).await
|
Platform::network_connect(address, username, &mut event_rx, &config, state.clone())
|
||||||
|
.await
|
||||||
{
|
{
|
||||||
error!("could not connect {:?}", error);
|
error!("could not connect {:?}", error);
|
||||||
*STATE.status.write() = ConnectionState::Failed(error.to_string());
|
*state.status.write_unchecked() = ConnectionState::Failed(error.to_string());
|
||||||
} else {
|
} else {
|
||||||
*STATE.status.write() = ConnectionState::Disconnected;
|
*state.status.write_unchecked() = ConnectionState::Disconnected;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub async fn network_loop<R: AsyncRead + Unpin + 'static, W: AsyncWrite + Unpin + 'static>(
|
pub async fn network_loop<R: AsyncRead + Unpin + 'static, W: AsyncWrite + Unpin + 'static>(
|
||||||
username: String,
|
username: String,
|
||||||
|
state: SharedState,
|
||||||
event_rx: &mut UnboundedReceiver<Command>,
|
event_rx: &mut UnboundedReceiver<Command>,
|
||||||
mut reader: FramedRead<R, ControlCodec<Serverbound, Clientbound>>,
|
mut reader: FramedRead<R, ControlCodec<Serverbound, Clientbound>>,
|
||||||
mut writer: FramedWrite<W, ControlCodec<Serverbound, Clientbound>>,
|
mut writer: FramedWrite<W, ControlCodec<Serverbound, Clientbound>>,
|
||||||
@@ -149,7 +152,7 @@ pub async fn network_loop<R: AsyncRead + Unpin + 'static, W: AsyncWrite + Unpin
|
|||||||
if !matches!(msg, ControlPacket::UDPTunnel(_) | ControlPacket::Ping(_)) {
|
if !matches!(msg, ControlPacket::UDPTunnel(_) | ControlPacket::Ping(_)) {
|
||||||
info!("receiving packet {:#?}", msg);
|
info!("receiving packet {:#?}", msg);
|
||||||
}
|
}
|
||||||
let res = accept_packet(msg, &mut audio, &mut decoder_map);
|
let res = accept_packet(msg, &mut audio, &mut decoder_map, &state);
|
||||||
if let Err(err) = res {
|
if let Err(err) = res {
|
||||||
error!("error accepting packet {:?}", err)
|
error!("error accepting packet {:?}", err)
|
||||||
}
|
}
|
||||||
@@ -168,7 +171,7 @@ pub async fn network_loop<R: AsyncRead + Unpin + 'static, W: AsyncWrite + Unpin
|
|||||||
match command {
|
match command {
|
||||||
Some(Command::Disconnect) => break,
|
Some(Command::Disconnect) => break,
|
||||||
Some(command) => {
|
Some(command) => {
|
||||||
let res = accept_command(command, &mut send_chan, &mut audio);
|
let res = accept_command(command, &mut send_chan, &mut audio, &state);
|
||||||
if let Err(err) = res {
|
if let Err(err) = res {
|
||||||
info!("error accepting command {:?}", err)
|
info!("error accepting command {:?}", err)
|
||||||
}
|
}
|
||||||
@@ -187,9 +190,10 @@ fn accept_command(
|
|||||||
command: Command,
|
command: Command,
|
||||||
send_chan: &mut UnboundedSender<ControlPacket<mumble_protocol::Serverbound>>,
|
send_chan: &mut UnboundedSender<ControlPacket<mumble_protocol::Serverbound>>,
|
||||||
audio: &mut AudioSystem,
|
audio: &mut AudioSystem,
|
||||||
|
state: &State,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
use Command::*;
|
use Command::*;
|
||||||
let Some(session) = STATE.server.read().session else {
|
let Some(session) = state.server.read().session else {
|
||||||
bail!("no session id")
|
bail!("no session id")
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -212,7 +216,7 @@ fn accept_command(
|
|||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut server = STATE.server.write();
|
let mut server = state.server.write_unchecked();
|
||||||
let Some(me) = server.session else {
|
let Some(me) = server.session else {
|
||||||
bail!("not signed in with a session id")
|
bail!("not signed in with a session id")
|
||||||
};
|
};
|
||||||
@@ -253,7 +257,7 @@ fn accept_command(
|
|||||||
};
|
};
|
||||||
|
|
||||||
{
|
{
|
||||||
let mut server = STATE.server.write();
|
let mut server = state.server.write_unchecked();
|
||||||
let Some(me) = server.session else {
|
let Some(me) = server.session else {
|
||||||
bail!("not signed in with a session id")
|
bail!("not signed in with a session id")
|
||||||
};
|
};
|
||||||
@@ -304,6 +308,7 @@ fn accept_packet(
|
|||||||
msg: ControlPacket<mumble_protocol::Clientbound>,
|
msg: ControlPacket<mumble_protocol::Clientbound>,
|
||||||
audio_context: &mut AudioSystem,
|
audio_context: &mut AudioSystem,
|
||||||
player_map: &mut HashMap<u32, AudioPlayer>,
|
player_map: &mut HashMap<u32, AudioPlayer>,
|
||||||
|
state: &State,
|
||||||
) -> Result<(), Error> {
|
) -> Result<(), Error> {
|
||||||
match msg {
|
match msg {
|
||||||
ControlPacket::UDPTunnel(u) => {
|
ControlPacket::UDPTunnel(u) => {
|
||||||
@@ -340,15 +345,15 @@ fn accept_packet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ControlPacket::ChannelState(u) => {
|
ControlPacket::ChannelState(u) => {
|
||||||
let mut server = STATE.server.write();
|
let mut server = state.server.write_unchecked();
|
||||||
server.channels_state.update_from_channel_state(&u);
|
server.channels_state.update_from_channel_state(&u);
|
||||||
}
|
}
|
||||||
ControlPacket::ChannelRemove(u) => {
|
ControlPacket::ChannelRemove(u) => {
|
||||||
let mut server = STATE.server.write();
|
let mut server = state.server.write_unchecked();
|
||||||
server.channels_state.update_from_channel_remove(&u);
|
server.channels_state.update_from_channel_remove(&u);
|
||||||
}
|
}
|
||||||
ControlPacket::UserState(u) => {
|
ControlPacket::UserState(u) => {
|
||||||
let mut server = STATE.server.write();
|
let mut server = state.server.write_unchecked();
|
||||||
let server = &mut *server;
|
let server = &mut *server;
|
||||||
let id = u.get_session();
|
let id = u.get_session();
|
||||||
|
|
||||||
@@ -392,7 +397,7 @@ fn accept_packet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ControlPacket::UserRemove(u) => {
|
ControlPacket::UserRemove(u) => {
|
||||||
let mut server = STATE.server.write();
|
let mut server = state.server.write_unchecked();
|
||||||
let id = u.get_session();
|
let id = u.get_session();
|
||||||
if let Some(state) = server.users.remove(&id) {
|
if let Some(state) = server.users.remove(&id) {
|
||||||
if let Some(parent) = server.channels_state.channels.get_mut(&state.channel) {
|
if let Some(parent) = server.channels_state.channels.get_mut(&state.channel) {
|
||||||
@@ -401,7 +406,7 @@ fn accept_packet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ControlPacket::TextMessage(u) => {
|
ControlPacket::TextMessage(u) => {
|
||||||
let mut server = STATE.server.write();
|
let mut server = state.server.write_unchecked();
|
||||||
if u.has_message() {
|
if u.has_message() {
|
||||||
let text = u.get_message().to_string();
|
let text = u.get_message().to_string();
|
||||||
server.chat.push(Chat {
|
server.chat.push(Chat {
|
||||||
@@ -416,8 +421,8 @@ fn accept_packet(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
ControlPacket::ServerSync(u) => {
|
ControlPacket::ServerSync(u) => {
|
||||||
*STATE.status.write() = ConnectionState::Connected;
|
*state.status.write_unchecked() = ConnectionState::Connected;
|
||||||
let mut server = STATE.server.write();
|
let mut server = state.server.write_unchecked();
|
||||||
if u.has_welcome_text() {
|
if u.has_welcome_text() {
|
||||||
let text = u.get_welcome_text().to_string();
|
let text = u.get_welcome_text().to_string();
|
||||||
server.chat.push(Chat {
|
server.chat.push(Chat {
|
||||||
|
|||||||
Reference in New Issue
Block a user