feat: migrate backup albums to sqlite (#20049)

* do not migrate again on app start

* migrate backup albums over to sqlite

---------

Co-authored-by: shenlong-tanwen <139912620+shalong-tanwen@users.noreply.github.com>
This commit is contained in:
shenlong 2025-07-21 23:26:49 +05:30 committed by GitHub
parent dee6d072fb
commit 5fc4393e7a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 87 additions and 14 deletions

View File

@ -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 {

View File

@ -56,6 +56,10 @@ class _ChangeExperiencePageState extends ConsumerState<ChangeExperiencePage> {
ref.read(isarProvider),
ref.read(driftProvider),
);
await migrateBackupAlbumsToSqlite(
ref.read(isarProvider),
ref.read(driftProvider),
);
}
} else {
await ref.read(backgroundSyncProvider).cancel();

View File

@ -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<void> migrateDatabaseIfNeeded(Isar db) async {
final int version = Store.get(StoreKey.version, targetVersion);
@ -56,18 +60,6 @@ Future<void> 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<void> migrateDeviceAssetToSqlite(Isar db, Drift drift) async {
}
}
Future<void> 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<int>? hash;