refactor: remove superseded code paths
This commit is contained in:
@@ -22,11 +22,6 @@ extension DownloadDatabaseOperations on AppDatabase {
|
||||
await (delete(downloadOwners)..where((t) => t.profileId.equals(profileId) & t.globalKey.equals(globalKey))).go();
|
||||
}
|
||||
|
||||
Future<void> removeDownloadOwnersForProfile(String profileId) async {
|
||||
if (profileId.isEmpty) return;
|
||||
await (delete(downloadOwners)..where((t) => t.profileId.equals(profileId))).go();
|
||||
}
|
||||
|
||||
Future<void> clearAllDownloadOwners() async {
|
||||
await delete(downloadOwners).go();
|
||||
}
|
||||
@@ -59,12 +54,7 @@ extension DownloadDatabaseOperations on AppDatabase {
|
||||
final connectionRows = await select(connections).get();
|
||||
final connectionIds = connectionRows.map((row) => row.id).toSet();
|
||||
return candidates
|
||||
.where((row) {
|
||||
if (localProfileIds.contains(row.profileId)) return true;
|
||||
final plexHome = parsePlexHomeProfileId(row.profileId);
|
||||
if (plexHome != null) return connectionIds.contains(plexHome.accountConnectionId);
|
||||
return localProfileIds.isEmpty;
|
||||
})
|
||||
.where((row) => _isValidDownloadOwner(row, localProfileIds: localProfileIds, connectionIds: connectionIds))
|
||||
.toList(growable: false);
|
||||
}
|
||||
|
||||
@@ -83,16 +73,10 @@ extension DownloadDatabaseOperations on AppDatabase {
|
||||
final owners = await select(downloadOwners).get();
|
||||
final localProfileIds = (await select(profiles).get()).map((row) => row.id).toSet();
|
||||
final connectionIds = (await select(connections).get()).map((row) => row.id).toSet();
|
||||
bool valid(DownloadOwnerItem owner) {
|
||||
if (localProfileIds.contains(owner.profileId)) return true;
|
||||
final plexHome = parsePlexHomeProfileId(owner.profileId);
|
||||
if (plexHome != null) return connectionIds.contains(plexHome.accountConnectionId);
|
||||
return localProfileIds.isEmpty;
|
||||
}
|
||||
|
||||
final ownedKeys = <String>{
|
||||
for (final owner in owners)
|
||||
if (valid(owner)) owner.globalKey,
|
||||
if (_isValidDownloadOwner(owner, localProfileIds: localProfileIds, connectionIds: connectionIds))
|
||||
owner.globalKey,
|
||||
};
|
||||
for (final row in rows) {
|
||||
if (!ownedKeys.contains(row.globalKey)) {
|
||||
@@ -333,3 +317,14 @@ extension DownloadDatabaseOperations on AppDatabase {
|
||||
return item?.bgTaskId;
|
||||
}
|
||||
}
|
||||
|
||||
bool _isValidDownloadOwner(
|
||||
DownloadOwnerItem owner, {
|
||||
required Set<String> localProfileIds,
|
||||
required Set<String> connectionIds,
|
||||
}) {
|
||||
if (localProfileIds.contains(owner.profileId)) return true;
|
||||
final plexHome = parsePlexHomeProfileId(owner.profileId);
|
||||
if (plexHome != null) return connectionIds.contains(plexHome.accountConnectionId);
|
||||
return localProfileIds.isEmpty;
|
||||
}
|
||||
|
||||
@@ -198,31 +198,6 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
|
||||
await _releaseDownloadsForProfileWhere(profileId, (_) => true);
|
||||
}
|
||||
|
||||
Future<void> deleteAllDownloads() async {
|
||||
final downloads = await _downloadManager.getAllDownloads();
|
||||
for (final row in downloads) {
|
||||
await _downloadManager.deleteDownload(row.globalKey);
|
||||
}
|
||||
await _database.clearAllDownloadOwners();
|
||||
|
||||
try {
|
||||
final artworkDirectory = await DownloadStorageService.instance.getArtworkDirectory();
|
||||
if (await artworkDirectory.exists()) {
|
||||
await artworkDirectory.delete(recursive: true);
|
||||
}
|
||||
} catch (e, stackTrace) {
|
||||
appLogger.w('Failed to delete shared download artwork directory', error: e, stackTrace: stackTrace);
|
||||
}
|
||||
|
||||
_downloads.clear();
|
||||
_metadata.clear();
|
||||
_artworkPaths.clear();
|
||||
_queueing.clear();
|
||||
_ownedDownloadKeys.clear();
|
||||
_deletionProgress.clear();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Preserve physical downloads across a full logout while detaching them
|
||||
/// from profiles that are about to be deleted. The next selected profile
|
||||
/// adopts the ownerless rows through [_loadDownloadOwners].
|
||||
@@ -1695,16 +1670,6 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
|
||||
return buildProfileScopedGlobalKey(owner, ServerId(serverId), ratingKey);
|
||||
}
|
||||
|
||||
String syncRuleKeyForGlobalKey(String globalKey) {
|
||||
final scoped = parseProfileScopedGlobalKey(globalKey);
|
||||
if (scoped != null) {
|
||||
return syncRuleKeyFor(scoped.serverId, scoped.ratingKey, profileId: scoped.profileId);
|
||||
}
|
||||
final parsed = parseGlobalKey(globalKey);
|
||||
if (parsed == null) return globalKey;
|
||||
return syncRuleKeyFor(parsed.serverId, parsed.ratingKey);
|
||||
}
|
||||
|
||||
String syncRuleKeyForClient(MediaServerClient client, String ratingKey, {ServerId? serverId}) {
|
||||
return syncRuleKeyFor(serverId ?? client.serverId, ratingKey);
|
||||
}
|
||||
|
||||
@@ -178,14 +178,6 @@ class _SeasonEpisodePager {
|
||||
}
|
||||
}
|
||||
|
||||
void updateEpisode(String seasonId, int index, MediaItem updated) {
|
||||
final state = _states[seasonId];
|
||||
if (state == null || index < 0 || index >= state.items.length) return;
|
||||
final next = List<MediaItem>.of(state.items);
|
||||
next[index] = updated;
|
||||
_states[seasonId] = state.replaceItems(next);
|
||||
}
|
||||
|
||||
void patchEpisode(String episodeId, MediaItem Function(MediaItem existing) patch) {
|
||||
for (final entry in _states.entries.toList()) {
|
||||
var changed = false;
|
||||
|
||||
@@ -110,29 +110,6 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a host session — starts WebSocket server, returns local addresses and port.
|
||||
Future<({List<String> addresses, int port})> createSession(
|
||||
String deviceName,
|
||||
String platform,
|
||||
List<int> homeSecret,
|
||||
String clientIdentifier,
|
||||
List<String> homeUserUUIDs,
|
||||
) async {
|
||||
final auth = RemoteAuthService.instance;
|
||||
return createSessionForContexts(deviceName, platform, [
|
||||
RemoteAuthContext(
|
||||
id: auth.computeAuthContextId(homeSecret),
|
||||
backend: 'legacy',
|
||||
connectionId: '',
|
||||
homeSecret: homeSecret,
|
||||
discoveryKey: const [],
|
||||
clientIdentifier: clientIdentifier,
|
||||
userUuid: homeUserUUIDs.isEmpty ? '' : homeUserUUIDs.first,
|
||||
allowedUserUuids: homeUserUUIDs,
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
/// Create a host session that accepts any of the provided remote identities.
|
||||
Future<({List<String> addresses, int port})> createSessionForContexts(
|
||||
String deviceName,
|
||||
@@ -425,37 +402,6 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Join a host session as a remote client.
|
||||
Future<void> joinSession(
|
||||
String deviceName,
|
||||
String platform,
|
||||
String hostAddress,
|
||||
List<int> homeSecret,
|
||||
String hostClientId,
|
||||
String userUUID,
|
||||
String clientIdentifier,
|
||||
) async {
|
||||
final auth = RemoteAuthService.instance;
|
||||
final context = RemoteAuthContext(
|
||||
id: auth.computeAuthContextId(homeSecret),
|
||||
backend: 'legacy',
|
||||
connectionId: '',
|
||||
homeSecret: homeSecret,
|
||||
discoveryKey: const [],
|
||||
clientIdentifier: clientIdentifier,
|
||||
userUuid: userUUID,
|
||||
allowedUserUuids: [userUUID],
|
||||
);
|
||||
return joinSessionWithContexts(
|
||||
deviceName,
|
||||
platform,
|
||||
hostAddress,
|
||||
[context],
|
||||
authContextId: context.id,
|
||||
expectedHostClientId: hostClientId,
|
||||
);
|
||||
}
|
||||
|
||||
/// Join a host session with any local auth context that the host also supports.
|
||||
Future<void> joinSessionWithContexts(
|
||||
String deviceName,
|
||||
@@ -720,37 +666,6 @@ class CompanionRemotePeerService with KeepaliveMixin {
|
||||
);
|
||||
}
|
||||
|
||||
/// Race WebSocket connections to multiple host addresses in parallel.
|
||||
Future<String> joinSessionRacing(
|
||||
String deviceName,
|
||||
String platform,
|
||||
List<String> hostAddresses,
|
||||
List<int> homeSecret,
|
||||
String hostClientId,
|
||||
String userUUID,
|
||||
String clientIdentifier,
|
||||
) async {
|
||||
final auth = RemoteAuthService.instance;
|
||||
final context = RemoteAuthContext(
|
||||
id: auth.computeAuthContextId(homeSecret),
|
||||
backend: 'legacy',
|
||||
connectionId: '',
|
||||
homeSecret: homeSecret,
|
||||
discoveryKey: const [],
|
||||
clientIdentifier: clientIdentifier,
|
||||
userUuid: userUUID,
|
||||
allowedUserUuids: [userUUID],
|
||||
);
|
||||
return joinSessionRacingWithContexts(
|
||||
deviceName,
|
||||
platform,
|
||||
hostAddresses,
|
||||
[context],
|
||||
authContextId: context.id,
|
||||
expectedHostClientId: hostClientId,
|
||||
);
|
||||
}
|
||||
|
||||
/// Race WebSocket connections and authenticate with the selected shared identity.
|
||||
Future<String> joinSessionRacingWithContexts(
|
||||
String deviceName,
|
||||
|
||||
@@ -59,34 +59,6 @@ class LanDiscoveryService {
|
||||
|
||||
// ── Host: Broadcasting ──
|
||||
|
||||
Future<void> startBroadcasting({
|
||||
required List<int> discoveryKey,
|
||||
required String deviceName,
|
||||
required String platform,
|
||||
required String clientId,
|
||||
required int wsPort,
|
||||
required List<String> ips,
|
||||
}) async {
|
||||
return startBroadcastingForContexts(
|
||||
contexts: [
|
||||
RemoteAuthContext(
|
||||
id: clientId,
|
||||
backend: 'legacy',
|
||||
connectionId: clientId,
|
||||
homeSecret: const [],
|
||||
discoveryKey: discoveryKey,
|
||||
clientIdentifier: clientId,
|
||||
userUuid: '',
|
||||
allowedUserUuids: const [],
|
||||
),
|
||||
],
|
||||
deviceName: deviceName,
|
||||
platform: platform,
|
||||
wsPort: wsPort,
|
||||
ips: ips,
|
||||
);
|
||||
}
|
||||
|
||||
Future<void> startBroadcastingForContexts({
|
||||
required List<RemoteAuthContext> contexts,
|
||||
required String deviceName,
|
||||
@@ -168,23 +140,6 @@ class LanDiscoveryService {
|
||||
|
||||
// ── Client: Listening ──
|
||||
|
||||
/// Start listening for host beacons.
|
||||
/// Returns a stream of currently-visible hosts, updated on each beacon or stale cleanup.
|
||||
Stream<List<DiscoveredHost>> startListening({required List<int> discoveryKey}) {
|
||||
return startListeningForContexts([
|
||||
RemoteAuthContext(
|
||||
id: '',
|
||||
backend: 'legacy',
|
||||
connectionId: '',
|
||||
homeSecret: const [],
|
||||
discoveryKey: discoveryKey,
|
||||
clientIdentifier: '',
|
||||
userUuid: '',
|
||||
allowedUserUuids: const [],
|
||||
),
|
||||
]);
|
||||
}
|
||||
|
||||
Stream<List<DiscoveredHost>> startListeningForContexts(List<RemoteAuthContext> contexts) {
|
||||
_stopListeningInternal();
|
||||
_discoveredHosts.clear();
|
||||
|
||||
@@ -528,22 +528,6 @@ class MultiServerManager {
|
||||
return bound;
|
||||
}
|
||||
|
||||
/// Tear down all servers belonging to the given Plex account. Called when
|
||||
/// the user removes the account from the Connections screen. Idempotent —
|
||||
/// servers already gone are silently skipped.
|
||||
void removePlexAccount(PlexAccountConnection connection) {
|
||||
for (final server in connection.servers) {
|
||||
final id = server.clientIdentifier;
|
||||
final client = _clients.remove(id);
|
||||
if (client != null) _closeClient(client);
|
||||
_plexServers.remove(id);
|
||||
_serverStatus.remove(id);
|
||||
_authErrorServers.remove(id);
|
||||
_clientIdByServer.remove(id);
|
||||
}
|
||||
_statusController.add(Map.from(_serverStatus));
|
||||
}
|
||||
|
||||
/// Add a Jellyfin server backed by an authenticated [JellyfinConnection].
|
||||
/// Returns true on success.
|
||||
///
|
||||
|
||||
@@ -769,42 +769,6 @@ class PlexServer {
|
||||
return _ConnectionCandidate(httpsConnection, httpsUrl, resultingIsPlexDirect, true);
|
||||
}
|
||||
|
||||
Future<PlexConnection?> upgradeConnectionToHttps(PlexConnection current) async {
|
||||
if (current.uri.startsWith('https://')) {
|
||||
return current;
|
||||
}
|
||||
|
||||
final baseConnection = _findMatchingBaseConnection(current);
|
||||
if (baseConnection == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
final candidate = _ConnectionCandidate(
|
||||
baseConnection,
|
||||
current.uri,
|
||||
current.uri.contains('.plex.direct'),
|
||||
current.uri.startsWith('https://'),
|
||||
);
|
||||
final upgradedCandidate = await _upgradeCandidateToHttpsIfPossible(candidate);
|
||||
if (upgradedCandidate == null) {
|
||||
return null;
|
||||
}
|
||||
return _updateConnectionUrl(upgradedCandidate.connection, upgradedCandidate.url);
|
||||
}
|
||||
|
||||
PlexConnection? _findMatchingBaseConnection(PlexConnection connection) {
|
||||
for (final base in connections) {
|
||||
final sameAddress = base.address == connection.address;
|
||||
final samePort = base.port == connection.port;
|
||||
final sameLocal = base.local == connection.local;
|
||||
final sameRelay = base.relay == connection.relay;
|
||||
if (sameAddress && samePort && sameLocal && sameRelay) {
|
||||
return base;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/// Select the best candidate considering priority, latency, and URL type preference
|
||||
_ConnectionCandidate? _selectBestCandidateWithLatency(Map<_ConnectionCandidate, ConnectionTestResult> results) {
|
||||
// Group candidates by connection type (local/remote/relay)
|
||||
|
||||
@@ -2083,11 +2083,6 @@ class PlexClient
|
||||
Future<_LibraryContentResult> _getPlaylist(String playlistId, {int? start, int? size, AbortController? abort}) =>
|
||||
_fetchPaginatedList('/playlists/$playlistId/items', start: start, size: size, abort: abort);
|
||||
|
||||
/// Fetch every page of a playlist's items. For callers that need the full list
|
||||
/// (downloads, sync rules, context-menu shuffle).
|
||||
Future<List<PlexMetadataDto>> _fetchAllPlaylistItemsDto(String playlistId) =>
|
||||
_fetchAllPages((start, size, abort) => _getPlaylist(playlistId, start: start, size: size, abort: abort));
|
||||
|
||||
/// Get all playlists.
|
||||
/// Filters by playlistType=video by default.
|
||||
/// Set smart to true/false to filter smart playlists, or null for all.
|
||||
@@ -2509,22 +2504,6 @@ class PlexClient
|
||||
librarySectionTitle: librarySectionTitle,
|
||||
);
|
||||
|
||||
/// Fetch every item in a collection (downloads, sync rules, context-menu shuffle).
|
||||
Future<List<PlexMetadataDto>> _fetchAllCollectionItemsDto(
|
||||
String collectionId, {
|
||||
String? librarySectionID,
|
||||
String? librarySectionTitle,
|
||||
}) => _fetchAllPages(
|
||||
(start, size, abort) => _getCollectionItems(
|
||||
collectionId,
|
||||
start: start,
|
||||
size: size,
|
||||
abort: abort,
|
||||
librarySectionID: librarySectionID,
|
||||
librarySectionTitle: librarySectionTitle,
|
||||
),
|
||||
);
|
||||
|
||||
/// Get media featuring a specific person (actor/director), paginated.
|
||||
Future<_LibraryContentResult> _getPersonMedia(String personId, {int? start, int? size, AbortController? abort}) =>
|
||||
_fetchPaginatedList('/library/people/$personId/media', start: start, size: size, abort: abort);
|
||||
@@ -2941,12 +2920,6 @@ class PlexClient
|
||||
/// Get library-specific playlists
|
||||
/// Filters playlists by checking if they contain items from the specified library
|
||||
/// This is a client-side filter since the API doesn't support sectionId for playlists
|
||||
Future<List<PlexPlaylistDto>> _getLibraryPlaylists({String playlistType = 'video'}) {
|
||||
// For now, return all video playlists
|
||||
// Future enhancement: filter by checking playlist items' library
|
||||
return _getPlaylists(playlistType: playlistType);
|
||||
}
|
||||
|
||||
/// Scan/refresh a library section to detect new files
|
||||
Future<void> scanLibrary(String sectionId) async {
|
||||
await _getWithFailover('/library/sections/$sectionId/refresh');
|
||||
@@ -4213,26 +4186,6 @@ class PlexClient
|
||||
);
|
||||
}
|
||||
|
||||
/// Plex-specific: full collection contents across pages.
|
||||
Future<List<MediaItem>> fetchAllCollectionItemsAsMediaItems(
|
||||
String collectionId, {
|
||||
String? libraryId,
|
||||
String? libraryTitle,
|
||||
}) async {
|
||||
final raw = await _fetchAllCollectionItemsDto(
|
||||
collectionId,
|
||||
librarySectionID: libraryId,
|
||||
librarySectionTitle: libraryTitle,
|
||||
);
|
||||
return raw.map((m) => PlexMappers.mediaItem(m)).toList();
|
||||
}
|
||||
|
||||
/// Plex-specific: full playlist contents across pages.
|
||||
Future<List<MediaItem>> fetchAllPlaylistItemsAsMediaItems(String playlistId) async {
|
||||
final raw = await _fetchAllPlaylistItemsDto(playlistId);
|
||||
return raw.map((m) => PlexMappers.mediaItem(m)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
Future<LibraryPage<MediaItem>> fetchPersonMediaPage(
|
||||
String personId, {
|
||||
@@ -4330,12 +4283,6 @@ class PlexClient
|
||||
return raw.map((m) => PlexMappers.mediaItem(m)).toList();
|
||||
}
|
||||
|
||||
/// Plex-specific: library-scoped playlists.
|
||||
Future<List<MediaPlaylist>> fetchLibraryPlaylists({String playlistType = 'video'}) async {
|
||||
final raw = await _getLibraryPlaylists(playlistType: playlistType);
|
||||
return raw.map((p) => PlexMappers.mediaPlaylist(p)).toList();
|
||||
}
|
||||
|
||||
/// Plex-specific: paginated library content with raw Plex filter map,
|
||||
/// returning neutral [MediaItem]s. The aggregation bridge uses this when it
|
||||
/// has Plex-specific filter strings (`unwatched=1`, `genre=...`) to forward.
|
||||
|
||||
@@ -28,7 +28,6 @@ mixin _PlexLiveTvClientMethods on MediaServerCacheMixin {
|
||||
|
||||
Map<String, dynamic>? _getMediaContainer(MediaServerResponse response);
|
||||
PlexMetadataDto _createTaggedMetadata(Map<String, dynamic> json);
|
||||
List<PlexMetadataDto> _extractMetadataList(MediaServerResponse response);
|
||||
|
||||
Future<List<T>> _wrapListApiCall<T>(
|
||||
Future<MediaServerResponse> Function() apiCall,
|
||||
@@ -1061,15 +1060,6 @@ mixin _PlexLiveTvClientMethods on MediaServerCacheMixin {
|
||||
return '${config.baseUrl}$streamPath'.withPlexToken(config.token);
|
||||
}
|
||||
|
||||
/// Get active live TV sessions
|
||||
Future<List<PlexMetadataDto>> _getLiveTvSessions() {
|
||||
return _wrapListApiCall<PlexMetadataDto>(
|
||||
() => _http.get('/livetv/sessions'),
|
||||
_extractMetadataList,
|
||||
'Failed to get live TV sessions',
|
||||
);
|
||||
}
|
||||
|
||||
Future<List<LiveTvSession>> getLiveTvSessionsDetailed() async {
|
||||
final response = await _getWithFailover('/livetv/sessions');
|
||||
return _extractContainerList(response, const [
|
||||
@@ -1153,12 +1143,6 @@ mixin _PlexLiveTvClientMethods on MediaServerCacheMixin {
|
||||
}
|
||||
}
|
||||
|
||||
/// Plex-specific: live TV sessions (active recordings/playback).
|
||||
Future<List<MediaItem>> fetchLiveTvSessions() async {
|
||||
final raw = await _getLiveTvSessions();
|
||||
return raw.map((m) => PlexMappers.mediaItem(m)).toList();
|
||||
}
|
||||
|
||||
@override
|
||||
LiveTvSupport get liveTv => _PlexLiveTvSupport(this as PlexClient);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user