From 00bab2ae63150bad67d348c63c3822883f049113 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Sat, 29 Aug 2026 23:56:18 +0200 Subject: [PATCH] Stop a playlist import that outlived its login MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Videos are read with one account's credential and written one at a time into a local playlist, which is not account-scoped: it belongs to whoever uses the device and syncs to iCloud. The sheet can be dismissed and somebody else signed in on the same server while the import is still running — nothing cancels it — and the rest of one person's private playlist would land in the other's library. The credential is checked before every write rather than only at the start. What was already written stays: it was imported under the login that asked for it. --- Yattee/Localizable.xcstrings | 11 +++++++++++ Yattee/Views/Settings/ImportPlaylistsView.swift | 15 +++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/Yattee/Localizable.xcstrings b/Yattee/Localizable.xcstrings index 9a4d7220..45de79f6 100644 --- a/Yattee/Localizable.xcstrings +++ b/Yattee/Localizable.xcstrings @@ -5006,6 +5006,17 @@ } } }, + "import.playlists.loginChanged" : { + "comment" : "Error message when the account was signed out or replaced during an import", + "localizations" : { + "en" : { + "stringUnit" : { + "state" : "translated", + "value" : "The login changed while importing, so the rest was not added." + } + } + } + }, "import.playlists.notLoggedIn" : { "comment" : "Error message when not logged in to Invidious", "localizations" : { diff --git a/Yattee/Views/Settings/ImportPlaylistsView.swift b/Yattee/Views/Settings/ImportPlaylistsView.swift index 0f01ca42..ce619a3e 100644 --- a/Yattee/Views/Settings/ImportPlaylistsView.swift +++ b/Yattee/Views/Settings/ImportPlaylistsView.swift @@ -370,6 +370,18 @@ struct ImportPlaylistsView: View { importProgress = (current: index + 1, total: total) } + // These videos are private to the login they were read with, + // and the destination is not: local playlists are shared by + // whoever uses the device and sync to iCloud. The sheet can be + // dismissed and somebody else signed in on the same server + // while this runs, so the credential is checked before every + // write rather than only at the start. What has already been + // written stays — it was imported under the login that asked + // for it. + guard appEnvironment.credential(forInstance: instance) == credential else { + throw ImportError.loginChanged + } + // Skip if already in playlist if localPlaylist.contains(videoID: video.id.videoID) { skippedCount += 1 @@ -425,6 +437,7 @@ struct ImportPlaylistsView: View { enum ImportError: LocalizedError { case notLoggedIn case notSupported + case loginChanged var errorDescription: String? { switch self { @@ -432,6 +445,8 @@ struct ImportPlaylistsView: View { return String(localized: "import.playlists.notLoggedIn") case .notSupported: return String(localized: "import.playlists.notSupported") + case .loginChanged: + return String(localized: "import.playlists.loginChanged") } } }