fix(qconnect): treat omitted cloud queue ids as zero

cx
This commit is contained in:
vicrodh committed 2026-03-12 20:56:56 -06:00
1 parent 6cdacd0c29
commit e748ab6616
4 files changed
+175 -15

No files matched your search

+109 -12
View File
@@ -185,9 +185,7 @@ fn decode_queue_server_event(
};
map_ctrl_max_audio_quality_changed(payload)?
}
code if code
== QConnectMessageType::MessageTypeSrvrCtrlFileAudioQualityChanged as i32 =>
{
code if code == QConnectMessageType::MessageTypeSrvrCtrlFileAudioQualityChanged as i32 => {
let Some(payload) = message.srvr_ctrl_file_audio_quality_changed else {
return Ok(None);
};
@@ -285,9 +283,7 @@ fn resolve_queue_message_type(message: &QConnectMessage) -> Option<i32> {
return Some(QConnectMessageType::MessageTypeSrvrCtrlFileAudioQualityChanged as i32);
}
if message.srvr_ctrl_device_audio_quality_changed.is_some() {
return Some(
QConnectMessageType::MessageTypeSrvrCtrlDeviceAudioQualityChanged as i32,
);
return Some(QConnectMessageType::MessageTypeSrvrCtrlDeviceAudioQualityChanged as i32);
}
None
})
@@ -768,7 +764,8 @@ fn map_tracks_added_from_autoplay(
// --- Session management event mappers ---
fn map_session_state(payload: CtrlSessionStateMessage) -> Result<QueueServerEvent, ProtocolError> {
let session_uuid = uuid_bytes_to_string_opt(payload.session_uuid, "session_state.session_uuid")?;
let session_uuid =
uuid_bytes_to_string_opt(payload.session_uuid, "session_state.session_uuid")?;
Ok(QueueServerEvent {
event_type: QueueEventType::SrvrCtrlSessionState,
action_uuid: None,
@@ -974,8 +971,7 @@ fn queue_track_to_json(
ProtocolError::InvalidPayload("missing required field 'queue_track.track_id'".to_string())
})?;
let queue_item_id =
optional_i32_to_u64_named(track.queue_item_id, "queue_track.queue_item_id")?
.unwrap_or(track_id);
optional_i32_to_u64_named(track.queue_item_id, "queue_track.queue_item_id")?.unwrap_or(0);
Ok(json!({
"track_context_uuid": context_uuid.unwrap_or_default(),
@@ -994,7 +990,7 @@ fn queue_track_with_context_to_json(track: QueueTrackWithContext) -> Result<Valu
track.queue_item_id,
"queue_track_with_context.queue_item_id",
)?
.unwrap_or(track_id);
.unwrap_or(0);
let context_uuid =
uuid_bytes_to_string_opt(track.context_uuid, "queue_track_with_context.context_uuid")?
.unwrap_or_default();
@@ -1073,8 +1069,8 @@ mod tests {
use crate::queue_command_proto::{
QConnectMessage, QConnectMessageType, QConnectMessages, QueueTrack, QueueTrackWithContext,
QueueTracksAddedMessage, QueueVersionRef, RendererMuteVolumeMessage,
RendererSetStateMessage,
QueueTracksAddedMessage, QueueTracksLoadedMessage, QueueVersionRef,
RendererMuteVolumeMessage, RendererSetStateMessage,
};
use super::{decode_queue_server_events, decode_renderer_server_commands};
@@ -1170,6 +1166,107 @@ mod tests {
assert_eq!(commands[0].payload["current_position"], 42_000);
}
#[test]
fn decodes_missing_queue_item_id_as_zero_in_queue_events() {
let message = QConnectMessage {
message_type: Some(QConnectMessageType::MessageTypeSrvrCtrlQueueTracksLoaded as i32),
srvr_ctrl_queue_tracks_loaded: Some(QueueTracksLoadedMessage {
queue_version: Some(QueueVersionRef {
major: Some(7),
minor: Some(1),
}),
action_uuid: Some(
uuid::Uuid::parse_str("f2b1f0a4-3d0a-4a67-b234-4d1df4d58c8f")
.expect("uuid")
.as_bytes()
.to_vec(),
),
tracks: vec![QueueTrack {
queue_item_id: None,
track_id: Some(126_886_862),
}],
queue_position: Some(0),
shuffle_mode: Some(false),
shuffle_seed: None,
shuffle_pivot_queue_item_id: None,
context_uuid: Some(
uuid::Uuid::parse_str("4c321d71-aef2-4c98-8cd3-8d2ad4bfe0f4")
.expect("context uuid")
.as_bytes()
.to_vec(),
),
autoplay_reset: Some(true),
autoplay_loading: Some(false),
queue_hash: None,
}),
..Default::default()
};
let batch = QConnectMessages {
messages_time: Some(1),
messages_id: Some(4),
messages: vec![message],
};
let encoded = batch.encode_to_vec();
let events = decode_queue_server_events(&encoded).expect("decode queue events");
assert_eq!(events.len(), 1);
assert_eq!(events[0].payload["tracks"][0]["queue_item_id"], 0);
assert_eq!(events[0].payload["tracks"][0]["track_id"], 126_886_862);
}
#[test]
fn decodes_missing_queue_item_id_as_zero_in_renderer_state() {
let message = QConnectMessage {
message_type: Some(QConnectMessageType::MessageTypeSrvrRndrSetState as i32),
srvr_rndr_set_state: Some(RendererSetStateMessage {
playing_state: Some(2),
current_position: Some(8_000),
queue_version: Some(QueueVersionRef {
major: Some(7),
minor: Some(1),
}),
current_track: Some(QueueTrackWithContext {
queue_item_id: None,
track_id: Some(126_886_862),
context_uuid: Some(
uuid::Uuid::parse_str("4c321d71-aef2-4c98-8cd3-8d2ad4bfe0f4")
.expect("context uuid")
.as_bytes()
.to_vec(),
),
}),
next_track: Some(QueueTrackWithContext {
queue_item_id: Some(1),
track_id: Some(25_584_418),
context_uuid: Some(
uuid::Uuid::parse_str("4c321d71-aef2-4c98-8cd3-8d2ad4bfe0f4")
.expect("context uuid")
.as_bytes()
.to_vec(),
),
}),
}),
..Default::default()
};
let batch = QConnectMessages {
messages_time: Some(1),
messages_id: Some(5),
messages: vec![message],
};
let encoded = batch.encode_to_vec();
let commands = decode_renderer_server_commands(&encoded).expect("decode renderer commands");
assert_eq!(commands.len(), 1);
assert_eq!(commands[0].payload["current_track"]["queue_item_id"], 0);
assert_eq!(
commands[0].payload["current_track"]["track_id"],
126_886_862
);
assert_eq!(commands[0].payload["next_track"]["queue_item_id"], 1);
}
#[test]
fn decodes_srvr_rndr_mute_volume_command_batch() {
let message = QConnectMessage {
+44 -2
View File
@@ -423,8 +423,14 @@ fn map_renderer_report(report: &RendererReport) -> Result<QConnectMessage, Proto
.unwrap_or(report.queue_version_ref);
let current_position = optional_i32(&report.payload, "current_position")?;
let duration = optional_i32(&report.payload, "duration")?;
let current_qid = optional_i32(&report.payload, "current_queue_item_id")?;
let next_qid = optional_i32(&report.payload, "next_queue_item_id")?;
let current_qid = normalize_renderer_state_queue_item_id(optional_i32(
&report.payload,
"current_queue_item_id",
)?);
let next_qid = normalize_renderer_state_queue_item_id(optional_i32(
&report.payload,
"next_queue_item_id",
)?);
let playing_state = optional_i32(&report.payload, "playing_state")?;
let playback_position = current_position.map(|value| PlaybackPositionMessage {
timestamp: Some(now_ms()),
@@ -493,6 +499,13 @@ fn map_renderer_report(report: &RendererReport) -> Result<QConnectMessage, Proto
}
}
fn normalize_renderer_state_queue_item_id(queue_item_id: Option<i32>) -> Option<i32> {
match queue_item_id {
Some(0) | None => None,
other => other,
}
}
fn to_proto_queue_version(
version: qconnect_core::QueueVersion,
) -> Result<QueueVersionRef, ProtocolError> {
@@ -1172,6 +1185,35 @@ mod tests {
assert_eq!(state.next_queue_item_id, Some(9003));
}
#[test]
fn encodes_renderer_state_updated_omitting_zero_queue_item_ids() {
let report = RendererReport::new(
RendererReportType::RndrSrvrStateUpdated,
"9b7db27a-efac-4fc4-9f22-6034b4444697",
QueueVersion::new(7, 2),
json!({
"playing_state": 2,
"buffer_state": 2,
"current_position": 8000,
"duration": 679000,
"current_queue_item_id": 0,
"next_queue_item_id": 18
}),
);
let payload = encode_renderer_report_batch(&report).expect("renderer report batch");
let decoded = QConnectMessages::decode(payload.as_slice()).expect("decode batch");
let message = &decoded.messages[0];
let state = message
.rndr_srvr_state_updated
.as_ref()
.and_then(|payload| payload.state.as_ref())
.expect("state payload");
assert_eq!(state.current_queue_item_id, None);
assert_eq!(state.next_queue_item_id, Some(18));
}
#[test]
fn build_renderer_outbound_envelope_uses_renderer_message_type() {
let report = RendererReport::new(