|
|
|
|
@ -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,
|
|
|
|
|
);
|
|
|
|
|
});
|
|
|
|
|
|