refactor(mobile): backup provider (#16360)

* refactor(mobile): backup provider

* refactor(mobile): backup provider
This commit is contained in:
Alex 2025-02-27 09:56:23 -06:00 committed by GitHub
parent 082471dfd9
commit c70c9067b0
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
9 changed files with 109 additions and 81 deletions

View File

@ -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<List<BackupAlbum>> getAll({BackupAlbumSort? sort});
Future<List<String>> getIdsBySelection(BackupSelection backup);

View File

@ -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<BackupNotifier, BackUpState>((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<BackUpState> {
BackupNotifier(
this._backupService,
@ -45,10 +58,9 @@ class BackupNotifier extends StateNotifier<BackUpState> {
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<BackUpState> {
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<BackUpState> {
state = state.copyWith(availableAlbums: availableAlbums);
final List<BackupAlbum> excludedBackupAlbums =
await _backupRepository.getAllBySelection(BackupSelection.exclude);
await _backupAlbumService.getAllBySelection(BackupSelection.exclude);
final List<BackupAlbum> selectedBackupAlbums =
await _backupRepository.getAllBySelection(BackupSelection.select);
await _backupAlbumService.getAllBySelection(BackupSelection.select);
final Set<AvailableAlbum> selectedAlbums = {};
for (final BackupAlbum ba in selectedBackupAlbums) {
@ -439,7 +450,7 @@ class BackupNotifier extends StateNotifier<BackUpState> {
}
/// Save user selection of selected albums and excluded albums to database
Future<void> _updatePersistentAlbumsSelection() {
Future<void> _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<BackUpState> {
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<int> toDelete = [];
final List<BackupAlbum> 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<int> toDelete = [];
final List<BackupAlbum> 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<BackUpState> {
}
Future<void> resumeBackup() async {
final List<BackupAlbum> selectedBackupAlbums = await _db.backupAlbums
.filter()
.selectionEqualTo(BackupSelection.select)
.findAll();
final List<BackupAlbum> excludedBackupAlbums = await _db.backupAlbums
.filter()
.selectionEqualTo(BackupSelection.exclude)
.findAll();
final List<BackupAlbum> selectedBackupAlbums =
await _backupAlbumService.getAllBySelection(BackupSelection.select);
final List<BackupAlbum> excludedBackupAlbums =
await _backupAlbumService.getAllBySelection(BackupSelection.exclude);
Set<AvailableAlbum> selectedAlbums = state.selectedBackupAlbums;
Set<AvailableAlbum> excludedAlbums = state.excludedBackupAlbums;
if (selectedAlbums.isNotEmpty) {
@ -756,23 +764,8 @@ class BackupNotifier extends StateNotifier<BackUpState> {
}
BackUpProgressEnum get backupProgress => state.backupProgress;
void updateBackupProgress(BackUpProgressEnum backupProgress) {
state = state.copyWith(backupProgress: backupProgress);
}
}
final backupProvider =
StateNotifierProvider<BackupNotifier, BackUpState>((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,
);
});

View File

@ -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<ManualUploadState> {
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<ManualUploadState> {
}
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<BackupCandidate> candidates =

View File

@ -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<List<BackupAlbum>> getAll({BackupAlbumSort? sort}) {

View File

@ -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');

View File

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

View File

@ -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();

View File

@ -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<BackupAlbumService>((ref) {
return BackupAlbumService(ref.watch(backupAlbumRepositoryProvider));
});
class BackupAlbumService {
final IBackupAlbumRepository _backupAlbumRepository;
BackupAlbumService(this._backupAlbumRepository);
Future<List<BackupAlbum>> getAll({BackupAlbumSort? sort}) {
return _backupAlbumRepository.getAll(sort: sort);
}
Future<List<String>> getIdsBySelection(BackupSelection backup) {
return _backupAlbumRepository.getIdsBySelection(backup);
}
Future<List<BackupAlbum>> getAllBySelection(BackupSelection backup) {
return _backupAlbumRepository.getAllBySelection(backup);
}
Future<void> deleteAll(List<int> ids) {
return _backupAlbumRepository.deleteAll(ids);
}
Future<void> updateAll(List<BackupAlbum> backupAlbums) {
return _backupAlbumRepository.updateAll(backupAlbums);
}
}

View File

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