Files
tuliprox/backend/app/src/api/model/app_state/view.rs
T
euzuandGitHub 69040f022b Feature/maintenance (#916)
* Maintenance mode
* fix(hls): release origin slot before next origin I/O
2026-10-13 22:30:21 +02:00

119 lines
4.8 KiB
Rust

//! Views onto [`AppState`]: the subsets each subsystem actually reads.
//!
//! Every extracted subsystem takes a small context struct - the handles it
//! needs - rather than the server's root state. Those structs live in their own
//! crates and cannot name `AppState`, so something has to build them, and this
//! is it.
//!
//! # Why the trait is defined here
//!
//! The obvious shape - a `FromAppState` trait in `tuliprox-core`, implemented by
//! each context beside its own definition - does not compile. The orphan rule
//! allows `impl ForeignTrait for LocalType` or `impl LocalTrait for ForeignType`,
//! and that arrangement is neither: the trait would be core's, the context would
//! be (say) `tuliprox-hls`'s, and the impl would have to live in this binary,
//! which owns neither. It also could not be written in `tuliprox-hls`, because
//! nothing there can name `AppState`.
//!
//! So the trait is local to the binary and the contexts are the foreign types.
//! That is the one arrangement the orphan rule permits, and it puts every view
//! in one file - which is the property worth having anyway: "what does this
//! subsystem read from the server?" is answerable without opening its crate.
//!
//! # What is not here
//!
//! `WeakHlsCtx` is derived from `HlsCtx`, not from `AppState`. `SharedStreamCtx`
//! borrows rather than owning its handles, so it cannot be built by value.
//! Neither is a view in this sense.
use super::AppState;
/// A subset of [`AppState`] that one subsystem reads.
pub trait AppStateView {
/// Build the view by cloning the handles it names.
fn from_app_state(state: &AppState) -> Self;
}
impl AppState {
/// The subset that `V` reads.
///
/// Prefer the named accessors below at call sites; this is what they call,
/// and what generic code can use.
pub fn view<V: AppStateView>(&self) -> V { V::from_app_state(self) }
}
/// Declare a view: the accessor name, the context type, and the `AppState`
/// fields it clones. Field names are the same on both sides by construction -
/// a view renaming a handle would be a view lying about what it reads.
///
/// The one permitted exception is `ctx_field <- state_path`, for a context
/// that is generic over a handle `AppState` holds concretely - `events <-
/// event_manager` - or for a handle `AppState` keeps inside a container -
/// `hls_proxy <- hls.proxy`. The context names the handle by the role it
/// plays; the view still reads exactly the handle it names.
///
/// A context that holds a handle as a trait object says so with `as` -
/// `maintenance as Arc<dyn MaintenanceGate>`.
macro_rules! view_field {
($state:ident, $field:ident) => {
::core::clone::Clone::clone(&$state.$field)
};
($state:ident, $field:ident, $($source:ident).+) => {
::core::clone::Clone::clone(&$state.$($source).+)
};
}
macro_rules! app_state_views {
($(
$(#[$meta:meta])*
$accessor:ident => $ctx:ty { $($field:ident $(<- $($source:ident).+)? $(as $cast:ty)?),+ $(,)? }
)+) => {
$(
impl AppStateView for $ctx {
fn from_app_state(state: &AppState) -> Self {
Self { $($field: view_field!(state, $field $(, $($source).+)?) $(as $cast)?),+ }
}
}
impl AppState {
$(#[$meta])*
#[must_use]
pub fn $accessor(&self) -> $ctx {
self.view()
}
}
)+
};
}
app_state_views! {
/// The handles the DVR needs: the recording queue and what feeds it.
recording_ctx => crate::api::model::recording::recording_ctx::RecordingCtx<std::sync::Arc<tuliprox_session::EventManager>> {
app_config, recordings, events <- event_manager, http_clients, recording_capacity,
}
/// The handles the HLS proxy needs: itself, plus provider allocation and
/// session accounting.
hls_ctx => crate::api::model::hls_cache::HlsCtx {
app_config, hls_proxy <- hls.proxy, active_provider, connection_manager, active_users,
}
/// The handles admission reads: it decides over connections and users.
admission_ctx => tuliprox_session::admission::AdmissionCtx {
app_config, active_users, connection_manager,
}
/// The handles the provider side of a stream needs: the redirect-aware
/// HTTP clients.
provider_stream_ctx => tuliprox_session::stream_ctx::ProviderStreamCtx {
app_config, active_provider, connection_manager, http_clients,
}
/// The handles the background metadata worker reads.
metadata_update_ctx => tuliprox_metadata::ctx::BoundMetadataUpdateCtx {
app_config, active_provider, connection_manager, events <- event_manager, playlists,
update_guard <- playlist_updates.update_guard, http_clients,
maintenance as std::sync::Arc<dyn tuliprox_core::model::MaintenanceGate>,
}
}