chore(plex): hydrate Plex Home users at bootstrap through PlexHomeService
ConnectionBootstrap kept a private cache read/fetch/write path for Plex Home users beside PlexHomeService, which already owns refresh coalescing, generation checks, durable writes, and publication. The bootstrap now keeps only the one-time legacy cache migration, then reloads the service from storage or asks it to refresh, and reads the hydrated users from the service snapshot. A failed hydration clears the service's empty cache slot before the migrated account is removed.
This commit is contained in:
@@ -2,8 +2,8 @@ import 'dart:convert';
|
||||
|
||||
import '../models/plex/plex_home.dart';
|
||||
import '../models/plex/plex_home_user.dart';
|
||||
import '../profiles/plex_home_service.dart';
|
||||
import '../profiles/profile.dart';
|
||||
import '../profiles/plex_home_cache_codec.dart';
|
||||
import '../profiles/profile_registry.dart';
|
||||
import '../services/plex_auth_service.dart';
|
||||
import '../services/server_registry.dart';
|
||||
@@ -20,24 +20,23 @@ import 'plex_account_setup.dart';
|
||||
///
|
||||
/// Plex Home users are NOT persisted here — the bootstrap copies the
|
||||
/// legacy `homeUsersCache` into the per-connection
|
||||
/// `plex_home_users_{connectionId}` SharedPreferences slot so
|
||||
/// [PlexHomeService] picks it up on cold start.
|
||||
/// `plex_home_users_{connectionId}` SharedPreferences slot and asks
|
||||
/// [PlexHomeService] to reload (or fetch) it.
|
||||
class ConnectionBootstrap {
|
||||
ConnectionBootstrap({
|
||||
required this.storage,
|
||||
required this.connectionRegistry,
|
||||
required this.serverRegistry,
|
||||
required this.profileRegistry,
|
||||
Future<List<PlexHomeUser>> Function(String accountToken)? plexHomeUserFetcher,
|
||||
required this.plexHome,
|
||||
Future<Map<String, dynamic>> Function(String accountToken)? plexUserInfoFetcher,
|
||||
}) : _plexHomeUserFetcher = plexHomeUserFetcher ?? fetchPlexHomeUsers,
|
||||
_plexUserInfoFetcher = plexUserInfoFetcher ?? _fetchPlexUserInfo;
|
||||
}) : _plexUserInfoFetcher = plexUserInfoFetcher ?? _fetchPlexUserInfo;
|
||||
|
||||
final StorageService storage;
|
||||
final ConnectionRegistry connectionRegistry;
|
||||
final ServerRegistry serverRegistry;
|
||||
final ProfileRegistry profileRegistry;
|
||||
final Future<List<PlexHomeUser>> Function(String accountToken) _plexHomeUserFetcher;
|
||||
final PlexHomeService plexHome;
|
||||
final Future<Map<String, dynamic>> Function(String accountToken) _plexUserInfoFetcher;
|
||||
|
||||
static const String _keyProfileMigrationV1Done = 'profile_migration_v1_done';
|
||||
@@ -63,6 +62,9 @@ class ConnectionBootstrap {
|
||||
final prepared = await _preparePlexVirtualProfile(account);
|
||||
if (!prepared) {
|
||||
if (migratedAccount != null && hadLegacyPlexToken) {
|
||||
// A failed/empty fetch may have left an empty cache slot on disk
|
||||
// for the id we are about to drop; don't leave it orphaned.
|
||||
await storage.clearPlexHomeUsersCache(migratedAccount.id);
|
||||
await connectionRegistry.remove(migratedAccount.id);
|
||||
}
|
||||
appLogger.w('Migration: could not hydrate Plex Home profiles for ${account.id}; will retry later');
|
||||
@@ -147,9 +149,12 @@ class ConnectionBootstrap {
|
||||
/// profile. Plex users are never persisted as local Plezy profiles.
|
||||
Future<bool> _preparePlexVirtualProfile(PlexAccountConnection account) async {
|
||||
final copied = await _migrateLegacyPlexHomeUsersCache(account.id);
|
||||
var users = copied ? _readPlexHomeUsersCache(account.id) : null;
|
||||
users ??= await _fetchAndCachePlexHomeUsers(account);
|
||||
final hydratedUsers = users;
|
||||
if (copied) {
|
||||
await plexHome.reloadFromStorage();
|
||||
} else {
|
||||
await plexHome.refresh(account);
|
||||
}
|
||||
final hydratedUsers = plexHome.current[account.id] ?? const [];
|
||||
if (hydratedUsers.isEmpty) return false;
|
||||
|
||||
final legacyActiveUuid = storage.getCurrentUserUUID();
|
||||
@@ -196,31 +201,6 @@ class ConnectionBootstrap {
|
||||
}
|
||||
}
|
||||
|
||||
List<PlexHomeUser>? _readPlexHomeUsersCache(String connectionId) {
|
||||
final raw = storage.getPlexHomeUsersCacheJson(connectionId);
|
||||
if (raw == null || raw.isEmpty) return null;
|
||||
try {
|
||||
return decodePlexHomeUsersCache(raw);
|
||||
} catch (e, st) {
|
||||
appLogger.w('Migration: failed to read Plex Home cache for $connectionId', error: e, stackTrace: st);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Future<List<PlexHomeUser>> _fetchAndCachePlexHomeUsers(PlexAccountConnection account) async {
|
||||
try {
|
||||
final users = await _plexHomeUserFetcher(account.accountToken);
|
||||
if (users.isNotEmpty) {
|
||||
await storage.savePlexHomeUsersCache(account.id, encodePlexHomeUsersCache(users));
|
||||
appLogger.i('Migration: fetched ${users.length} Plex Home users for ${account.id}');
|
||||
}
|
||||
return users;
|
||||
} catch (e, st) {
|
||||
appLogger.w('Migration: Plex Home fetch failed for ${account.id}', error: e, stackTrace: st);
|
||||
return const [];
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _migrateLegacyPlexHomeUsersCacheForExistingAccount(PlexAccountConnection? account) async {
|
||||
if (storage.prefs.getString('home_users_cache') == null) return;
|
||||
var target = account;
|
||||
|
||||
@@ -2032,6 +2032,7 @@ class _SetupScreenState extends State<SetupScreen> with MountedSetStateMixin {
|
||||
connectionRegistry: connRegistry,
|
||||
serverRegistry: registry,
|
||||
profileRegistry: profileRegistry,
|
||||
plexHome: context.read<PlexHomeService>(),
|
||||
);
|
||||
await bootstrap.run();
|
||||
final pruned = await ProfileConnectionCleanup(
|
||||
|
||||
@@ -7,6 +7,8 @@ import 'package:plezy/connection/connection_bootstrap.dart';
|
||||
import 'package:plezy/connection/connection_registry.dart';
|
||||
import 'package:plezy/database/app_database.dart';
|
||||
import 'package:plezy/models/plex/plex_home_user.dart';
|
||||
import 'package:plezy/profiles/plex_home_service.dart';
|
||||
import 'package:plezy/profiles/profile_connection_registry.dart';
|
||||
import 'package:plezy/profiles/profile_registry.dart';
|
||||
import 'package:plezy/services/server_registry.dart';
|
||||
import 'package:plezy/services/storage_service.dart';
|
||||
@@ -32,12 +34,19 @@ void main() {
|
||||
serverRegistry = ServerRegistry(storage);
|
||||
fetchedHomeUsers = const [];
|
||||
fetchedUserInfo = null;
|
||||
final plexHome = PlexHomeService(
|
||||
connections: registry,
|
||||
profileConnections: ProfileConnectionRegistry(db),
|
||||
storage: storage,
|
||||
plexHomeUserFetcher: (_) async => fetchedHomeUsers,
|
||||
);
|
||||
addTearDown(plexHome.dispose);
|
||||
bootstrap = ConnectionBootstrap(
|
||||
storage: storage,
|
||||
connectionRegistry: registry,
|
||||
serverRegistry: serverRegistry,
|
||||
profileRegistry: profileRegistry,
|
||||
plexHomeUserFetcher: (_) async => fetchedHomeUsers,
|
||||
plexHome: plexHome,
|
||||
plexUserInfoFetcher: (_) async => fetchedUserInfo ?? (throw StateError('user info unavailable')),
|
||||
);
|
||||
});
|
||||
@@ -226,6 +235,7 @@ void main() {
|
||||
expect(storage.getActiveProfileId(), isNull);
|
||||
expect(await registry.list(), isEmpty);
|
||||
expect(await profileRegistry.list(), isEmpty);
|
||||
expect(storage.getPlexHomeUsersCacheJson('plex.client-owner'), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user