feat(qconnect): queue_hash divergence scaffold + AskForQueueState trigger
Comparison seam joins the existing resync path. Local hash algorithm is a documented BLOCKING open question; stub returns None so detection stays inert (no spurious resyncs) until the Qobuz algorithm is verified. cc
This commit is contained in:
1 file changed
+42
-1
@@ -326,6 +326,14 @@ where
|
|||||||
) {
|
) {
|
||||||
should_trigger_resync = true;
|
should_trigger_resync = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// queue_hash divergence seam. Inert today (local hash algorithm
|
||||||
|
// is a BLOCKING unknown -> compute_local_queue_hash returns None),
|
||||||
|
// so this never fires spurious resyncs. When the algorithm lands,
|
||||||
|
// a mismatch will pull the authoritative QueueState.
|
||||||
|
if queue_hashes_diverge(&state.queue) {
|
||||||
|
should_trigger_resync = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
snapshot = state.queue.clone();
|
snapshot = state.queue.clone();
|
||||||
}
|
}
|
||||||
@@ -611,6 +619,28 @@ fn infer_buffer_state(playing_state: Option<i32>) -> Option<i32> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// BLOCKING OPEN QUESTION: the Qobuz `queue_hash` (field #100) algorithm is
|
||||||
|
/// undocumented. Until it is verified, this returns `None`, which keeps
|
||||||
|
/// `queue_hashes_diverge` inert so it can NOT fire spurious resyncs. Flipping
|
||||||
|
/// this to a real implementation is the single switch that turns on divergence
|
||||||
|
/// detection.
|
||||||
|
fn compute_local_queue_hash(_queue: &QConnectQueueState) -> Option<Vec<u8>> {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Algorithm-agnostic divergence seam. Only reports divergence when BOTH a
|
||||||
|
/// locally computed hash and a server-reported hash exist and differ. Because
|
||||||
|
/// `compute_local_queue_hash` returns `None` today, this is always `false`.
|
||||||
|
fn queue_hashes_diverge(queue: &QConnectQueueState) -> bool {
|
||||||
|
match (
|
||||||
|
compute_local_queue_hash(queue),
|
||||||
|
queue.last_server_queue_hash.as_ref(),
|
||||||
|
) {
|
||||||
|
(Some(local), Some(server)) => &local != server,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn map_server_event(event: &QueueServerEvent, current: &QConnectQueueState) -> QueueEvent {
|
fn map_server_event(event: &QueueServerEvent, current: &QConnectQueueState) -> QueueEvent {
|
||||||
let version = event
|
let version = event
|
||||||
.queue_version
|
.queue_version
|
||||||
@@ -968,7 +998,8 @@ mod tests {
|
|||||||
|
|
||||||
use crate::{QconnectAppEvent, QconnectEventSink};
|
use crate::{QconnectAppEvent, QconnectEventSink};
|
||||||
|
|
||||||
use super::QconnectApp;
|
use super::{queue_hashes_diverge, QconnectApp};
|
||||||
|
use qconnect_core::QConnectQueueState;
|
||||||
use qconnect_protocol::{
|
use qconnect_protocol::{
|
||||||
QueueCommandType, QueueEventType, QueueServerEvent, RendererCommandType,
|
QueueCommandType, QueueEventType, QueueServerEvent, RendererCommandType,
|
||||||
RendererServerCommand,
|
RendererServerCommand,
|
||||||
@@ -1186,6 +1217,16 @@ mod tests {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn queue_hashes_diverge_is_inert_without_local_algorithm() {
|
||||||
|
let mut queue = QConnectQueueState::default();
|
||||||
|
queue.last_server_queue_hash = Some(vec![9, 9, 9]);
|
||||||
|
assert!(
|
||||||
|
!queue_hashes_diverge(&queue),
|
||||||
|
"divergence detection must stay inert until the local hash algorithm is known"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn queue_state_event_surfaces_server_queue_hash() {
|
async fn queue_state_event_surfaces_server_queue_hash() {
|
||||||
let (app, _sink, _transport, _events_rx) = build_connected_app().await;
|
let (app, _sink, _transport, _events_rx) = build_connected_app().await;
|
||||||
|
|||||||
Reference in new issue
Block a user