fix(jellyfin): ask the server who may delete before offering it

Jellyfin never consults IsAdministrator when authorizing a library
delete: BaseItem.IsAuthorizedToDelete looks at EnableContentDeletion and
the per-library grant, and only the first user a server creates gets the
former for free. Gating the "Delete from server" entry on the admin bit
therefore offered a destructive action that answers 401 to later
administrators, and hid it from plain users who do hold the grant.

Ask the server per item instead, through the new
MediaDeletionPermissionClient capability: BaseItemDto.CanDelete already
folds the global grant, the per-library grant, and item state such as
missing files or an in-progress recording. The probe runs when a menu
opens on a deletable kind, costs ~0.5 KB, carries a whole-request
deadline because the client's own budget covers connect and receive
separately, and fails closed on anything unknown. Plex keeps its
account-level owner/admin gate; it has no per-item permission on the
wire.

close #1749
This commit is contained in:
edde746
2026-08-02 07:08:14 +02:00
parent bc0d14a749
commit 2a7e5f4f9c
7 changed files with 615 additions and 10 deletions
+9
View File
@@ -52,6 +52,15 @@ class MediaServerTimeouts {
/// `/System/Info/Public` and `/Users/Me`.
static const jellyfinProbe = Duration(seconds: 8);
/// Per-item delete-permission probe. Shorter than [jellyfinProbe] because it
/// blocks a context menu from opening: a server that is nominally online but
/// hung must not hold the menu for a health-sweep budget. Unlike the other
/// values here it is also applied as a whole-request deadline by the caller
/// (the per-request budget covers the connect and receive phases
/// individually), and expiry fails closed — no delete entry — so the ceiling
/// only ever costs an entry, never safety.
static const jellyfinDeletePermission = Duration(seconds: 3);
/// Best-effort `/Sessions/Logout` timeout — short because the call is
/// fire-and-forget; the token is removed locally regardless.
static const jellyfinSignOut = Duration(seconds: 5);