[feat](trx-frontend): add context types for explicit initialization
Create explicit context types for frontend registration and runtime: FrontendRegistrationContext: - register_frontend(name, spawner) - register a frontend - is_frontend_registered(name) - check if registered - registered_frontends() -> Vec<String> - list all frontends - spawn_frontend(name, ...) -> DynResult - spawn a frontend FrontendRuntimeContext (NEW): - audio_rx: broadcast channel for audio RX - audio_tx: mpsc channel for audio TX - audio_info: watch channel for audio stream metadata - decode_rx: broadcast channel for decoded messages - aprs_history: Arc<Mutex<VecDeque>> for APRS decode history - cw_history: Arc<Mutex<VecDeque>> for CW decode history - ft8_history: Arc<Mutex<VecDeque>> for FT8 decode history - auth_tokens: HashSet for authentication Replaces global mutable state with explicit context that can be threaded through bootstrap. Maintains global API for compatibility. Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com> Signed-off-by: Stanislaw Grams <stanislawgrams@gmail.com>
This commit is contained in:
@@ -8,5 +8,6 @@ version = "0.1.0"
|
||||
edition = "2021"
|
||||
|
||||
[dependencies]
|
||||
bytes = "1"
|
||||
trx-core = { path = "../../trx-core" }
|
||||
tokio = { workspace = true, features = ["sync"] }
|
||||
|
||||
@@ -2,13 +2,17 @@
|
||||
//
|
||||
// SPDX-License-Identifier: BSD-2-Clause
|
||||
|
||||
use std::collections::HashMap;
|
||||
use std::collections::{HashMap, VecDeque, HashSet};
|
||||
use std::net::SocketAddr;
|
||||
use std::sync::{Mutex, OnceLock};
|
||||
use std::sync::{Arc, Mutex, OnceLock};
|
||||
use std::time::Instant;
|
||||
|
||||
use tokio::sync::{mpsc, watch};
|
||||
use bytes::Bytes;
|
||||
use tokio::sync::{broadcast, mpsc, watch};
|
||||
use tokio::task::JoinHandle;
|
||||
|
||||
use trx_core::audio::AudioStreamInfo;
|
||||
use trx_core::decode::{AprsPacket, CwEvent, DecodedMessage, Ft8Message};
|
||||
use trx_core::{DynResult, RigRequest, RigState};
|
||||
|
||||
/// Trait implemented by concrete frontends to expose a runner entrypoint.
|
||||
@@ -21,13 +25,112 @@ pub trait FrontendSpawner {
|
||||
) -> JoinHandle<()>;
|
||||
}
|
||||
|
||||
type FrontendSpawnFn = fn(
|
||||
pub type FrontendSpawnFn = fn(
|
||||
watch::Receiver<RigState>,
|
||||
mpsc::Sender<RigRequest>,
|
||||
Option<String>,
|
||||
SocketAddr,
|
||||
) -> JoinHandle<()>;
|
||||
|
||||
/// Context for registering and spawning frontends.
|
||||
pub struct FrontendRegistrationContext {
|
||||
spawners: HashMap<String, FrontendSpawnFn>,
|
||||
}
|
||||
|
||||
impl FrontendRegistrationContext {
|
||||
/// Create a new empty registration context.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
spawners: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Register a frontend spawner under a stable name (e.g. "http").
|
||||
pub fn register_frontend(&mut self, name: &str, spawner: FrontendSpawnFn) {
|
||||
let key = normalize_name(name);
|
||||
self.spawners.insert(key, spawner);
|
||||
}
|
||||
|
||||
/// Check whether a frontend name is registered.
|
||||
pub fn is_frontend_registered(&self, name: &str) -> bool {
|
||||
let key = normalize_name(name);
|
||||
self.spawners.contains_key(&key)
|
||||
}
|
||||
|
||||
/// List registered frontend names.
|
||||
pub fn registered_frontends(&self) -> Vec<String> {
|
||||
let mut names: Vec<String> = self.spawners.keys().cloned().collect();
|
||||
names.sort();
|
||||
names
|
||||
}
|
||||
|
||||
/// Spawn a registered frontend by name.
|
||||
pub fn spawn_frontend(
|
||||
&self,
|
||||
name: &str,
|
||||
state_rx: watch::Receiver<RigState>,
|
||||
rig_tx: mpsc::Sender<RigRequest>,
|
||||
callsign: Option<String>,
|
||||
listen_addr: SocketAddr,
|
||||
) -> DynResult<JoinHandle<()>> {
|
||||
let key = normalize_name(name);
|
||||
let spawner = self
|
||||
.spawners
|
||||
.get(&key)
|
||||
.ok_or_else(|| format!("Unknown frontend: {}", name))?;
|
||||
Ok(spawner(state_rx, rig_tx, callsign, listen_addr))
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FrontendRegistrationContext {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Runtime context for frontend operation, containing audio channels and decode state.
|
||||
pub struct FrontendRuntimeContext {
|
||||
/// Audio RX broadcast channel (server → browser)
|
||||
pub audio_rx: Option<broadcast::Sender<Bytes>>,
|
||||
/// Audio TX channel (browser → server)
|
||||
pub audio_tx: Option<mpsc::Sender<Bytes>>,
|
||||
/// Audio stream info watch channel
|
||||
pub audio_info: Option<watch::Receiver<Option<AudioStreamInfo>>>,
|
||||
/// Decode message broadcast channel
|
||||
pub decode_rx: Option<broadcast::Sender<DecodedMessage>>,
|
||||
/// APRS decode history (timestamp, packet)
|
||||
pub aprs_history: Arc<Mutex<VecDeque<(Instant, AprsPacket)>>>,
|
||||
/// CW decode history (timestamp, event)
|
||||
pub cw_history: Arc<Mutex<VecDeque<(Instant, CwEvent)>>>,
|
||||
/// FT8 decode history (timestamp, message)
|
||||
pub ft8_history: Arc<Mutex<VecDeque<(Instant, Ft8Message)>>>,
|
||||
/// Authentication tokens for HTTP-JSON frontend
|
||||
pub auth_tokens: HashSet<String>,
|
||||
}
|
||||
|
||||
impl FrontendRuntimeContext {
|
||||
/// Create a new empty runtime context.
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
audio_rx: None,
|
||||
audio_tx: None,
|
||||
audio_info: None,
|
||||
decode_rx: None,
|
||||
aprs_history: Arc::new(Mutex::new(VecDeque::new())),
|
||||
cw_history: Arc::new(Mutex::new(VecDeque::new())),
|
||||
ft8_history: Arc::new(Mutex::new(VecDeque::new())),
|
||||
auth_tokens: HashSet::new(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Default for FrontendRuntimeContext {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
// Legacy global registry for plugin compatibility
|
||||
struct FrontendRegistry {
|
||||
spawners: HashMap<String, FrontendSpawnFn>,
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user