diff --git a/mobile/lib/entities/backup_album.entity.dart b/mobile/lib/entities/backup_album.entity.dart index 4d4d7b3aa3..1e96c0452e 100644 --- a/mobile/lib/entities/backup_album.entity.dart +++ b/mobile/lib/entities/backup_album.entity.dart @@ -13,6 +13,18 @@ class BackupAlbum { BackupAlbum(this.id, this.lastBackup, this.selection); Id get isarId => fastHash(id); + + BackupAlbum copyWith({ + String? id, + DateTime? lastBackup, + BackupSelection? selection, + }) { + return BackupAlbum( + id ?? this.id, + lastBackup ?? this.lastBackup, + selection ?? this.selection, + ); + } } enum BackupSelection { diff --git a/mobile/lib/pages/common/change_experience.page.dart b/mobile/lib/pages/common/change_experience.page.dart index 5d298edb42..a8569b25a0 100644 --- a/mobile/lib/pages/common/change_experience.page.dart +++ b/mobile/lib/pages/common/change_experience.page.dart @@ -56,6 +56,10 @@ class _ChangeExperiencePageState extends ConsumerState { ref.read(isarProvider), ref.read(driftProvider), ); + await migrateBackupAlbumsToSqlite( + ref.read(isarProvider), + ref.read(driftProvider), + ); } } else { await ref.read(backgroundSyncProvider).cancel(); diff --git a/mobile/lib/utils/migration.dart b/mobile/lib/utils/migration.dart index d09f2b9978..a95c376ac2 100644 --- a/mobile/lib/utils/migration.dart +++ b/mobile/lib/utils/migration.dart @@ -2,19 +2,23 @@ import 'dart:async'; import 'dart:convert'; import 'dart:io'; +import 'package:collection/collection.dart'; import 'package:drift/drift.dart'; import 'package:flutter/foundation.dart'; import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/domain/models/album/local_album.model.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; -import 'package:immich_mobile/domain/utils/background_sync.dart'; import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/android_device_asset.entity.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart' + as isar_backup_album; import 'package:immich_mobile/entities/etag.entity.dart'; import 'package:immich_mobile/entities/ios_device_asset.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/device_asset.entity.dart'; import 'package:immich_mobile/infrastructure/entities/exif.entity.dart'; +import 'package:immich_mobile/infrastructure/entities/local_album.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/local_asset.entity.drift.dart'; import 'package:immich_mobile/infrastructure/entities/store.entity.dart'; import 'package:immich_mobile/infrastructure/entities/user.entity.dart'; @@ -27,7 +31,7 @@ import 'package:logging/logging.dart'; // ignore: import_rule_photo_manager import 'package:photo_manager/photo_manager.dart'; -const int targetVersion = 14; +const int targetVersion = 13; Future migrateDatabaseIfNeeded(Isar db) async { final int version = Store.get(StoreKey.version, targetVersion); @@ -56,18 +60,6 @@ Future migrateDatabaseIfNeeded(Isar db) async { await Store.put(StoreKey.photoManagerCustomFilter, true); } - if (version < 14) { - if (!Store.isBetaTimelineEnabled) { - // Try again when beta timeline is enabled and the app is restarted - return; - } - final backgroundSync = BackgroundSyncManager(); - await backgroundSync.syncLocal(); - final drift = Drift(); - await migrateDeviceAssetToSqlite(db, drift); - await drift.close(); - } - if (targetVersion >= 12) { await Store.put(StoreKey.version, targetVersion); return; @@ -204,6 +196,71 @@ Future migrateDeviceAssetToSqlite(Isar db, Drift drift) async { } } +Future migrateBackupAlbumsToSqlite( + Isar db, + Drift drift, +) async { + try { + final isarBackupAlbums = await db.backupAlbums.where().findAll(); + // Recents is a virtual album on Android, and we don't have it with the new sync + // If recents is selected previously, select all albums during migration except the excluded ones + if (Platform.isAndroid) { + final recentAlbum = + isarBackupAlbums.firstWhereOrNull((album) => album.id == 'isAll'); + if (recentAlbum != null) { + await drift.localAlbumEntity.update().write( + const LocalAlbumEntityCompanion( + backupSelection: Value(BackupSelection.selected), + ), + ); + final excluded = isarBackupAlbums + .where( + (album) => + album.selection == isar_backup_album.BackupSelection.exclude, + ) + .map((album) => album.id) + .toList(); + await drift.batch((batch) async { + for (final id in excluded) { + batch.update( + drift.localAlbumEntity, + const LocalAlbumEntityCompanion( + backupSelection: Value(BackupSelection.excluded), + ), + where: (t) => t.id.equals(id), + ); + } + }); + } + return; + } + + await drift.batch((batch) { + for (final album in isarBackupAlbums) { + batch.update( + drift.localAlbumEntity, + LocalAlbumEntityCompanion( + backupSelection: Value( + switch (album.selection) { + isar_backup_album.BackupSelection.none => BackupSelection.none, + isar_backup_album.BackupSelection.select => + BackupSelection.selected, + isar_backup_album.BackupSelection.exclude => + BackupSelection.excluded, + }, + ), + ), + where: (t) => t.id.equals(album.id), + ); + } + }); + } catch (error) { + debugPrint( + "[MIGRATION] Error while migrating backup albums to SQLite: $error", + ); + } +} + class _DeviceAsset { final String assetId; final List? hash;