qconnect: on-connect queue push runs mixed-queue guard
The TransportConnected handler pushed the live queue via loadQconnectQueue with no source check, shipping local/Plex row ids as Qobuz ids. The frontend display queue (getQueueState) carries no `source`/`is_local`, so wire the push to getBackendQueueState() whose tracks DO carry them (v2_get_queue_state maps source per track in commands_v2/queue.rs). Route the ordered snapshot ([history, current, upcoming]) through assessQconnectQueueSync: refuse the whole push on a mixed queue (no silent subset) and toast the user via the new qconnect.mixedQueueNotCast key (all five locales). Lock the refuse-not-subset contract with a unit test; page wiring covered by svelte-check + manual verify. cc
This commit is contained in:
7 files changed
+53
-14
No files matched your search
@@ -1810,6 +1810,7 @@
|
||||
"endpointLabel": "Endpunkt",
|
||||
"lastErrorLabel": "Letzter Fehler",
|
||||
"queueStateTitle": "Remote-Warteschlange",
|
||||
"mixedQueueNotCast": "Gemischte Warteschlange nicht an Qobuz Connect gesendet (enthält lokale oder Plex-Titel).",
|
||||
"queueVersionLabel": "Version",
|
||||
"queueItemsLabel": "Warteschlangeneinträge",
|
||||
"autoplayModeLabel": "Autoplay",
|
||||
|
||||
@@ -1810,6 +1810,7 @@
|
||||
"endpointLabel": "Endpoint",
|
||||
"lastErrorLabel": "Last error",
|
||||
"queueStateTitle": "Remote Queue",
|
||||
"mixedQueueNotCast": "Mixed queue not cast to Qobuz Connect (contains local or Plex tracks).",
|
||||
"queueVersionLabel": "Version",
|
||||
"queueItemsLabel": "Queue items",
|
||||
"autoplayModeLabel": "Autoplay",
|
||||
|
||||
@@ -1810,6 +1810,7 @@
|
||||
"endpointLabel": "Endpoint",
|
||||
"lastErrorLabel": "Ultimo error",
|
||||
"queueStateTitle": "Cola remota",
|
||||
"mixedQueueNotCast": "La cola mixta no se envió a Qobuz Connect (tiene pistas locales o de Plex).",
|
||||
"queueVersionLabel": "Version",
|
||||
"queueItemsLabel": "Items en cola",
|
||||
"autoplayModeLabel": "Autoplay",
|
||||
|
||||
@@ -1810,6 +1810,7 @@
|
||||
"endpointLabel": "Point d'accès",
|
||||
"lastErrorLabel": "Dernière erreur",
|
||||
"queueStateTitle": "File d'attente distante",
|
||||
"mixedQueueNotCast": "File d'attente mixte non envoyée à Qobuz Connect (contient des pistes locales ou Plex).",
|
||||
"queueVersionLabel": "Version",
|
||||
"queueItemsLabel": "Éléments de la file",
|
||||
"autoplayModeLabel": "Lecture auto",
|
||||
|
||||
@@ -1810,6 +1810,7 @@
|
||||
"endpointLabel": "Endpoint",
|
||||
"lastErrorLabel": "Último erro",
|
||||
"queueStateTitle": "Fila remota",
|
||||
"mixedQueueNotCast": "Fila mista não enviada ao Qobuz Connect (contém faixas locais ou do Plex).",
|
||||
"queueVersionLabel": "Versão",
|
||||
"queueItemsLabel": "Itens da fila",
|
||||
"autoplayModeLabel": "Autoplay",
|
||||
|
||||
@@ -65,6 +65,16 @@ describe('assessQconnectQueueSync', () => {
|
||||
blockedTrackIds: []
|
||||
});
|
||||
});
|
||||
|
||||
it('refuses (not subset) when queue is mixed — on-connect contract', () => {
|
||||
const r = assessQconnectQueueSync([
|
||||
buildTrack(1, { source: 'qobuz' }),
|
||||
buildTrack(2, { source: 'local' })
|
||||
]);
|
||||
expect(r.syncable).toBe(false);
|
||||
expect(r.reason).toBe('queue_contains_non_qobuz_tracks');
|
||||
expect(r.trackIds).toEqual([]); // subset NOT returned
|
||||
});
|
||||
});
|
||||
|
||||
describe('assessQconnectQueueSync — offline-cache', () => {
|
||||
|
||||
+38
-14
@@ -357,10 +357,13 @@
|
||||
handleAddToFavorites,
|
||||
addToPlaylist,
|
||||
shareQobuzTrackLink,
|
||||
shareSonglinkTrack,
|
||||
loadQconnectQueue
|
||||
shareSonglinkTrack
|
||||
} from '$lib/services/trackActions';
|
||||
import { replacePlaybackQueue } from '$lib/services/queuePlaybackService';
|
||||
import {
|
||||
replacePlaybackQueue,
|
||||
assessQconnectQueueSync,
|
||||
syncQconnectQueueFromTracks
|
||||
} from '$lib/services/queuePlaybackService';
|
||||
import type {
|
||||
QconnectDiagnosticsPayload,
|
||||
QconnectQueueSnapshot,
|
||||
@@ -5726,17 +5729,38 @@
|
||||
// Delay briefly so the QConnect session setup (ask_for_queue_state etc.)
|
||||
// completes before we try to push our queue.
|
||||
setTimeout(() => {
|
||||
const queueState = getQueueState();
|
||||
const trackIds = queueState.queue
|
||||
.map(item => item.trackId ?? parseInt(item.id))
|
||||
.filter((id): id is number => typeof id === 'number' && !isNaN(id) && id > 0);
|
||||
if (trackIds.length > 0) {
|
||||
console.log('[QConnect] Pushing local queue to remote on connect (%d tracks)', trackIds.length);
|
||||
loadQconnectQueue(trackIds, 0).then(ok => {
|
||||
if (ok) console.log('[QConnect] Local queue pushed to remote on connect');
|
||||
else console.warn('[QConnect] Local queue NOT pushed on connect (rejected or failed)');
|
||||
}).catch(err => console.error('[QConnect] Local queue push on connect error:', err));
|
||||
}
|
||||
void (async () => {
|
||||
// Use the BACKEND queue snapshot: its tracks carry `source`
|
||||
// and `is_local`, which the frontend display queue
|
||||
// (getQueueState) does not. The mixed-queue guard needs them
|
||||
// to refuse pushing a local/Plex row id to a Qobuz renderer.
|
||||
const state = await getBackendQueueState();
|
||||
if (!state) {
|
||||
console.warn('[QConnect] On-connect push skipped: no backend queue snapshot');
|
||||
return;
|
||||
}
|
||||
const tracks = [
|
||||
...state.history,
|
||||
...(state.current_track ? [state.current_track] : []),
|
||||
...state.upcoming
|
||||
];
|
||||
// Start index = the current track's position in the ordered list.
|
||||
const startIndex = state.current_track ? state.history.length : 0;
|
||||
const assessment = assessQconnectQueueSync(tracks);
|
||||
if (!assessment.syncable) {
|
||||
console.warn('[QConnect] On-connect push refused: reason=%s blocked=%o',
|
||||
assessment.reason, assessment.blockedTrackIds);
|
||||
if (assessment.reason === 'queue_contains_non_qobuz_tracks') {
|
||||
showToast($t('qconnect.mixedQueueNotCast'), 'warning');
|
||||
}
|
||||
return;
|
||||
}
|
||||
console.log('[QConnect] Pushing local queue to remote on connect (%d tracks)', tracks.length);
|
||||
const ok = await syncQconnectQueueFromTracks(tracks, startIndex, 'on-connect-push');
|
||||
console.log(ok
|
||||
? '[QConnect] Local queue pushed to remote on connect'
|
||||
: '[QConnect] Local queue NOT pushed on connect (rejected or failed)');
|
||||
})().catch(err => console.error('[QConnect] Local queue push on connect error:', err));
|
||||
}, 2000);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user