fix(downloads): retain offline media after logout
This commit is contained in:
@@ -89,6 +89,7 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
|
||||
final Map<String, SyncRuleItem> _syncRules = {};
|
||||
|
||||
String? _activeProfileId;
|
||||
Future<void>? _profileScopedReloadFuture;
|
||||
|
||||
OfflineModeSource? _offlineSource;
|
||||
|
||||
@@ -141,7 +142,9 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
|
||||
void setActiveProfileId(String? profileId) {
|
||||
if (_activeProfileId == profileId) return;
|
||||
_activeProfileId = profileId;
|
||||
unawaited(_reloadProfileScopedStateForActiveProfile());
|
||||
final reload = _reloadProfileScopedStateForActiveProfile();
|
||||
_profileScopedReloadFuture = reload;
|
||||
unawaited(reload);
|
||||
}
|
||||
|
||||
Future<void> _reloadProfileScopedStateForActiveProfile() async {
|
||||
@@ -219,6 +222,22 @@ class DownloadProvider extends ChangeNotifier with DisposableChangeNotifierMixin
|
||||
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].
|
||||
Future<void> detachDownloadsForLogout() async {
|
||||
// Invalidate any in-flight profile reload before waiting for it: an old
|
||||
// reload may still finish its DB adoption, but cannot repopulate the
|
||||
// active-profile view after this point.
|
||||
_activeProfileId = null;
|
||||
await _initFuture;
|
||||
await _profileScopedReloadFuture;
|
||||
await _database.clearAllDownloadOwners();
|
||||
_ownedDownloadKeys.clear();
|
||||
_syncRules.clear();
|
||||
safeNotifyListeners();
|
||||
}
|
||||
|
||||
/// Remove ownership rows for [profileId] that belong to the removed
|
||||
/// connection's public server ids. Physical files stay when any other valid
|
||||
/// owner remains.
|
||||
|
||||
@@ -230,7 +230,10 @@ Future<void> logoutAllProfiles(BuildContext context) async {
|
||||
|
||||
await companionRemote.resetForLogout();
|
||||
await userProfileProvider.logout();
|
||||
await scope.downloads.deleteAllDownloads();
|
||||
// Downloads are device-local data, not credentials. Keep their physical
|
||||
// files and pinned metadata, but detach profile ownership before deleting
|
||||
// the profiles so the next selected profile can adopt them.
|
||||
await scope.downloads.detachDownloadsForLogout();
|
||||
scope.multiServer.clearAllConnections();
|
||||
// Drop the profile/connection rows so the next sign-in starts clean and
|
||||
// doesn't bind to stale tokens or orphaned profile rows.
|
||||
@@ -246,9 +249,9 @@ Future<void> logoutAllProfiles(BuildContext context) async {
|
||||
// through the next sign-in's clients).
|
||||
await scope.database.clearAllWatchActions();
|
||||
await scope.database.clearAllSyncRules();
|
||||
// Downloads were removed above, so no pinned cache rows need to survive this
|
||||
// app-global logout into the next sign-in.
|
||||
await ApiCache.instance.clearAll();
|
||||
// Preserve pinned rows backing offline downloads; all session/API data is
|
||||
// volatile and must not cross into the next sign-in.
|
||||
await ApiCache.instance.clearVolatile();
|
||||
await scope.hiddenLibraries?.refresh();
|
||||
playbackState.clearShuffle();
|
||||
|
||||
|
||||
@@ -156,6 +156,34 @@ void main() {
|
||||
|
||||
p.dispose();
|
||||
});
|
||||
|
||||
test('logout detaches ownership without deleting physical downloads', () async {
|
||||
const globalKey = 'srv:preserved';
|
||||
await db.insertDownload(
|
||||
serverId: ServerId('srv'),
|
||||
ratingKey: 'preserved',
|
||||
globalKey: globalKey,
|
||||
type: 'movie',
|
||||
status: DownloadStatus.completed.index,
|
||||
);
|
||||
await db.addDownloadOwner(profileId: 'test-profile', globalKey: globalKey);
|
||||
|
||||
final p = DownloadProvider.forTesting(downloadManager: downloadManager, database: db);
|
||||
await p.ensureInitialized();
|
||||
p.debugSeedState(
|
||||
downloads: {globalKey: const DownloadProgress(globalKey: globalKey, status: DownloadStatus.completed)},
|
||||
ownedDownloadKeys: {globalKey},
|
||||
);
|
||||
expect(p.downloads, contains(globalKey));
|
||||
|
||||
await p.detachDownloadsForLogout();
|
||||
|
||||
expect(await db.getDownloadedMedia(globalKey), isNotNull);
|
||||
expect(await db.hasDownloadOwner(globalKey), isFalse);
|
||||
expect(p.downloads, isEmpty);
|
||||
|
||||
p.dispose();
|
||||
});
|
||||
});
|
||||
|
||||
group('DownloadProvider — local file selection', () {
|
||||
|
||||
Reference in New Issue
Block a user