diff --git a/mobile/lib/interfaces/backup.interface.dart b/mobile/lib/interfaces/backup_album.interface.dart similarity index 85% rename from mobile/lib/interfaces/backup.interface.dart rename to mobile/lib/interfaces/backup_album.interface.dart index c32199a58f..f98adb6821 100644 --- a/mobile/lib/interfaces/backup.interface.dart +++ b/mobile/lib/interfaces/backup_album.interface.dart @@ -1,7 +1,7 @@ import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/interfaces/database.interface.dart'; -abstract interface class IBackupRepository implements IDatabaseRepository { +abstract interface class IBackupAlbumRepository implements IDatabaseRepository { Future> getAll({BackupAlbumSort? sort}); Future> getIdsBySelection(BackupSelection backup); diff --git a/mobile/lib/providers/backup/backup.provider.dart b/mobile/lib/providers/backup/backup.provider.dart index 3b0f724411..a4f4fea45c 100644 --- a/mobile/lib/providers/backup/backup.provider.dart +++ b/mobile/lib/providers/backup/backup.provider.dart @@ -10,7 +10,7 @@ import 'package:immich_mobile/entities/album.entity.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; import 'package:immich_mobile/interfaces/album_media.interface.dart'; -import 'package:immich_mobile/interfaces/backup.interface.dart'; +import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart'; import 'package:immich_mobile/models/auth/auth_state.model.dart'; import 'package:immich_mobile/models/backup/available_album.model.dart'; @@ -23,21 +23,34 @@ import 'package:immich_mobile/models/server_info/server_disk_info.model.dart'; import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; import 'package:immich_mobile/providers/auth.provider.dart'; import 'package:immich_mobile/providers/backup/error_backup_list.provider.dart'; -import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/providers/gallery_permission.provider.dart'; import 'package:immich_mobile/repositories/album_media.repository.dart'; -import 'package:immich_mobile/repositories/backup.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/services/backup.service.dart'; +import 'package:immich_mobile/services/backup_album.service.dart'; import 'package:immich_mobile/services/server_info.service.dart'; import 'package:immich_mobile/utils/backup_progress.dart'; import 'package:immich_mobile/utils/diff.dart'; -import 'package:isar/isar.dart'; import 'package:logging/logging.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:photo_manager/photo_manager.dart' show PMProgressHandler; +final backupProvider = + StateNotifierProvider((ref) { + return BackupNotifier( + ref.watch(backupServiceProvider), + ref.watch(serverInfoServiceProvider), + ref.watch(authProvider), + ref.watch(backgroundServiceProvider), + ref.watch(galleryPermissionNotifier.notifier), + ref.watch(albumMediaRepositoryProvider), + ref.watch(fileMediaRepositoryProvider), + ref.watch(backupAlbumServiceProvider), + ref, + ); +}); + class BackupNotifier extends StateNotifier { BackupNotifier( this._backupService, @@ -45,10 +58,9 @@ class BackupNotifier extends StateNotifier { this._authState, this._backgroundService, this._galleryPermissionNotifier, - this._db, this._albumMediaRepository, this._fileMediaRepository, - this._backupRepository, + this._backupAlbumService, this.ref, ) : super( BackUpState( @@ -96,10 +108,9 @@ class BackupNotifier extends StateNotifier { final AuthState _authState; final BackgroundService _backgroundService; final GalleryPermissionNotifier _galleryPermissionNotifier; - final Isar _db; final IAlbumMediaRepository _albumMediaRepository; final IFileMediaRepository _fileMediaRepository; - final IBackupRepository _backupRepository; + final BackupAlbumService _backupAlbumService; final Ref ref; /// @@ -260,9 +271,9 @@ class BackupNotifier extends StateNotifier { state = state.copyWith(availableAlbums: availableAlbums); final List excludedBackupAlbums = - await _backupRepository.getAllBySelection(BackupSelection.exclude); + await _backupAlbumService.getAllBySelection(BackupSelection.exclude); final List selectedBackupAlbums = - await _backupRepository.getAllBySelection(BackupSelection.select); + await _backupAlbumService.getAllBySelection(BackupSelection.select); final Set selectedAlbums = {}; for (final BackupAlbum ba in selectedBackupAlbums) { @@ -439,7 +450,7 @@ class BackupNotifier extends StateNotifier { } /// Save user selection of selected albums and excluded albums to database - Future _updatePersistentAlbumsSelection() { + Future _updatePersistentAlbumsSelection() async { final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); final selected = state.selectedBackupAlbums.map( (e) => BackupAlbum(e.id, e.lastBackup ?? epoch, BackupSelection.select), @@ -447,29 +458,30 @@ class BackupNotifier extends StateNotifier { final excluded = state.excludedBackupAlbums.map( (e) => BackupAlbum(e.id, e.lastBackup ?? epoch, BackupSelection.exclude), ); - final backupAlbums = selected.followedBy(excluded).toList(); - backupAlbums.sortBy((e) => e.id); - return _db.writeTxn(() async { - final dbAlbums = await _db.backupAlbums.where().sortById().findAll(); - final List toDelete = []; - final List toUpsert = []; - // stores the most recent `lastBackup` per album but always keeps the `selection` the user just made - diffSortedListsSync( - dbAlbums, - backupAlbums, - compare: (BackupAlbum a, BackupAlbum b) => a.id.compareTo(b.id), - both: (BackupAlbum a, BackupAlbum b) { - b.lastBackup = - a.lastBackup.isAfter(b.lastBackup) ? a.lastBackup : b.lastBackup; - toUpsert.add(b); - return true; - }, - onlyFirst: (BackupAlbum a) => toDelete.add(a.isarId), - onlySecond: (BackupAlbum b) => toUpsert.add(b), - ); - await _db.backupAlbums.deleteAll(toDelete); - await _db.backupAlbums.putAll(toUpsert); - }); + final candidates = selected.followedBy(excluded).toList(); + candidates.sortBy((e) => e.id); + + final savedBackupAlbums = + await _backupAlbumService.getAll(sort: BackupAlbumSort.id); + final List toDelete = []; + final List toUpsert = []; + + diffSortedListsSync( + savedBackupAlbums, + candidates, + compare: (BackupAlbum a, BackupAlbum b) => a.id.compareTo(b.id), + both: (BackupAlbum a, BackupAlbum b) { + b.lastBackup = + a.lastBackup.isAfter(b.lastBackup) ? a.lastBackup : b.lastBackup; + toUpsert.add(b); + return true; + }, + onlyFirst: (BackupAlbum a) => toDelete.add(a.isarId), + onlySecond: (BackupAlbum b) => toUpsert.add(b), + ); + + await _backupAlbumService.deleteAll(toDelete); + await _backupAlbumService.updateAll(toUpsert); } /// Invoke backup process @@ -686,14 +698,10 @@ class BackupNotifier extends StateNotifier { } Future resumeBackup() async { - final List selectedBackupAlbums = await _db.backupAlbums - .filter() - .selectionEqualTo(BackupSelection.select) - .findAll(); - final List excludedBackupAlbums = await _db.backupAlbums - .filter() - .selectionEqualTo(BackupSelection.exclude) - .findAll(); + final List selectedBackupAlbums = + await _backupAlbumService.getAllBySelection(BackupSelection.select); + final List excludedBackupAlbums = + await _backupAlbumService.getAllBySelection(BackupSelection.exclude); Set selectedAlbums = state.selectedBackupAlbums; Set excludedAlbums = state.excludedBackupAlbums; if (selectedAlbums.isNotEmpty) { @@ -756,23 +764,8 @@ class BackupNotifier extends StateNotifier { } BackUpProgressEnum get backupProgress => state.backupProgress; + void updateBackupProgress(BackUpProgressEnum backupProgress) { state = state.copyWith(backupProgress: backupProgress); } } - -final backupProvider = - StateNotifierProvider((ref) { - return BackupNotifier( - ref.watch(backupServiceProvider), - ref.watch(serverInfoServiceProvider), - ref.watch(authProvider), - ref.watch(backgroundServiceProvider), - ref.watch(galleryPermissionNotifier.notifier), - ref.watch(dbProvider), - ref.watch(albumMediaRepositoryProvider), - ref.watch(fileMediaRepositoryProvider), - ref.watch(backupRepositoryProvider), - ref, - ); -}); diff --git a/mobile/lib/providers/backup/manual_upload.provider.dart b/mobile/lib/providers/backup/manual_upload.provider.dart index 192126f085..6eaf0f7226 100644 --- a/mobile/lib/providers/backup/manual_upload.provider.dart +++ b/mobile/lib/providers/backup/manual_upload.provider.dart @@ -9,7 +9,6 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; import 'package:immich_mobile/models/backup/success_upload_asset.model.dart'; -import 'package:immich_mobile/repositories/backup.repository.dart'; import 'package:immich_mobile/repositories/file_media.repository.dart'; import 'package:immich_mobile/services/background.service.dart'; import 'package:immich_mobile/models/backup/backup_state.model.dart'; @@ -24,6 +23,7 @@ import 'package:immich_mobile/providers/app_settings.provider.dart'; import 'package:immich_mobile/services/app_settings.service.dart'; import 'package:immich_mobile/entities/asset.entity.dart'; import 'package:immich_mobile/providers/app_life_cycle.provider.dart'; +import 'package:immich_mobile/services/backup_album.service.dart'; import 'package:immich_mobile/services/local_notification.service.dart'; import 'package:immich_mobile/widgets/common/immich_toast.dart'; import 'package:immich_mobile/utils/backup_progress.dart'; @@ -37,7 +37,7 @@ final manualUploadProvider = ref.watch(localNotificationService), ref.watch(backupProvider.notifier), ref.watch(backupServiceProvider), - ref.watch(backupRepositoryProvider), + ref.watch(backupAlbumServiceProvider), ref, ); }); @@ -47,14 +47,14 @@ class ManualUploadNotifier extends StateNotifier { final LocalNotificationService _localNotificationService; final BackupNotifier _backupProvider; final BackupService _backupService; - final BackupRepository _backupRepository; + final BackupAlbumService _backupAlbumService; final Ref ref; ManualUploadNotifier( this._localNotificationService, this._backupProvider, this._backupService, - this._backupRepository, + this._backupAlbumService, this.ref, ) : super( ManualUploadState( @@ -210,9 +210,9 @@ class ManualUploadNotifier extends StateNotifier { } final selectedBackupAlbums = - await _backupRepository.getAllBySelection(BackupSelection.select); - final excludedBackupAlbums = - await _backupRepository.getAllBySelection(BackupSelection.exclude); + await _backupAlbumService.getAllBySelection(BackupSelection.select); + final excludedBackupAlbums = await _backupAlbumService + .getAllBySelection(BackupSelection.exclude); // Get candidates from selected albums and excluded albums Set candidates = diff --git a/mobile/lib/repositories/backup.repository.dart b/mobile/lib/repositories/backup.repository.dart index ed3a9c27e4..f7f3051f46 100644 --- a/mobile/lib/repositories/backup.repository.dart +++ b/mobile/lib/repositories/backup.repository.dart @@ -1,15 +1,16 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; -import 'package:immich_mobile/interfaces/backup.interface.dart'; +import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/providers/db.provider.dart'; import 'package:immich_mobile/repositories/database.repository.dart'; import 'package:isar/isar.dart'; -final backupRepositoryProvider = - Provider((ref) => BackupRepository(ref.watch(dbProvider))); +final backupAlbumRepositoryProvider = + Provider((ref) => BackupAlbumRepository(ref.watch(dbProvider))); -class BackupRepository extends DatabaseRepository implements IBackupRepository { - BackupRepository(super.db); +class BackupAlbumRepository extends DatabaseRepository + implements IBackupAlbumRepository { + BackupAlbumRepository(super.db); @override Future> getAll({BackupAlbumSort? sort}) { diff --git a/mobile/lib/services/album.service.dart b/mobile/lib/services/album.service.dart index 142ac48193..9c1f25f0a5 100644 --- a/mobile/lib/services/album.service.dart +++ b/mobile/lib/services/album.service.dart @@ -16,7 +16,7 @@ import 'package:immich_mobile/interfaces/album.interface.dart'; import 'package:immich_mobile/interfaces/album_api.interface.dart'; import 'package:immich_mobile/interfaces/album_media.interface.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; -import 'package:immich_mobile/interfaces/backup.interface.dart'; +import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/models/albums/album_add_asset_response.model.dart'; import 'package:immich_mobile/models/albums/album_search.model.dart'; import 'package:immich_mobile/repositories/album.repository.dart'; @@ -36,7 +36,7 @@ final albumServiceProvider = Provider( ref.watch(entityServiceProvider), ref.watch(albumRepositoryProvider), ref.watch(assetRepositoryProvider), - ref.watch(backupRepositoryProvider), + ref.watch(backupAlbumRepositoryProvider), ref.watch(albumMediaRepositoryProvider), ref.watch(albumApiRepositoryProvider), ), @@ -48,7 +48,7 @@ class AlbumService { final EntityService _entityService; final IAlbumRepository _albumRepository; final IAssetRepository _assetRepository; - final IBackupRepository _backupAlbumRepository; + final IBackupAlbumRepository _backupAlbumRepository; final IAlbumMediaRepository _albumMediaRepository; final IAlbumApiRepository _albumApiRepository; final Logger _log = Logger('AlbumService'); diff --git a/mobile/lib/services/asset.service.dart b/mobile/lib/services/asset.service.dart index b4a2c097b7..a4e77c216d 100644 --- a/mobile/lib/services/asset.service.dart +++ b/mobile/lib/services/asset.service.dart @@ -10,7 +10,7 @@ import 'package:immich_mobile/entities/user.entity.dart'; import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset_api.interface.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart'; -import 'package:immich_mobile/interfaces/backup.interface.dart'; +import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart'; import 'package:immich_mobile/interfaces/exif_info.interface.dart'; import 'package:immich_mobile/interfaces/user.interface.dart'; @@ -39,7 +39,7 @@ final assetServiceProvider = Provider( ref.watch(exifInfoRepositoryProvider), ref.watch(userRepositoryProvider), ref.watch(etagRepositoryProvider), - ref.watch(backupRepositoryProvider), + ref.watch(backupAlbumRepositoryProvider), ref.watch(apiServiceProvider), ref.watch(syncServiceProvider), ref.watch(userServiceProvider), @@ -55,7 +55,7 @@ class AssetService { final IExifInfoRepository _exifInfoRepository; final IUserRepository _userRepository; final IETagRepository _etagRepository; - final IBackupRepository _backupRepository; + final IBackupAlbumRepository _backupRepository; final ApiService _apiService; final SyncService _syncService; final UserService _userService; diff --git a/mobile/lib/services/background.service.dart b/mobile/lib/services/background.service.dart index 81619bdca1..2a7bfb2bb4 100644 --- a/mobile/lib/services/background.service.dart +++ b/mobile/lib/services/background.service.dart @@ -14,7 +14,7 @@ import 'package:hooks_riverpod/hooks_riverpod.dart'; import 'package:immich_mobile/domain/models/store.model.dart'; import 'package:immich_mobile/entities/backup_album.entity.dart'; import 'package:immich_mobile/entities/store.entity.dart'; -import 'package:immich_mobile/interfaces/backup.interface.dart'; +import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/main.dart'; import 'package:immich_mobile/models/backup/backup_candidate.model.dart'; import 'package:immich_mobile/models/backup/current_upload_asset.model.dart'; @@ -377,7 +377,7 @@ class BackgroundService { AppSettingsService settingsService = AppSettingsService(); AlbumRepository albumRepository = AlbumRepository(db); AssetRepository assetRepository = AssetRepository(db); - BackupRepository backupRepository = BackupRepository(db); + BackupAlbumRepository backupRepository = BackupAlbumRepository(db); ExifInfoRepository exifInfoRepository = ExifInfoRepository(db); ETagRepository eTagRepository = ETagRepository(db); AlbumMediaRepository albumMediaRepository = AlbumMediaRepository(); diff --git a/mobile/lib/services/backup_album.service.dart b/mobile/lib/services/backup_album.service.dart new file mode 100644 index 0000000000..8030d66937 --- /dev/null +++ b/mobile/lib/services/backup_album.service.dart @@ -0,0 +1,34 @@ +import 'package:hooks_riverpod/hooks_riverpod.dart'; +import 'package:immich_mobile/entities/backup_album.entity.dart'; +import 'package:immich_mobile/interfaces/backup_album.interface.dart'; +import 'package:immich_mobile/repositories/backup.repository.dart'; + +final backupAlbumServiceProvider = Provider((ref) { + return BackupAlbumService(ref.watch(backupAlbumRepositoryProvider)); +}); + +class BackupAlbumService { + final IBackupAlbumRepository _backupAlbumRepository; + + BackupAlbumService(this._backupAlbumRepository); + + Future> getAll({BackupAlbumSort? sort}) { + return _backupAlbumRepository.getAll(sort: sort); + } + + Future> getIdsBySelection(BackupSelection backup) { + return _backupAlbumRepository.getIdsBySelection(backup); + } + + Future> getAllBySelection(BackupSelection backup) { + return _backupAlbumRepository.getAllBySelection(backup); + } + + Future deleteAll(List ids) { + return _backupAlbumRepository.deleteAll(ids); + } + + Future updateAll(List backupAlbums) { + return _backupAlbumRepository.updateAll(backupAlbums); + } +} diff --git a/mobile/test/repository.mocks.dart b/mobile/test/repository.mocks.dart index 3dda932cac..bad7d3ebab 100644 --- a/mobile/test/repository.mocks.dart +++ b/mobile/test/repository.mocks.dart @@ -5,7 +5,7 @@ import 'package:immich_mobile/interfaces/asset.interface.dart'; import 'package:immich_mobile/interfaces/asset_media.interface.dart'; import 'package:immich_mobile/interfaces/auth.interface.dart'; import 'package:immich_mobile/interfaces/auth_api.interface.dart'; -import 'package:immich_mobile/interfaces/backup.interface.dart'; +import 'package:immich_mobile/interfaces/backup_album.interface.dart'; import 'package:immich_mobile/interfaces/etag.interface.dart'; import 'package:immich_mobile/interfaces/exif_info.interface.dart'; import 'package:immich_mobile/interfaces/file_media.interface.dart'; @@ -18,7 +18,7 @@ class MockAssetRepository extends Mock implements IAssetRepository {} class MockUserRepository extends Mock implements IUserRepository {} -class MockBackupRepository extends Mock implements IBackupRepository {} +class MockBackupRepository extends Mock implements IBackupAlbumRepository {} class MockExifInfoRepository extends Mock implements IExifInfoRepository {}