Let an account change the playlists it owns

Reading a server-side playlist was all the app could do: the write half of
both APIs was missing, and the only thing that knew which credential to use
was the subscription layer. So playlists arrive here as an account concern —
hand AccountService an account and it finds the instance, reads the secret,
refuses what the backend cannot do, and records what the answer implies
about the login.

Capabilities decide, never the instance type: a privacy setting sent to a
backend that has none is dropped rather than refused, because the playlist
is still perfectly creatable there. Every call captures the credential it
used and re-reads it before recording an outcome, so a late 401 cannot
reject the login the user just fixed, nor a late success clear a rejection
it never earned.

Removal is where the backends stop being alike. Invidious names the row
itself with an indexId that survives refetches, so it removes directly.
Piped removes by position, and a position is only true of the fetch that
minted it — so the playlist is refetched and the position checked to still
hold the same video before anything is deleted. That narrows the window; it
cannot close it, and only the refetch afterwards shows what really happened.

The cache exists so that refetch cannot be forgotten. Every mutation goes
through it and republishes what it touched — on failure as much as on
success, because a write that reported failure may still have landed — and
nothing is ever edited optimistically. It is in memory alone: the data
belongs to a login rather than to this device, and a stored Piped entry id
would be aimed at whatever occupies that slot next launch. A per-account
generation fences results that come back after the account was cleared,
since clearing has never cancelled the request that was filling it.

Along the way, clients minted per instance sent no User-Agent at all —
instances that whitelist the Yattee prefix answered them with a 401 the
shared client never earned. The factory now bakes the app's User-Agent in
at construction, where no request can outrun it.
This commit is contained in:
Arkadiusz Fal
2026-09-02 23:29:13 +02:00
parent de4ee9a728
commit b2741bbb32
12 changed files with 1926 additions and 15 deletions

View File

@@ -73,3 +73,14 @@ struct RemotePlaylistSnapshot: Hashable, Sendable {
self.entries = entries
}
}
/// Who may see a server-side playlist.
///
/// The raw values are the words Invidious accepts. Piped has no visibility
/// concept at all — its capability row says so, and the write layer drops the
/// value rather than inventing one.
enum PlaylistPrivacy: String, Codable, Hashable, Sendable, CaseIterable {
case `public`
case unlisted
case `private`
}