refactor(qconnect): relocate session topology types to qconnect-app (slice 2+4)

Move QconnectSessionState, QconnectRendererInfo, and
QconnectSessionRendererState from the Tauri adapter into the
frontend-agnostic qconnect-app::session module, re-exporting them from
the Tauri qconnect module so all existing super:: references and the
command surface compile unchanged. Field visibility widened to pub for
cross-crate access; serialized shape is unchanged.

First step of hoisting the QConnect session/liveness layer up so both
the Tauri adapter and a future Slint adapter share one session model.
qconnect crates + src-tauri qconnect tests green.

cc
This commit is contained in:
vicrodh committed 2026-06-02 12:19:15 -06:00
1 parent ec4b10fcb6
commit b6ddfc1b5f
3 files changed
+60 -38

No files matched your search

+2 -1
View File
@@ -17,7 +17,8 @@ pub use events::{NoOpEventSink, QconnectAppEvent, QconnectEventSink};
pub use session::{
compute_connection_state, deferred_join_reason, quality_from_max_audio_quality,
should_arm_renderer_watchdog, should_reask_queue_state, ConnectionDecision,
QconnectLifecycleState, RendererStatus, ServerActiveState,
QconnectLifecycleState, QconnectRendererInfo, QconnectSessionRendererState,
QconnectSessionState, RendererStatus, ServerActiveState,
JOIN_SESSION_REASON_CONTROLLER_REQUEST, JOIN_SESSION_REASON_RECONNECTION,
QCONNECT_RENDERER_LOST_TIMEOUT_MS,
};
+51
View File
@@ -186,6 +186,57 @@ pub fn quality_from_max_audio_quality(level: Option<i32>) -> Quality {
}
}
// ---------------------------------------------------------------------------
// Session topology types (relocated from the Tauri adapter — slice 2+4).
//
// These are pure data describing who is in a QConnect session and the cloud's
// per-renderer view. They move here (frontend-agnostic) so both the Tauri and
// Slint adapters share one session-topology model under a single lock. Field
// visibility is widened to `pub` because the adapters access these fields
// across the crate boundary; serialized shape is unchanged.
// ---------------------------------------------------------------------------
/// Per-renderer cached state derived from session-management events
/// (RENDERER_STATE_UPDATED etc.): the cloud's latest view of one renderer.
#[derive(Debug, Clone, Default)]
pub struct QconnectSessionRendererState {
pub active: Option<bool>,
pub playing_state: Option<i32>,
pub current_position_ms: Option<u64>,
pub current_queue_item_id: Option<u64>,
pub volume: Option<i32>,
pub muted: Option<bool>,
pub max_audio_quality: Option<i32>,
pub loop_mode: Option<i32>,
pub shuffle_mode: Option<bool>,
pub updated_at_ms: u64,
}
/// Session topology — stored from session management events (types 81-87):
/// who is in the session, who is active, and which renderer is us.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct QconnectSessionState {
pub session_uuid: Option<String>,
pub active_renderer_id: Option<i32>,
pub local_renderer_id: Option<i32>,
pub renderers: Vec<QconnectRendererInfo>,
}
/// One renderer registered in the QConnect session.
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QconnectRendererInfo {
pub renderer_id: i32,
pub device_uuid: Option<String>,
pub friendly_name: Option<String>,
pub brand: Option<String>,
pub model: Option<String>,
pub device_type: Option<i32>,
/// capabilities.volume_remote_control. 1 == ALLOWED. None == not advertised
/// (treated as allowed to avoid regressing renderers that omit it).
#[serde(default)]
pub volume_remote_control: Option<i32>,
}
#[cfg(test)]
mod tests {
use super::*;
+7 -37
View File
@@ -7,7 +7,7 @@
use qconnect_app::{QConnectQueueState, QConnectRendererState};
use qconnect_core::QueueItem;
use serde::{Deserialize, Serialize};
use serde::Serialize;
use super::queue_resolution::{
find_cursor_index_by_queue_item_id, normalize_current_queue_item_id_from_queue_state,
@@ -25,6 +25,12 @@ use super::{
/// is not named directly on the Tauri side; it lives in `qconnect_app`.)
pub(super) use qconnect_app::{compute_connection_state, ServerActiveState};
/// Session topology types now live in the frontend-agnostic `qconnect_app::session`
/// module (slice 2+4). Re-exported here so existing `super::session::…` /
/// `super::…` references inside this module compile unchanged, and so the Tauri
/// command surface keeps the same serialized shape.
pub use qconnect_app::{QconnectRendererInfo, QconnectSessionRendererState, QconnectSessionState};
pub(super) fn queue_item_snapshot_for_cursor(
queue: &QConnectQueueState,
cursor: QconnectOrderedQueueCursor,
@@ -40,20 +46,6 @@ pub(super) fn queue_item_snapshot_for_cursor(
}
}
#[derive(Debug, Clone, Default)]
pub(crate) struct QconnectSessionRendererState {
pub(super) active: Option<bool>,
pub(super) playing_state: Option<i32>,
pub(super) current_position_ms: Option<u64>,
pub(super) current_queue_item_id: Option<u64>,
pub(super) volume: Option<i32>,
pub(super) muted: Option<bool>,
pub(super) max_audio_quality: Option<i32>,
pub(super) loop_mode: Option<i32>,
pub(super) shuffle_mode: Option<bool>,
pub(super) updated_at_ms: u64,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(crate) struct QconnectFileAudioQualitySnapshot {
pub(super) sampling_rate: i32,
@@ -62,14 +54,6 @@ pub(crate) struct QconnectFileAudioQualitySnapshot {
pub(super) audio_quality: i32,
}
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct QconnectSessionState {
pub session_uuid: Option<String>,
pub active_renderer_id: Option<i32>,
pub local_renderer_id: Option<i32>,
pub renderers: Vec<QconnectRendererInfo>,
}
#[derive(Debug, Clone, Serialize)]
pub(crate) struct QconnectRendererReportDebugEvent {
pub(super) requested_current_queue_item_id: Option<i32>,
@@ -87,20 +71,6 @@ pub(crate) struct QconnectRendererReportDebugEvent {
pub(super) resolution_strategy: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QconnectRendererInfo {
pub renderer_id: i32,
pub device_uuid: Option<String>,
pub friendly_name: Option<String>,
pub brand: Option<String>,
pub model: Option<String>,
pub device_type: Option<i32>,
/// capabilities.volume_remote_control. 1 == ALLOWED. None == not advertised
/// (treated as allowed to avoid regressing renderers that omit it).
#[serde(default)]
pub volume_remote_control: Option<i32>,
}
/// Whether the active renderer permits remote volume control. Absent capability
/// (None) defaults to allowed; only an explicit non-ALLOWED value disables.
pub(super) fn renderer_allows_remote_volume(info: &QconnectRendererInfo) -> bool {