Files
plezy/lib/utils/deletion_notifier.dart
T
edde746 c228a12d1e fix(media): preserve profile, source, and focus identity across refreshes
Library refreshes could move focus, catalog lookups could cross profile or query boundaries, and detail labels could describe a different source from playback.

Keep hub and grid focus with committed item identities. Pace pushed deletions without delaying local eviction. Bind push channels to committed authentication sessions and catalog completeness to the active profile and effective query. Resolve preview source and container defaults with playback selection rules.

The shared client contracts and all consumers migrate together. Include regression fixtures and document safe host-transfer compatibility.

Verified: 6826 Flutter tests passed, 5 skipped; aggregate quality checks and final analyzer/formatting checks passed. All 86 owned paths match the isolated validated snapshot.
2026-09-06 04:34:58 +02:00

96 lines
3.0 KiB
Dart

import '../media/media_item.dart';
import '../media/ids.dart';
import 'app_logger.dart';
import 'base_notifier.dart';
import 'global_key_utils.dart';
import 'hierarchical_event_mixin.dart';
import 'media_event_keys.dart';
/// The owner of server reconciliation after immediate local eviction.
enum DeletionOrigin { local, serverPush }
/// Event representing a media item deletion with parent chain for hierarchical invalidation
class DeletionEvent with HierarchicalEventMixin {
/// The id of the deleted item (Plex ratingKey, Jellyfin GUID, …).
@override
final String itemId;
/// Composite key: serverId:itemId
@override
final String globalKey;
/// Server this item belongs to
@override
final ServerId serverId;
/// Parent chain for hierarchical invalidation
/// For an episode: [seasonId, showId]
/// For a season: [showId]
/// For a movie: []
@override
final List<String> parentChain;
/// Media type of the deleted item
final String mediaType;
/// Number of leaf items (episodes) contained in the deleted item.
/// For an episode: 1. For a season: its episode count. For a show: its total episode count.
final int leafCount;
/// True if only the local download was deleted (not the server-side media).
/// Screens should only remove items for download deletions when in offline mode.
final bool isDownloadOnly;
/// Pushes also publish a coarse library change. Paced consumers leave their
/// network reconciliation to that event; item-specific consumers may still
/// reconcile their own detail state.
final DeletionOrigin origin;
DeletionEvent({
required this.itemId,
required this.serverId,
required this.parentChain,
required this.mediaType,
this.leafCount = 1,
this.isDownloadOnly = false,
this.origin = DeletionOrigin.local,
}) : globalKey = buildGlobalKey(ServerId(serverId), itemId);
@override
String toString() => 'DeletionEvent(deleted: $globalKey, type: $mediaType, parents: $parentChain)';
}
/// Notifier for media deletion events across the app.
///
/// Singleton pattern following [WatchStateNotifier]. Screens subscribe
/// to receive events when items are deleted from the server.
class DeletionNotifier extends BaseNotifier<DeletionEvent> {
static final DeletionNotifier _instance = DeletionNotifier._internal();
factory DeletionNotifier() => _instance;
DeletionNotifier._internal();
/// Emit a deletion event with logging
@override
void notify(DeletionEvent event) {
appLogger.d('DeletionNotifier: $event');
super.notify(event);
}
void notifyDeletedItem({required MediaItem item, bool isDownloadOnly = false}) {
final serverId = serverIdForEvent(item, notifier: 'DeletionNotifier', event: 'deletion');
if (serverId == null) return;
notify(
DeletionEvent(
itemId: item.id,
serverId: serverId,
parentChain: item.parentChain,
mediaType: item.kind.id,
leafCount: item.leafCount ?? 1,
isDownloadOnly: isDownloadOnly,
),
);
}
}