Skip to content

Commit 77e7ed1

Browse files
committed
fix: preserve unfinished playback-speed migrations for older clients
1 parent 37a009f commit 77e7ed1

2 files changed

Lines changed: 39 additions & 5 deletions

File tree

README.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,13 @@ The dedicated `/v1/channel_playback_speeds` endpoints and encrypted
2121
saved channel preferences, including playback speeds, in the encrypted
2222
`settings` collection. Current clients still read existing `playbackSpeeds` data
2323
to migrate it into `settings`, but no longer upload the deprecated collection.
24-
Its absence does not mark legacy encrypted migration as incomplete.
24+
After successfully syncing speeds into `settings`, current clients acknowledge
25+
this with `GET /v1/encrypted_sync?playback_speeds_in_settings=true`. For those
26+
requests, the deprecated collection is no longer required for migration completion.
27+
Requests without this acknowledgment retain the older completion rule so an
28+
interrupted migration can still discover speeds in the original encrypted document.
29+
The presence of opaque `settings` alone is not proof that speeds were migrated.
30+
Older clients may still recreate a deleted `playbackSpeeds` collection.
2531

2632
The dedicated plaintext endpoints will be removed on 1 October 2026. Until
2733
then, their responses include the standard `Deprecation` and `Sunset` headers.

src/handlers/encrypted_sync.rs

Lines changed: 32 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,8 @@ use crate::{WebData, get_db_conn};
1717
const MEBIBYTE: usize = 1024 * 1024;
1818
const MAX_ENCRYPTED_SYNC_BYTES: usize = 64 * MEBIBYTE;
1919
const MAX_ENCRYPTED_SYNC_ACCOUNT_BYTES: usize = 128 * MEBIBYTE;
20-
// Deprecated playbackSpeeds is read into settings by current clients and must
21-
// not be required to finish legacy document migration.
20+
// Current clients can acknowledge saved playback speeds in settings. Older
21+
// clients still require the deprecated collection to resume partial migrations.
2222
const LEGACY_ENCRYPTED_COLLECTIONS: [&str; 5] = [
2323
"subscriptions",
2424
"playlists",
@@ -27,6 +27,14 @@ const LEGACY_ENCRYPTED_COLLECTIONS: [&str; 5] = [
2727
"playlistBookmarks",
2828
];
2929

30+
#[derive(Debug, Default, serde::Deserialize, utoipa::IntoParams)]
31+
#[into_params(parameter_in = Query)]
32+
struct EncryptedSyncManifestQuery {
33+
/// The requesting client has successfully synced playback speeds into settings.
34+
#[serde(default)]
35+
playback_speeds_in_settings: bool,
36+
}
37+
3038
pub struct EncryptedSyncHandler {}
3139

3240
impl ScopedHandler for EncryptedSyncHandler {
@@ -74,11 +82,12 @@ fn collection_limit(collection: &str) -> HandlerResult<usize> {
7482
}
7583
}
7684

77-
#[utoipa::path(responses((status = OK, body = EncryptedSyncManifest)), security(("api_jwt_token" = [])))]
85+
#[utoipa::path(params(EncryptedSyncManifestQuery), responses((status = OK, body = EncryptedSyncManifest)), security(("api_jwt_token" = [])))]
7886
#[get("")]
7987
async fn get_encrypted_sync_manifest(
8088
account: Account,
8189
pool: WebData,
90+
query: web::Query<EncryptedSyncManifestQuery>,
8291
) -> HandlerResult<impl Responder> {
8392
let mut conn = get_db_conn!(pool);
8493
let documents = encrypted_sync::get_all(&mut conn, &account.id)
@@ -92,7 +101,11 @@ async fn get_encrypted_sync_manifest(
92101
.iter()
93102
.any(|document| document.collection == *collection)
94103
});
95-
let legacy_encrypted_data = !has_all_migrated_collections
104+
let has_migrated_playback_speeds = query.playback_speeds_in_settings
105+
|| documents
106+
.iter()
107+
.any(|document| document.collection == "playbackSpeeds");
108+
let legacy_encrypted_data = !(has_all_migrated_collections && has_migrated_playback_speeds)
96109
&& encrypted_sync::get_legacy_encrypted(&mut conn, &account.id)
97110
.await
98111
.map_err(|_| HandlerError::InternalDatabaseError)?
@@ -301,6 +314,15 @@ mod migration_tests {
301314
.status()
302315
.is_success()
303316
);
317+
// Even an existing settings ciphertext cannot prove that speeds
318+
// were migrated: the user may have excluded that setting.
319+
let request = test::TestRequest::get().uri("/sync").to_request();
320+
request.extensions_mut().insert(account.clone());
321+
let manifest: serde_json::Value = test::call_and_read_body_json(&app, request).await;
322+
assert_eq!(
323+
manifest["legacy_encrypted_data"],
324+
collection != "playbackSpeeds"
325+
);
304326
}
305327
for deleted in [false, true] {
306328
if deleted {
@@ -312,6 +334,12 @@ mod migration_tests {
312334
request.extensions_mut().insert(account.clone());
313335
let manifest: serde_json::Value = test::call_and_read_body_json(&app, request).await;
314336
assert_eq!(manifest["legacy_data"], false);
337+
assert_eq!(manifest["legacy_encrypted_data"], deleted);
338+
let request = test::TestRequest::get()
339+
.uri("/sync?playback_speeds_in_settings=true")
340+
.to_request();
341+
request.extensions_mut().insert(account.clone());
342+
let manifest: serde_json::Value = test::call_and_read_body_json(&app, request).await;
315343
assert_eq!(manifest["legacy_encrypted_data"], false);
316344
assert_eq!(
317345
manifest["collections"]

0 commit comments

Comments
 (0)