mirror of
				https://github.com/KevinMidboe/immich.git
				synced 2025-10-29 17:40:28 +00:00 
			
		
		
		
	feature(mobile): no longer wait for background backup in settings (#1984)
* feature(mobile): no longer wait for background backup in settings migrate all Hive boxes required for the backup process to Isar * add final modifier
This commit is contained in:
		
				
					committed by
					
						 GitHub
						GitHub
					
				
			
			
				
	
			
			
			
						parent
						
							f56eaae019
						
					
				
				
					commit
					05cf5d57a9
				
			| @@ -9,6 +9,8 @@ import 'package:hive_flutter/hive_flutter.dart'; | |||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:immich_mobile/constants/locales.dart'; | import 'package:immich_mobile/constants/locales.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; | import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/backup_album.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; | import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart'; | import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; | import 'package:immich_mobile/modules/backup/providers/backup.provider.dart'; | ||||||
| @@ -104,6 +106,8 @@ Future<Isar> loadDb() async { | |||||||
|       AssetSchema, |       AssetSchema, | ||||||
|       AlbumSchema, |       AlbumSchema, | ||||||
|       UserSchema, |       UserSchema, | ||||||
|  |       BackupAlbumSchema, | ||||||
|  |       DuplicatedAssetSchema, | ||||||
|     ], |     ], | ||||||
|     directory: dir.path, |     directory: dir.path, | ||||||
|     maxSizeMiB: 256, |     maxSizeMiB: 256, | ||||||
| @@ -156,10 +160,12 @@ class ImmichAppState extends ConsumerState<ImmichApp> | |||||||
|  |  | ||||||
|         ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo(); |         ref.watch(releaseInfoProvider.notifier).checkGithubReleaseInfo(); | ||||||
|  |  | ||||||
|         ref.watch(notificationPermissionProvider.notifier) |         ref | ||||||
|           .getNotificationPermission(); |             .watch(notificationPermissionProvider.notifier) | ||||||
|         ref.watch(galleryPermissionNotifier.notifier) |             .getNotificationPermission(); | ||||||
|           .getGalleryPermissionStatus(); |         ref | ||||||
|  |             .watch(galleryPermissionNotifier.notifier) | ||||||
|  |             .getGalleryPermissionStatus(); | ||||||
|  |  | ||||||
|         ref.read(iOSBackgroundSettingsProvider.notifier).refresh(); |         ref.read(iOSBackgroundSettingsProvider.notifier).refresh(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -2,11 +2,9 @@ import 'dart:async'; | |||||||
|  |  | ||||||
| import 'package:collection/collection.dart'; | import 'package:collection/collection.dart'; | ||||||
| import 'package:flutter/foundation.dart'; | import 'package:flutter/foundation.dart'; | ||||||
| import 'package:hive_flutter/hive_flutter.dart'; |  | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:immich_mobile/constants/hive_box.dart'; | import 'package:immich_mobile/modules/backup/models/backup_album.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; | import 'package:immich_mobile/modules/backup/services/backup.service.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; |  | ||||||
| import 'package:immich_mobile/shared/models/album.dart'; | import 'package:immich_mobile/shared/models/album.dart'; | ||||||
| import 'package:immich_mobile/shared/models/asset.dart'; | import 'package:immich_mobile/shared/models/asset.dart'; | ||||||
| import 'package:immich_mobile/shared/models/store.dart'; | import 'package:immich_mobile/shared/models/store.dart'; | ||||||
| @@ -24,27 +22,27 @@ final albumServiceProvider = Provider( | |||||||
|   (ref) => AlbumService( |   (ref) => AlbumService( | ||||||
|     ref.watch(apiServiceProvider), |     ref.watch(apiServiceProvider), | ||||||
|     ref.watch(userServiceProvider), |     ref.watch(userServiceProvider), | ||||||
|     ref.watch(backgroundServiceProvider), |  | ||||||
|     ref.watch(syncServiceProvider), |     ref.watch(syncServiceProvider), | ||||||
|     ref.watch(dbProvider), |     ref.watch(dbProvider), | ||||||
|  |     ref.watch(backupServiceProvider), | ||||||
|   ), |   ), | ||||||
| ); | ); | ||||||
|  |  | ||||||
| class AlbumService { | class AlbumService { | ||||||
|   final ApiService _apiService; |   final ApiService _apiService; | ||||||
|   final UserService _userService; |   final UserService _userService; | ||||||
|   final BackgroundService _backgroundService; |  | ||||||
|   final SyncService _syncService; |   final SyncService _syncService; | ||||||
|   final Isar _db; |   final Isar _db; | ||||||
|  |   final BackupService _backupService; | ||||||
|   Completer<bool> _localCompleter = Completer()..complete(false); |   Completer<bool> _localCompleter = Completer()..complete(false); | ||||||
|   Completer<bool> _remoteCompleter = Completer()..complete(false); |   Completer<bool> _remoteCompleter = Completer()..complete(false); | ||||||
|  |  | ||||||
|   AlbumService( |   AlbumService( | ||||||
|     this._apiService, |     this._apiService, | ||||||
|     this._userService, |     this._userService, | ||||||
|     this._backgroundService, |  | ||||||
|     this._syncService, |     this._syncService, | ||||||
|     this._db, |     this._db, | ||||||
|  |     this._backupService, | ||||||
|   ); |   ); | ||||||
|  |  | ||||||
|   /// Checks all selected device albums for changes of albums and their assets |   /// Checks all selected device albums for changes of albums and their assets | ||||||
| @@ -58,13 +56,11 @@ class AlbumService { | |||||||
|     final Stopwatch sw = Stopwatch()..start(); |     final Stopwatch sw = Stopwatch()..start(); | ||||||
|     bool changes = false; |     bool changes = false; | ||||||
|     try { |     try { | ||||||
|       if (!await _backgroundService.hasAccess) { |       final List<String> excludedIds = | ||||||
|         return false; |           await _backupService.excludedAlbumsQuery().idProperty().findAll(); | ||||||
|       } |       final List<String> selectedIds = | ||||||
|       final HiveBackupAlbums? infos = |           await _backupService.selectedAlbumsQuery().idProperty().findAll(); | ||||||
|           (await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox)) |       if (selectedIds.isEmpty) { | ||||||
|               .get(backupInfoKey); |  | ||||||
|       if (infos == null) { |  | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|       final List<AssetPathEntity> onDevice = |       final List<AssetPathEntity> onDevice = | ||||||
| @@ -72,11 +68,11 @@ class AlbumService { | |||||||
|         hasAll: true, |         hasAll: true, | ||||||
|         filterOption: FilterOptionGroup(containsPathModified: true), |         filterOption: FilterOptionGroup(containsPathModified: true), | ||||||
|       ); |       ); | ||||||
|       if (infos.excludedAlbumsIds.isNotEmpty) { |       if (excludedIds.isNotEmpty) { | ||||||
|         // remove all excluded albums |         // remove all excluded albums | ||||||
|         onDevice.removeWhere((e) => infos.excludedAlbumsIds.contains(e.id)); |         onDevice.removeWhere((e) => excludedIds.contains(e.id)); | ||||||
|       } |       } | ||||||
|       final hasAll = infos.selectedAlbumIds |       final hasAll = selectedIds | ||||||
|           .map((id) => onDevice.firstWhereOrNull((a) => a.id == id)) |           .map((id) => onDevice.firstWhereOrNull((a) => a.id == id)) | ||||||
|           .whereNotNull() |           .whereNotNull() | ||||||
|           .any((a) => a.isAll); |           .any((a) => a.isAll); | ||||||
| @@ -85,7 +81,7 @@ class AlbumService { | |||||||
|         onDevice.removeWhere((e) => e.isAll); |         onDevice.removeWhere((e) => e.isAll); | ||||||
|       } else { |       } else { | ||||||
|         // keep only the explicitly selected albums |         // keep only the explicitly selected albums | ||||||
|         onDevice.removeWhere((e) => !infos.selectedAlbumIds.contains(e.id)); |         onDevice.removeWhere((e) => !selectedIds.contains(e.id)); | ||||||
|       } |       } | ||||||
|       changes = await _syncService.syncLocalAlbumAssetsToDb(onDevice); |       changes = await _syncService.syncLocalAlbumAssetsToDb(onDevice); | ||||||
|     } finally { |     } finally { | ||||||
|   | |||||||
| @@ -4,21 +4,25 @@ import 'dart:io'; | |||||||
| import 'dart:isolate'; | import 'dart:isolate'; | ||||||
| import 'dart:ui' show DartPluginRegistrant, IsolateNameServer, PluginUtilities; | import 'dart:ui' show DartPluginRegistrant, IsolateNameServer, PluginUtilities; | ||||||
| import 'package:cancellation_token_http/http.dart'; | import 'package:cancellation_token_http/http.dart'; | ||||||
|  | import 'package:collection/collection.dart'; | ||||||
| import 'package:easy_localization/easy_localization.dart'; | import 'package:easy_localization/easy_localization.dart'; | ||||||
| import 'package:flutter/services.dart'; | import 'package:flutter/services.dart'; | ||||||
| import 'package:flutter/widgets.dart'; | import 'package:flutter/widgets.dart'; | ||||||
| import 'package:hive_flutter/hive_flutter.dart'; | import 'package:hive_flutter/hive_flutter.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:immich_mobile/constants/hive_box.dart'; | import 'package:immich_mobile/constants/hive_box.dart'; | ||||||
|  | import 'package:immich_mobile/main.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/background_service/localization.dart'; | import 'package:immich_mobile/modules/backup/background_service/localization.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/backup_album.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart'; | import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart'; | import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; |  | ||||||
| import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart'; |  | ||||||
| import 'package:immich_mobile/modules/backup/services/backup.service.dart'; | import 'package:immich_mobile/modules/backup/services/backup.service.dart'; | ||||||
| import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart'; | import 'package:immich_mobile/modules/login/models/hive_saved_login_info.model.dart'; | ||||||
| import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; | import 'package:immich_mobile/modules/settings/services/app_settings.service.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/store.dart'; | ||||||
| import 'package:immich_mobile/shared/services/api.service.dart'; | import 'package:immich_mobile/shared/services/api.service.dart'; | ||||||
|  | import 'package:immich_mobile/utils/diff.dart'; | ||||||
|  | import 'package:isar/isar.dart'; | ||||||
| import 'package:path_provider_ios/path_provider_ios.dart'; | import 'package:path_provider_ios/path_provider_ios.dart'; | ||||||
| import 'package:photo_manager/photo_manager.dart'; | import 'package:photo_manager/photo_manager.dart'; | ||||||
|  |  | ||||||
| @@ -51,10 +55,6 @@ class BackgroundService { | |||||||
|       _Throttle(_updateProgress, notifyInterval); |       _Throttle(_updateProgress, notifyInterval); | ||||||
|   late final _Throttle _throttledDetailNotify = |   late final _Throttle _throttledDetailNotify = | ||||||
|       _Throttle(_updateDetailProgress, notifyInterval); |       _Throttle(_updateDetailProgress, notifyInterval); | ||||||
|   Completer<bool> _hasAccessCompleter = Completer(); |  | ||||||
|   late Future<bool> _hasAccess = _hasAccessCompleter.future; |  | ||||||
|  |  | ||||||
|   Future<bool> get hasAccess => _hasAccess; |  | ||||||
|  |  | ||||||
|   bool get isBackgroundInitialized { |   bool get isBackgroundInitialized { | ||||||
|     return _isBackgroundInitialized; |     return _isBackgroundInitialized; | ||||||
| @@ -194,11 +194,6 @@ class BackgroundService { | |||||||
|       debugPrint("WARNING: [acquireLock] called more than once"); |       debugPrint("WARNING: [acquireLock] called more than once"); | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|     if (_hasAccessCompleter.isCompleted) { |  | ||||||
|       debugPrint("WARNING: [acquireLock] _hasAccessCompleter is completed"); |  | ||||||
|       _hasAccessCompleter = Completer(); |  | ||||||
|       _hasAccess = _hasAccessCompleter.future; |  | ||||||
|     } |  | ||||||
|     final int lockTime = Timeline.now; |     final int lockTime = Timeline.now; | ||||||
|     _wantsLockTime = lockTime; |     _wantsLockTime = lockTime; | ||||||
|     final ReceivePort rp = ReceivePort(_portNameLock); |     final ReceivePort rp = ReceivePort(_portNameLock); | ||||||
| @@ -217,7 +212,6 @@ class BackgroundService { | |||||||
|     } |     } | ||||||
|     _hasLock = true; |     _hasLock = true; | ||||||
|     rp.listen(_heartbeatListener); |     rp.listen(_heartbeatListener); | ||||||
|     _hasAccessCompleter.complete(true); |  | ||||||
|     return true; |     return true; | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -267,8 +261,6 @@ class BackgroundService { | |||||||
|   void releaseLock() { |   void releaseLock() { | ||||||
|     _wantsLockTime = 0; |     _wantsLockTime = 0; | ||||||
|     if (_hasLock) { |     if (_hasLock) { | ||||||
|       _hasAccessCompleter = Completer(); |  | ||||||
|       _hasAccess = _hasAccessCompleter.future; |  | ||||||
|       IsolateNameServer.removePortNameMapping(_portNameLock); |       IsolateNameServer.removePortNameMapping(_portNameLock); | ||||||
|       _waitingIsolate?.send(true); |       _waitingIsolate?.send(true); | ||||||
|       _waitingIsolate = null; |       _waitingIsolate = null; | ||||||
| @@ -339,29 +331,24 @@ class BackgroundService { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<bool> _onAssetsChanged() async { |   Future<bool> _onAssetsChanged() async { | ||||||
|  |     final Isar db = await loadDb(); | ||||||
|     await Hive.initFlutter(); |     await Hive.initFlutter(); | ||||||
|  |  | ||||||
|     Hive.registerAdapter(HiveSavedLoginInfoAdapter()); |     Hive.registerAdapter(HiveSavedLoginInfoAdapter()); | ||||||
|     Hive.registerAdapter(HiveBackupAlbumsAdapter()); |  | ||||||
|     Hive.registerAdapter(HiveDuplicatedAssetsAdapter()); |  | ||||||
|  |  | ||||||
|     await Future.wait([ |     await Future.wait([ | ||||||
|       Hive.openBox(userInfoBox), |       Hive.openBox(userInfoBox), | ||||||
|       Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox), |       Hive.openBox<HiveSavedLoginInfo>(hiveLoginInfoBox), | ||||||
|       Hive.openBox(userSettingInfoBox), |       Hive.openBox(userSettingInfoBox), | ||||||
|       Hive.openBox(backgroundBackupInfoBox), |  | ||||||
|       Hive.openBox<HiveDuplicatedAssets>(duplicatedAssetsBox), |  | ||||||
|       Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox), |  | ||||||
|     ]); |     ]); | ||||||
|     ApiService apiService = ApiService(); |     ApiService apiService = ApiService(); | ||||||
|     apiService.setAccessToken(Hive.box(userInfoBox).get(accessTokenKey)); |     apiService.setAccessToken(Hive.box(userInfoBox).get(accessTokenKey)); | ||||||
|     BackupService backupService = BackupService(apiService); |     BackupService backupService = BackupService(apiService, db); | ||||||
|     AppSettingsService settingsService = AppSettingsService(); |     AppSettingsService settingsService = AppSettingsService(); | ||||||
|  |  | ||||||
|     final Box<HiveBackupAlbums> box = |     final selectedAlbums = backupService.selectedAlbumsQuery().findAllSync(); | ||||||
|         Hive.box<HiveBackupAlbums>(hiveBackupInfoBox); |     final excludedAlbums = backupService.excludedAlbumsQuery().findAllSync(); | ||||||
|     final HiveBackupAlbums? backupAlbumInfo = box.get(backupInfoKey); |     if (selectedAlbums.isEmpty) { | ||||||
|     if (backupAlbumInfo == null) { |  | ||||||
|       return true; |       return true; | ||||||
|     } |     } | ||||||
|  |  | ||||||
| @@ -371,18 +358,37 @@ class BackgroundService { | |||||||
|       final bool backupOk = await _runBackup( |       final bool backupOk = await _runBackup( | ||||||
|         backupService, |         backupService, | ||||||
|         settingsService, |         settingsService, | ||||||
|         backupAlbumInfo, |         selectedAlbums, | ||||||
|  |         excludedAlbums, | ||||||
|       ); |       ); | ||||||
|       if (backupOk) { |       if (backupOk) { | ||||||
|         await Hive.box(backgroundBackupInfoBox).delete(backupFailedSince); |         await Store.delete(StoreKey.backupFailedSince); | ||||||
|         await box.put( |         final backupAlbums = [...selectedAlbums, ...excludedAlbums]; | ||||||
|           backupInfoKey, |         backupAlbums.sortBy((e) => e.id); | ||||||
|           backupAlbumInfo, |         db.writeTxnSync(() { | ||||||
|         ); |           final dbAlbums = db.backupAlbums.where().sortById().findAllSync(); | ||||||
|       } else if (Hive.box(backgroundBackupInfoBox).get(backupFailedSince) == |           final List<int> toDelete = []; | ||||||
|           null) { |           final List<BackupAlbum> toUpsert = []; | ||||||
|         Hive.box(backgroundBackupInfoBox) |           // stores the most recent `lastBackup` per album but always keeps the `selection` from the most recent DB state | ||||||
|             .put(backupFailedSince, DateTime.now()); |           diffSortedListsSync( | ||||||
|  |             dbAlbums, | ||||||
|  |             backupAlbums, | ||||||
|  |             compare: (BackupAlbum a, BackupAlbum b) => a.id.compareTo(b.id), | ||||||
|  |             both: (BackupAlbum a, BackupAlbum b) { | ||||||
|  |               a.lastBackup = a.lastBackup.isAfter(b.lastBackup) | ||||||
|  |                   ? a.lastBackup | ||||||
|  |                   : b.lastBackup; | ||||||
|  |               toUpsert.add(a); | ||||||
|  |               return true; | ||||||
|  |             }, | ||||||
|  |             onlyFirst: (BackupAlbum a) => toUpsert.add(a), | ||||||
|  |             onlySecond: (BackupAlbum b) => toDelete.add(b.isarId), | ||||||
|  |           ); | ||||||
|  |           db.backupAlbums.deleteAllSync(toDelete); | ||||||
|  |           db.backupAlbums.putAllSync(toUpsert); | ||||||
|  |         }); | ||||||
|  |       } else if (Store.get(StoreKey.backupFailedSince) == null) { | ||||||
|  |         Store.put(StoreKey.backupFailedSince, DateTime.now()); | ||||||
|         return false; |         return false; | ||||||
|       } |       } | ||||||
|       // Android should check for new assets added while performing backup |       // Android should check for new assets added while performing backup | ||||||
| @@ -395,7 +401,8 @@ class BackgroundService { | |||||||
|   Future<bool> _runBackup( |   Future<bool> _runBackup( | ||||||
|     BackupService backupService, |     BackupService backupService, | ||||||
|     AppSettingsService settingsService, |     AppSettingsService settingsService, | ||||||
|     HiveBackupAlbums backupAlbumInfo, |     List<BackupAlbum> selectedAlbums, | ||||||
|  |     List<BackupAlbum> excludedAlbums, | ||||||
|   ) async { |   ) async { | ||||||
|     _errorGracePeriodExceeded = _isErrorGracePeriodExceeded(settingsService); |     _errorGracePeriodExceeded = _isErrorGracePeriodExceeded(settingsService); | ||||||
|     final bool notifyTotalProgress = settingsService |     final bool notifyTotalProgress = settingsService | ||||||
| @@ -407,8 +414,10 @@ class BackgroundService { | |||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     List<AssetEntity> toUpload = |     List<AssetEntity> toUpload = await backupService.buildUploadCandidates( | ||||||
|         await backupService.buildUploadCandidates(backupAlbumInfo); |       selectedAlbums, | ||||||
|  |       excludedAlbums, | ||||||
|  |     ); | ||||||
|  |  | ||||||
|     try { |     try { | ||||||
|       toUpload = await backupService.removeAlreadyUploadedAssets(toUpload); |       toUpload = await backupService.removeAlreadyUploadedAssets(toUpload); | ||||||
| @@ -520,8 +529,7 @@ class BackgroundService { | |||||||
|     } else if (value == 5) { |     } else if (value == 5) { | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|     final DateTime? failedSince = |     final DateTime? failedSince = Store.get(StoreKey.backupFailedSince); | ||||||
|         Hive.box(backgroundBackupInfoBox).get(backupFailedSince); |  | ||||||
|     if (failedSince == null) { |     if (failedSince == null) { | ||||||
|       return false; |       return false; | ||||||
|     } |     } | ||||||
|   | |||||||
							
								
								
									
										22
									
								
								mobile/lib/modules/backup/models/backup_album.model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										22
									
								
								mobile/lib/modules/backup/models/backup_album.model.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,22 @@ | |||||||
|  | import 'package:immich_mobile/utils/hash.dart'; | ||||||
|  | import 'package:isar/isar.dart'; | ||||||
|  |  | ||||||
|  | part 'backup_album.model.g.dart'; | ||||||
|  |  | ||||||
|  | @Collection(inheritance: false) | ||||||
|  | class BackupAlbum { | ||||||
|  |   String id; | ||||||
|  |   DateTime lastBackup; | ||||||
|  |   @Enumerated(EnumType.ordinal) | ||||||
|  |   BackupSelection selection; | ||||||
|  |  | ||||||
|  |   BackupAlbum(this.id, this.lastBackup, this.selection); | ||||||
|  |  | ||||||
|  |   Id get isarId => fastHash(id); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | enum BackupSelection { | ||||||
|  |   none, | ||||||
|  |   select, | ||||||
|  |   exclude; | ||||||
|  | } | ||||||
							
								
								
									
										653
									
								
								mobile/lib/modules/backup/models/backup_album.model.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										653
									
								
								mobile/lib/modules/backup/models/backup_album.model.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,653 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'backup_album.model.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // IsarCollectionGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | // coverage:ignore-file | ||||||
|  | // ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters | ||||||
|  |  | ||||||
|  | extension GetBackupAlbumCollection on Isar { | ||||||
|  |   IsarCollection<BackupAlbum> get backupAlbums => this.collection(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const BackupAlbumSchema = CollectionSchema( | ||||||
|  |   name: r'BackupAlbum', | ||||||
|  |   id: 8308487201128361847, | ||||||
|  |   properties: { | ||||||
|  |     r'id': PropertySchema( | ||||||
|  |       id: 0, | ||||||
|  |       name: r'id', | ||||||
|  |       type: IsarType.string, | ||||||
|  |     ), | ||||||
|  |     r'lastBackup': PropertySchema( | ||||||
|  |       id: 1, | ||||||
|  |       name: r'lastBackup', | ||||||
|  |       type: IsarType.dateTime, | ||||||
|  |     ), | ||||||
|  |     r'selection': PropertySchema( | ||||||
|  |       id: 2, | ||||||
|  |       name: r'selection', | ||||||
|  |       type: IsarType.byte, | ||||||
|  |       enumMap: _BackupAlbumselectionEnumValueMap, | ||||||
|  |     ) | ||||||
|  |   }, | ||||||
|  |   estimateSize: _backupAlbumEstimateSize, | ||||||
|  |   serialize: _backupAlbumSerialize, | ||||||
|  |   deserialize: _backupAlbumDeserialize, | ||||||
|  |   deserializeProp: _backupAlbumDeserializeProp, | ||||||
|  |   idName: r'isarId', | ||||||
|  |   indexes: {}, | ||||||
|  |   links: {}, | ||||||
|  |   embeddedSchemas: {}, | ||||||
|  |   getId: _backupAlbumGetId, | ||||||
|  |   getLinks: _backupAlbumGetLinks, | ||||||
|  |   attach: _backupAlbumAttach, | ||||||
|  |   version: '3.0.5', | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | int _backupAlbumEstimateSize( | ||||||
|  |   BackupAlbum object, | ||||||
|  |   List<int> offsets, | ||||||
|  |   Map<Type, List<int>> allOffsets, | ||||||
|  | ) { | ||||||
|  |   var bytesCount = offsets.last; | ||||||
|  |   bytesCount += 3 + object.id.length * 3; | ||||||
|  |   return bytesCount; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void _backupAlbumSerialize( | ||||||
|  |   BackupAlbum object, | ||||||
|  |   IsarWriter writer, | ||||||
|  |   List<int> offsets, | ||||||
|  |   Map<Type, List<int>> allOffsets, | ||||||
|  | ) { | ||||||
|  |   writer.writeString(offsets[0], object.id); | ||||||
|  |   writer.writeDateTime(offsets[1], object.lastBackup); | ||||||
|  |   writer.writeByte(offsets[2], object.selection.index); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | BackupAlbum _backupAlbumDeserialize( | ||||||
|  |   Id id, | ||||||
|  |   IsarReader reader, | ||||||
|  |   List<int> offsets, | ||||||
|  |   Map<Type, List<int>> allOffsets, | ||||||
|  | ) { | ||||||
|  |   final object = BackupAlbum( | ||||||
|  |     reader.readString(offsets[0]), | ||||||
|  |     reader.readDateTime(offsets[1]), | ||||||
|  |     _BackupAlbumselectionValueEnumMap[reader.readByteOrNull(offsets[2])] ?? | ||||||
|  |         BackupSelection.none, | ||||||
|  |   ); | ||||||
|  |   return object; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | P _backupAlbumDeserializeProp<P>( | ||||||
|  |   IsarReader reader, | ||||||
|  |   int propertyId, | ||||||
|  |   int offset, | ||||||
|  |   Map<Type, List<int>> allOffsets, | ||||||
|  | ) { | ||||||
|  |   switch (propertyId) { | ||||||
|  |     case 0: | ||||||
|  |       return (reader.readString(offset)) as P; | ||||||
|  |     case 1: | ||||||
|  |       return (reader.readDateTime(offset)) as P; | ||||||
|  |     case 2: | ||||||
|  |       return (_BackupAlbumselectionValueEnumMap[ | ||||||
|  |               reader.readByteOrNull(offset)] ?? | ||||||
|  |           BackupSelection.none) as P; | ||||||
|  |     default: | ||||||
|  |       throw IsarError('Unknown property with id $propertyId'); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const _BackupAlbumselectionEnumValueMap = { | ||||||
|  |   'none': 0, | ||||||
|  |   'select': 1, | ||||||
|  |   'exclude': 2, | ||||||
|  | }; | ||||||
|  | const _BackupAlbumselectionValueEnumMap = { | ||||||
|  |   0: BackupSelection.none, | ||||||
|  |   1: BackupSelection.select, | ||||||
|  |   2: BackupSelection.exclude, | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | Id _backupAlbumGetId(BackupAlbum object) { | ||||||
|  |   return object.isarId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | List<IsarLinkBase<dynamic>> _backupAlbumGetLinks(BackupAlbum object) { | ||||||
|  |   return []; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void _backupAlbumAttach( | ||||||
|  |     IsarCollection<dynamic> col, Id id, BackupAlbum object) {} | ||||||
|  |  | ||||||
|  | extension BackupAlbumQueryWhereSort | ||||||
|  |     on QueryBuilder<BackupAlbum, BackupAlbum, QWhere> { | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterWhere> anyIsarId() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addWhereClause(const IdWhereClause.any()); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension BackupAlbumQueryWhere | ||||||
|  |     on QueryBuilder<BackupAlbum, BackupAlbum, QWhereClause> { | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterWhereClause> isarIdEqualTo( | ||||||
|  |       Id isarId) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addWhereClause(IdWhereClause.between( | ||||||
|  |         lower: isarId, | ||||||
|  |         upper: isarId, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterWhereClause> isarIdNotEqualTo( | ||||||
|  |       Id isarId) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       if (query.whereSort == Sort.asc) { | ||||||
|  |         return query | ||||||
|  |             .addWhereClause( | ||||||
|  |               IdWhereClause.lessThan(upper: isarId, includeUpper: false), | ||||||
|  |             ) | ||||||
|  |             .addWhereClause( | ||||||
|  |               IdWhereClause.greaterThan(lower: isarId, includeLower: false), | ||||||
|  |             ); | ||||||
|  |       } else { | ||||||
|  |         return query | ||||||
|  |             .addWhereClause( | ||||||
|  |               IdWhereClause.greaterThan(lower: isarId, includeLower: false), | ||||||
|  |             ) | ||||||
|  |             .addWhereClause( | ||||||
|  |               IdWhereClause.lessThan(upper: isarId, includeUpper: false), | ||||||
|  |             ); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterWhereClause> isarIdGreaterThan( | ||||||
|  |       Id isarId, | ||||||
|  |       {bool include = false}) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addWhereClause( | ||||||
|  |         IdWhereClause.greaterThan(lower: isarId, includeLower: include), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterWhereClause> isarIdLessThan( | ||||||
|  |       Id isarId, | ||||||
|  |       {bool include = false}) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addWhereClause( | ||||||
|  |         IdWhereClause.lessThan(upper: isarId, includeUpper: include), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterWhereClause> isarIdBetween( | ||||||
|  |     Id lowerIsarId, | ||||||
|  |     Id upperIsarId, { | ||||||
|  |     bool includeLower = true, | ||||||
|  |     bool includeUpper = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addWhereClause(IdWhereClause.between( | ||||||
|  |         lower: lowerIsarId, | ||||||
|  |         includeLower: includeLower, | ||||||
|  |         upper: upperIsarId, | ||||||
|  |         includeUpper: includeUpper, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension BackupAlbumQueryFilter | ||||||
|  |     on QueryBuilder<BackupAlbum, BackupAlbum, QFilterCondition> { | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> idEqualTo( | ||||||
|  |     String value, { | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.equalTo( | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> idGreaterThan( | ||||||
|  |     String value, { | ||||||
|  |     bool include = false, | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.greaterThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> idLessThan( | ||||||
|  |     String value, { | ||||||
|  |     bool include = false, | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.lessThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> idBetween( | ||||||
|  |     String lower, | ||||||
|  |     String upper, { | ||||||
|  |     bool includeLower = true, | ||||||
|  |     bool includeUpper = true, | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.between( | ||||||
|  |         property: r'id', | ||||||
|  |         lower: lower, | ||||||
|  |         includeLower: includeLower, | ||||||
|  |         upper: upper, | ||||||
|  |         includeUpper: includeUpper, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> idStartsWith( | ||||||
|  |     String value, { | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.startsWith( | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> idEndsWith( | ||||||
|  |     String value, { | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.endsWith( | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> idContains( | ||||||
|  |       String value, | ||||||
|  |       {bool caseSensitive = true}) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.contains( | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> idMatches( | ||||||
|  |       String pattern, | ||||||
|  |       {bool caseSensitive = true}) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.matches( | ||||||
|  |         property: r'id', | ||||||
|  |         wildcard: pattern, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> idIsEmpty() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.equalTo( | ||||||
|  |         property: r'id', | ||||||
|  |         value: '', | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> idIsNotEmpty() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.greaterThan( | ||||||
|  |         property: r'id', | ||||||
|  |         value: '', | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> isarIdEqualTo( | ||||||
|  |       Id value) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.equalTo( | ||||||
|  |         property: r'isarId', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> | ||||||
|  |       isarIdGreaterThan( | ||||||
|  |     Id value, { | ||||||
|  |     bool include = false, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.greaterThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'isarId', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> isarIdLessThan( | ||||||
|  |     Id value, { | ||||||
|  |     bool include = false, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.lessThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'isarId', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> isarIdBetween( | ||||||
|  |     Id lower, | ||||||
|  |     Id upper, { | ||||||
|  |     bool includeLower = true, | ||||||
|  |     bool includeUpper = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.between( | ||||||
|  |         property: r'isarId', | ||||||
|  |         lower: lower, | ||||||
|  |         includeLower: includeLower, | ||||||
|  |         upper: upper, | ||||||
|  |         includeUpper: includeUpper, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> | ||||||
|  |       lastBackupEqualTo(DateTime value) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.equalTo( | ||||||
|  |         property: r'lastBackup', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> | ||||||
|  |       lastBackupGreaterThan( | ||||||
|  |     DateTime value, { | ||||||
|  |     bool include = false, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.greaterThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'lastBackup', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> | ||||||
|  |       lastBackupLessThan( | ||||||
|  |     DateTime value, { | ||||||
|  |     bool include = false, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.lessThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'lastBackup', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> | ||||||
|  |       lastBackupBetween( | ||||||
|  |     DateTime lower, | ||||||
|  |     DateTime upper, { | ||||||
|  |     bool includeLower = true, | ||||||
|  |     bool includeUpper = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.between( | ||||||
|  |         property: r'lastBackup', | ||||||
|  |         lower: lower, | ||||||
|  |         includeLower: includeLower, | ||||||
|  |         upper: upper, | ||||||
|  |         includeUpper: includeUpper, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> | ||||||
|  |       selectionEqualTo(BackupSelection value) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.equalTo( | ||||||
|  |         property: r'selection', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> | ||||||
|  |       selectionGreaterThan( | ||||||
|  |     BackupSelection value, { | ||||||
|  |     bool include = false, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.greaterThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'selection', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> | ||||||
|  |       selectionLessThan( | ||||||
|  |     BackupSelection value, { | ||||||
|  |     bool include = false, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.lessThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'selection', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> | ||||||
|  |       selectionBetween( | ||||||
|  |     BackupSelection lower, | ||||||
|  |     BackupSelection upper, { | ||||||
|  |     bool includeLower = true, | ||||||
|  |     bool includeUpper = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.between( | ||||||
|  |         property: r'selection', | ||||||
|  |         lower: lower, | ||||||
|  |         includeLower: includeLower, | ||||||
|  |         upper: upper, | ||||||
|  |         includeUpper: includeUpper, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension BackupAlbumQueryObject | ||||||
|  |     on QueryBuilder<BackupAlbum, BackupAlbum, QFilterCondition> {} | ||||||
|  |  | ||||||
|  | extension BackupAlbumQueryLinks | ||||||
|  |     on QueryBuilder<BackupAlbum, BackupAlbum, QFilterCondition> {} | ||||||
|  |  | ||||||
|  | extension BackupAlbumQuerySortBy | ||||||
|  |     on QueryBuilder<BackupAlbum, BackupAlbum, QSortBy> { | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> sortById() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'id', Sort.asc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> sortByIdDesc() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'id', Sort.desc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> sortByLastBackup() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'lastBackup', Sort.asc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> sortByLastBackupDesc() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'lastBackup', Sort.desc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> sortBySelection() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'selection', Sort.asc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> sortBySelectionDesc() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'selection', Sort.desc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension BackupAlbumQuerySortThenBy | ||||||
|  |     on QueryBuilder<BackupAlbum, BackupAlbum, QSortThenBy> { | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> thenById() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'id', Sort.asc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> thenByIdDesc() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'id', Sort.desc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> thenByIsarId() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'isarId', Sort.asc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> thenByIsarIdDesc() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'isarId', Sort.desc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> thenByLastBackup() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'lastBackup', Sort.asc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> thenByLastBackupDesc() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'lastBackup', Sort.desc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> thenBySelection() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'selection', Sort.asc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterSortBy> thenBySelectionDesc() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'selection', Sort.desc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension BackupAlbumQueryWhereDistinct | ||||||
|  |     on QueryBuilder<BackupAlbum, BackupAlbum, QDistinct> { | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QDistinct> distinctById( | ||||||
|  |       {bool caseSensitive = true}) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addDistinctBy(r'id', caseSensitive: caseSensitive); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QDistinct> distinctByLastBackup() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addDistinctBy(r'lastBackup'); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QDistinct> distinctBySelection() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addDistinctBy(r'selection'); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension BackupAlbumQueryProperty | ||||||
|  |     on QueryBuilder<BackupAlbum, BackupAlbum, QQueryProperty> { | ||||||
|  |   QueryBuilder<BackupAlbum, int, QQueryOperations> isarIdProperty() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addPropertyName(r'isarId'); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, String, QQueryOperations> idProperty() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addPropertyName(r'id'); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, DateTime, QQueryOperations> lastBackupProperty() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addPropertyName(r'lastBackup'); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupSelection, QQueryOperations> | ||||||
|  |       selectionProperty() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addPropertyName(r'selection'); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
							
								
								
									
										11
									
								
								mobile/lib/modules/backup/models/duplicated_asset.model.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								mobile/lib/modules/backup/models/duplicated_asset.model.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,11 @@ | |||||||
|  | import 'package:immich_mobile/utils/hash.dart'; | ||||||
|  | import 'package:isar/isar.dart'; | ||||||
|  |  | ||||||
|  | part 'duplicated_asset.model.g.dart'; | ||||||
|  |  | ||||||
|  | @Collection(inheritance: false) | ||||||
|  | class DuplicatedAsset { | ||||||
|  |   String id; | ||||||
|  |   DuplicatedAsset(this.id); | ||||||
|  |   Id get isarId => fastHash(id); | ||||||
|  | } | ||||||
							
								
								
									
										443
									
								
								mobile/lib/modules/backup/models/duplicated_asset.model.g.dart
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										443
									
								
								mobile/lib/modules/backup/models/duplicated_asset.model.g.dart
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,443 @@ | |||||||
|  | // GENERATED CODE - DO NOT MODIFY BY HAND | ||||||
|  |  | ||||||
|  | part of 'duplicated_asset.model.dart'; | ||||||
|  |  | ||||||
|  | // ************************************************************************** | ||||||
|  | // IsarCollectionGenerator | ||||||
|  | // ************************************************************************** | ||||||
|  |  | ||||||
|  | // coverage:ignore-file | ||||||
|  | // ignore_for_file: duplicate_ignore, non_constant_identifier_names, constant_identifier_names, invalid_use_of_protected_member, unnecessary_cast, prefer_const_constructors, lines_longer_than_80_chars, require_trailing_commas, inference_failure_on_function_invocation, unnecessary_parenthesis, unnecessary_raw_strings, unnecessary_null_checks, join_return_with_assignment, prefer_final_locals, avoid_js_rounded_ints, avoid_positional_boolean_parameters | ||||||
|  |  | ||||||
|  | extension GetDuplicatedAssetCollection on Isar { | ||||||
|  |   IsarCollection<DuplicatedAsset> get duplicatedAssets => this.collection(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | const DuplicatedAssetSchema = CollectionSchema( | ||||||
|  |   name: r'DuplicatedAsset', | ||||||
|  |   id: -2679334728174694496, | ||||||
|  |   properties: { | ||||||
|  |     r'id': PropertySchema( | ||||||
|  |       id: 0, | ||||||
|  |       name: r'id', | ||||||
|  |       type: IsarType.string, | ||||||
|  |     ) | ||||||
|  |   }, | ||||||
|  |   estimateSize: _duplicatedAssetEstimateSize, | ||||||
|  |   serialize: _duplicatedAssetSerialize, | ||||||
|  |   deserialize: _duplicatedAssetDeserialize, | ||||||
|  |   deserializeProp: _duplicatedAssetDeserializeProp, | ||||||
|  |   idName: r'isarId', | ||||||
|  |   indexes: {}, | ||||||
|  |   links: {}, | ||||||
|  |   embeddedSchemas: {}, | ||||||
|  |   getId: _duplicatedAssetGetId, | ||||||
|  |   getLinks: _duplicatedAssetGetLinks, | ||||||
|  |   attach: _duplicatedAssetAttach, | ||||||
|  |   version: '3.0.5', | ||||||
|  | ); | ||||||
|  |  | ||||||
|  | int _duplicatedAssetEstimateSize( | ||||||
|  |   DuplicatedAsset object, | ||||||
|  |   List<int> offsets, | ||||||
|  |   Map<Type, List<int>> allOffsets, | ||||||
|  | ) { | ||||||
|  |   var bytesCount = offsets.last; | ||||||
|  |   bytesCount += 3 + object.id.length * 3; | ||||||
|  |   return bytesCount; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void _duplicatedAssetSerialize( | ||||||
|  |   DuplicatedAsset object, | ||||||
|  |   IsarWriter writer, | ||||||
|  |   List<int> offsets, | ||||||
|  |   Map<Type, List<int>> allOffsets, | ||||||
|  | ) { | ||||||
|  |   writer.writeString(offsets[0], object.id); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | DuplicatedAsset _duplicatedAssetDeserialize( | ||||||
|  |   Id id, | ||||||
|  |   IsarReader reader, | ||||||
|  |   List<int> offsets, | ||||||
|  |   Map<Type, List<int>> allOffsets, | ||||||
|  | ) { | ||||||
|  |   final object = DuplicatedAsset( | ||||||
|  |     reader.readString(offsets[0]), | ||||||
|  |   ); | ||||||
|  |   return object; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | P _duplicatedAssetDeserializeProp<P>( | ||||||
|  |   IsarReader reader, | ||||||
|  |   int propertyId, | ||||||
|  |   int offset, | ||||||
|  |   Map<Type, List<int>> allOffsets, | ||||||
|  | ) { | ||||||
|  |   switch (propertyId) { | ||||||
|  |     case 0: | ||||||
|  |       return (reader.readString(offset)) as P; | ||||||
|  |     default: | ||||||
|  |       throw IsarError('Unknown property with id $propertyId'); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Id _duplicatedAssetGetId(DuplicatedAsset object) { | ||||||
|  |   return object.isarId; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | List<IsarLinkBase<dynamic>> _duplicatedAssetGetLinks(DuplicatedAsset object) { | ||||||
|  |   return []; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | void _duplicatedAssetAttach( | ||||||
|  |     IsarCollection<dynamic> col, Id id, DuplicatedAsset object) {} | ||||||
|  |  | ||||||
|  | extension DuplicatedAssetQueryWhereSort | ||||||
|  |     on QueryBuilder<DuplicatedAsset, DuplicatedAsset, QWhere> { | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterWhere> anyIsarId() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addWhereClause(const IdWhereClause.any()); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension DuplicatedAssetQueryWhere | ||||||
|  |     on QueryBuilder<DuplicatedAsset, DuplicatedAsset, QWhereClause> { | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterWhereClause> | ||||||
|  |       isarIdEqualTo(Id isarId) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addWhereClause(IdWhereClause.between( | ||||||
|  |         lower: isarId, | ||||||
|  |         upper: isarId, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterWhereClause> | ||||||
|  |       isarIdNotEqualTo(Id isarId) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       if (query.whereSort == Sort.asc) { | ||||||
|  |         return query | ||||||
|  |             .addWhereClause( | ||||||
|  |               IdWhereClause.lessThan(upper: isarId, includeUpper: false), | ||||||
|  |             ) | ||||||
|  |             .addWhereClause( | ||||||
|  |               IdWhereClause.greaterThan(lower: isarId, includeLower: false), | ||||||
|  |             ); | ||||||
|  |       } else { | ||||||
|  |         return query | ||||||
|  |             .addWhereClause( | ||||||
|  |               IdWhereClause.greaterThan(lower: isarId, includeLower: false), | ||||||
|  |             ) | ||||||
|  |             .addWhereClause( | ||||||
|  |               IdWhereClause.lessThan(upper: isarId, includeUpper: false), | ||||||
|  |             ); | ||||||
|  |       } | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterWhereClause> | ||||||
|  |       isarIdGreaterThan(Id isarId, {bool include = false}) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addWhereClause( | ||||||
|  |         IdWhereClause.greaterThan(lower: isarId, includeLower: include), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterWhereClause> | ||||||
|  |       isarIdLessThan(Id isarId, {bool include = false}) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addWhereClause( | ||||||
|  |         IdWhereClause.lessThan(upper: isarId, includeUpper: include), | ||||||
|  |       ); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterWhereClause> | ||||||
|  |       isarIdBetween( | ||||||
|  |     Id lowerIsarId, | ||||||
|  |     Id upperIsarId, { | ||||||
|  |     bool includeLower = true, | ||||||
|  |     bool includeUpper = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addWhereClause(IdWhereClause.between( | ||||||
|  |         lower: lowerIsarId, | ||||||
|  |         includeLower: includeLower, | ||||||
|  |         upper: upperIsarId, | ||||||
|  |         includeUpper: includeUpper, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension DuplicatedAssetQueryFilter | ||||||
|  |     on QueryBuilder<DuplicatedAsset, DuplicatedAsset, QFilterCondition> { | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       idEqualTo( | ||||||
|  |     String value, { | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.equalTo( | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       idGreaterThan( | ||||||
|  |     String value, { | ||||||
|  |     bool include = false, | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.greaterThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       idLessThan( | ||||||
|  |     String value, { | ||||||
|  |     bool include = false, | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.lessThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       idBetween( | ||||||
|  |     String lower, | ||||||
|  |     String upper, { | ||||||
|  |     bool includeLower = true, | ||||||
|  |     bool includeUpper = true, | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.between( | ||||||
|  |         property: r'id', | ||||||
|  |         lower: lower, | ||||||
|  |         includeLower: includeLower, | ||||||
|  |         upper: upper, | ||||||
|  |         includeUpper: includeUpper, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       idStartsWith( | ||||||
|  |     String value, { | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.startsWith( | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       idEndsWith( | ||||||
|  |     String value, { | ||||||
|  |     bool caseSensitive = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.endsWith( | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       idContains(String value, {bool caseSensitive = true}) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.contains( | ||||||
|  |         property: r'id', | ||||||
|  |         value: value, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       idMatches(String pattern, {bool caseSensitive = true}) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.matches( | ||||||
|  |         property: r'id', | ||||||
|  |         wildcard: pattern, | ||||||
|  |         caseSensitive: caseSensitive, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       idIsEmpty() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.equalTo( | ||||||
|  |         property: r'id', | ||||||
|  |         value: '', | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       idIsNotEmpty() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.greaterThan( | ||||||
|  |         property: r'id', | ||||||
|  |         value: '', | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       isarIdEqualTo(Id value) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.equalTo( | ||||||
|  |         property: r'isarId', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       isarIdGreaterThan( | ||||||
|  |     Id value, { | ||||||
|  |     bool include = false, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.greaterThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'isarId', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       isarIdLessThan( | ||||||
|  |     Id value, { | ||||||
|  |     bool include = false, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.lessThan( | ||||||
|  |         include: include, | ||||||
|  |         property: r'isarId', | ||||||
|  |         value: value, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterFilterCondition> | ||||||
|  |       isarIdBetween( | ||||||
|  |     Id lower, | ||||||
|  |     Id upper, { | ||||||
|  |     bool includeLower = true, | ||||||
|  |     bool includeUpper = true, | ||||||
|  |   }) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addFilterCondition(FilterCondition.between( | ||||||
|  |         property: r'isarId', | ||||||
|  |         lower: lower, | ||||||
|  |         includeLower: includeLower, | ||||||
|  |         upper: upper, | ||||||
|  |         includeUpper: includeUpper, | ||||||
|  |       )); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension DuplicatedAssetQueryObject | ||||||
|  |     on QueryBuilder<DuplicatedAsset, DuplicatedAsset, QFilterCondition> {} | ||||||
|  |  | ||||||
|  | extension DuplicatedAssetQueryLinks | ||||||
|  |     on QueryBuilder<DuplicatedAsset, DuplicatedAsset, QFilterCondition> {} | ||||||
|  |  | ||||||
|  | extension DuplicatedAssetQuerySortBy | ||||||
|  |     on QueryBuilder<DuplicatedAsset, DuplicatedAsset, QSortBy> { | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterSortBy> sortById() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'id', Sort.asc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterSortBy> sortByIdDesc() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'id', Sort.desc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension DuplicatedAssetQuerySortThenBy | ||||||
|  |     on QueryBuilder<DuplicatedAsset, DuplicatedAsset, QSortThenBy> { | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterSortBy> thenById() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'id', Sort.asc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterSortBy> thenByIdDesc() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'id', Sort.desc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterSortBy> thenByIsarId() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'isarId', Sort.asc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QAfterSortBy> | ||||||
|  |       thenByIsarIdDesc() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addSortBy(r'isarId', Sort.desc); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension DuplicatedAssetQueryWhereDistinct | ||||||
|  |     on QueryBuilder<DuplicatedAsset, DuplicatedAsset, QDistinct> { | ||||||
|  |   QueryBuilder<DuplicatedAsset, DuplicatedAsset, QDistinct> distinctById( | ||||||
|  |       {bool caseSensitive = true}) { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addDistinctBy(r'id', caseSensitive: caseSensitive); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
|  |  | ||||||
|  | extension DuplicatedAssetQueryProperty | ||||||
|  |     on QueryBuilder<DuplicatedAsset, DuplicatedAsset, QQueryProperty> { | ||||||
|  |   QueryBuilder<DuplicatedAsset, int, QQueryOperations> isarIdProperty() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addPropertyName(r'isarId'); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<DuplicatedAsset, String, QQueryOperations> idProperty() { | ||||||
|  |     return QueryBuilder.apply(this, (query) { | ||||||
|  |       return query.addPropertyName(r'id'); | ||||||
|  |     }); | ||||||
|  |   } | ||||||
|  | } | ||||||
| @@ -1,22 +1,26 @@ | |||||||
| import 'package:cancellation_token_http/http.dart'; | import 'package:cancellation_token_http/http.dart'; | ||||||
|  | import 'package:collection/collection.dart'; | ||||||
| import 'package:flutter/widgets.dart'; | import 'package:flutter/widgets.dart'; | ||||||
| import 'package:hive_flutter/hive_flutter.dart'; | import 'package:hive_flutter/hive_flutter.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:immich_mobile/constants/hive_box.dart'; | import 'package:immich_mobile/constants/hive_box.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/available_album.model.dart'; | import 'package:immich_mobile/modules/backup/models/available_album.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/backup_album.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; | import 'package:immich_mobile/modules/backup/models/backup_state.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart'; | import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart'; | import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; |  | ||||||
| import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart'; |  | ||||||
| import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart'; | import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; | import 'package:immich_mobile/modules/backup/background_service/background.service.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/services/backup.service.dart'; | import 'package:immich_mobile/modules/backup/services/backup.service.dart'; | ||||||
| import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; | import 'package:immich_mobile/modules/login/models/authentication_state.model.dart'; | ||||||
| import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | import 'package:immich_mobile/modules/login/providers/authentication.provider.dart'; | ||||||
| import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart'; | import 'package:immich_mobile/modules/onboarding/providers/gallery_permission.provider.dart'; | ||||||
|  | import 'package:immich_mobile/shared/models/store.dart'; | ||||||
| import 'package:immich_mobile/shared/providers/app_state.provider.dart'; | import 'package:immich_mobile/shared/providers/app_state.provider.dart'; | ||||||
|  | import 'package:immich_mobile/shared/providers/db.provider.dart'; | ||||||
| import 'package:immich_mobile/shared/services/server_info.service.dart'; | import 'package:immich_mobile/shared/services/server_info.service.dart'; | ||||||
|  | import 'package:immich_mobile/utils/diff.dart'; | ||||||
|  | import 'package:isar/isar.dart'; | ||||||
| import 'package:logging/logging.dart'; | import 'package:logging/logging.dart'; | ||||||
| import 'package:openapi/api.dart'; | import 'package:openapi/api.dart'; | ||||||
| import 'package:permission_handler/permission_handler.dart'; | import 'package:permission_handler/permission_handler.dart'; | ||||||
| @@ -29,6 +33,7 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|     this._authState, |     this._authState, | ||||||
|     this._backgroundService, |     this._backgroundService, | ||||||
|     this._galleryPermissionNotifier, |     this._galleryPermissionNotifier, | ||||||
|  |     this._db, | ||||||
|     this.ref, |     this.ref, | ||||||
|   ) : super( |   ) : super( | ||||||
|           BackUpState( |           BackUpState( | ||||||
| @@ -69,6 +74,7 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|   final AuthenticationState _authState; |   final AuthenticationState _authState; | ||||||
|   final BackgroundService _backgroundService; |   final BackgroundService _backgroundService; | ||||||
|   final GalleryPermissionNotifier _galleryPermissionNotifier; |   final GalleryPermissionNotifier _galleryPermissionNotifier; | ||||||
|  |   final Isar _db; | ||||||
|   final Ref ref; |   final Ref ref; | ||||||
|  |  | ||||||
|   /// |   /// | ||||||
| @@ -157,11 +163,13 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|             triggerMaxDelay: state.backupTriggerDelay * 10, |             triggerMaxDelay: state.backupTriggerDelay * 10, | ||||||
|           ); |           ); | ||||||
|       if (success) { |       if (success) { | ||||||
|         final box = Hive.box(backgroundBackupInfoBox); |  | ||||||
|         await Future.wait([ |         await Future.wait([ | ||||||
|           box.put(backupRequireWifi, state.backupRequireWifi), |           Store.put(StoreKey.backupRequireWifi, state.backupRequireWifi), | ||||||
|           box.put(backupRequireCharging, state.backupRequireCharging), |           Store.put( | ||||||
|           box.put(backupTriggerDelay, state.backupTriggerDelay), |             StoreKey.backupRequireCharging, | ||||||
|  |             state.backupRequireCharging, | ||||||
|  |           ), | ||||||
|  |           Store.put(StoreKey.backupTriggerDelay, state.backupTriggerDelay), | ||||||
|         ]); |         ]); | ||||||
|       } else { |       } else { | ||||||
|         state = state.copyWith( |         state = state.copyWith( | ||||||
| @@ -201,16 +209,16 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|     for (AssetPathEntity album in albums) { |     for (AssetPathEntity album in albums) { | ||||||
|       AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album); |       AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album); | ||||||
|  |  | ||||||
|       var assetCountInAlbum = await album.assetCountAsync; |       final assetCountInAlbum = await album.assetCountAsync; | ||||||
|       if (assetCountInAlbum > 0) { |       if (assetCountInAlbum > 0) { | ||||||
|         var assetList = |         final assetList = | ||||||
|             await album.getAssetListRange(start: 0, end: assetCountInAlbum); |             await album.getAssetListRange(start: 0, end: assetCountInAlbum); | ||||||
|  |  | ||||||
|         if (assetList.isNotEmpty) { |         if (assetList.isNotEmpty) { | ||||||
|           var thumbnailAsset = assetList.first; |           final thumbnailAsset = assetList.first; | ||||||
|  |  | ||||||
|           try { |           try { | ||||||
|             var thumbnailData = await thumbnailAsset |             final thumbnailData = await thumbnailAsset | ||||||
|                 .thumbnailDataWithSize(const ThumbnailSize(512, 512)); |                 .thumbnailDataWithSize(const ThumbnailSize(512, 512)); | ||||||
|             availableAlbum = |             availableAlbum = | ||||||
|                 availableAlbum.copyWith(thumbnailData: thumbnailData); |                 availableAlbum.copyWith(thumbnailData: thumbnailData); | ||||||
| @@ -229,34 +237,17 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|  |  | ||||||
|     state = state.copyWith(availableAlbums: availableAlbums); |     state = state.copyWith(availableAlbums: availableAlbums); | ||||||
|  |  | ||||||
|     // Put persistent storage info into local state of the app |     final List<BackupAlbum> excludedBackupAlbums = | ||||||
|     // Get local storage on selected backup album |         await _backupService.excludedAlbumsQuery().findAll(); | ||||||
|     Box<HiveBackupAlbums> backupAlbumInfoBox = |     final List<BackupAlbum> selectedBackupAlbums = | ||||||
|         Hive.box<HiveBackupAlbums>(hiveBackupInfoBox); |         await _backupService.selectedAlbumsQuery().findAll(); | ||||||
|     HiveBackupAlbums? backupAlbumInfo = backupAlbumInfoBox.get( |  | ||||||
|       backupInfoKey, |  | ||||||
|       defaultValue: HiveBackupAlbums( |  | ||||||
|         selectedAlbumIds: [], |  | ||||||
|         excludedAlbumsIds: [], |  | ||||||
|         lastSelectedBackupTime: [], |  | ||||||
|         lastExcludedBackupTime: [], |  | ||||||
|       ), |  | ||||||
|     ); |  | ||||||
|  |  | ||||||
|     if (backupAlbumInfo == null) { |  | ||||||
|       log.severe( |  | ||||||
|         "backupAlbumInfo == null", |  | ||||||
|         "Failed to get Hive backup album information", |  | ||||||
|       ); |  | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     // First time backup - set isAll album is the default one for backup. |     // First time backup - set isAll album is the default one for backup. | ||||||
|     if (backupAlbumInfo.selectedAlbumIds.isEmpty) { |     if (selectedBackupAlbums.isEmpty) { | ||||||
|       log.info("First time backup; setup 'Recent(s)' album as default"); |       log.info("First time backup; setup 'Recent(s)' album as default"); | ||||||
|  |  | ||||||
|       // Get album that contains all assets |       // Get album that contains all assets | ||||||
|       var list = await PhotoManager.getAssetPathList( |       final list = await PhotoManager.getAssetPathList( | ||||||
|         hasAll: true, |         hasAll: true, | ||||||
|         onlyAll: true, |         onlyAll: true, | ||||||
|         type: RequestType.common, |         type: RequestType.common, | ||||||
| @@ -267,48 +258,29 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|       } |       } | ||||||
|       AssetPathEntity albumHasAllAssets = list.first; |       AssetPathEntity albumHasAllAssets = list.first; | ||||||
|  |  | ||||||
|       backupAlbumInfoBox.put( |       final ba = BackupAlbum( | ||||||
|         backupInfoKey, |         albumHasAllAssets.id, | ||||||
|         HiveBackupAlbums( |         DateTime.fromMillisecondsSinceEpoch(0), | ||||||
|           selectedAlbumIds: [albumHasAllAssets.id], |         BackupSelection.select, | ||||||
|           excludedAlbumsIds: [], |  | ||||||
|           lastSelectedBackupTime: [ |  | ||||||
|             DateTime.fromMillisecondsSinceEpoch(0, isUtc: true) |  | ||||||
|           ], |  | ||||||
|           lastExcludedBackupTime: [], |  | ||||||
|         ), |  | ||||||
|       ); |       ); | ||||||
|  |       await _db.writeTxn(() => _db.backupAlbums.put(ba)); | ||||||
|       backupAlbumInfo = backupAlbumInfoBox.get(backupInfoKey); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Generate AssetPathEntity from id to add to local state |     // Generate AssetPathEntity from id to add to local state | ||||||
|     try { |     try { | ||||||
|       Set<AvailableAlbum> selectedAlbums = {}; |       final Set<AvailableAlbum> selectedAlbums = {}; | ||||||
|       for (var i = 0; i < backupAlbumInfo!.selectedAlbumIds.length; i++) { |       for (final BackupAlbum ba in selectedBackupAlbums) { | ||||||
|         var albumAsset = |         final albumAsset = await AssetPathEntity.fromId(ba.id); | ||||||
|             await AssetPathEntity.fromId(backupAlbumInfo.selectedAlbumIds[i]); |  | ||||||
|         selectedAlbums.add( |         selectedAlbums.add( | ||||||
|           AvailableAlbum( |           AvailableAlbum(albumEntity: albumAsset, lastBackup: ba.lastBackup), | ||||||
|             albumEntity: albumAsset, |  | ||||||
|             lastBackup: backupAlbumInfo.lastSelectedBackupTime.length > i |  | ||||||
|                 ? backupAlbumInfo.lastSelectedBackupTime[i] |  | ||||||
|                 : DateTime.fromMillisecondsSinceEpoch(0, isUtc: true), |  | ||||||
|           ), |  | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|  |  | ||||||
|       Set<AvailableAlbum> excludedAlbums = {}; |       final Set<AvailableAlbum> excludedAlbums = {}; | ||||||
|       for (var i = 0; i < backupAlbumInfo.excludedAlbumsIds.length; i++) { |       for (final BackupAlbum ba in excludedBackupAlbums) { | ||||||
|         var albumAsset = |         final albumAsset = await AssetPathEntity.fromId(ba.id); | ||||||
|             await AssetPathEntity.fromId(backupAlbumInfo.excludedAlbumsIds[i]); |  | ||||||
|         excludedAlbums.add( |         excludedAlbums.add( | ||||||
|           AvailableAlbum( |           AvailableAlbum(albumEntity: albumAsset, lastBackup: ba.lastBackup), | ||||||
|             albumEntity: albumAsset, |  | ||||||
|             lastBackup: backupAlbumInfo.lastExcludedBackupTime.length > i |  | ||||||
|                 ? backupAlbumInfo.lastExcludedBackupTime[i] |  | ||||||
|                 : DateTime.fromMillisecondsSinceEpoch(0, isUtc: true), |  | ||||||
|           ), |  | ||||||
|         ); |         ); | ||||||
|       } |       } | ||||||
|       state = state.copyWith( |       state = state.copyWith( | ||||||
| @@ -328,36 +300,36 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|   /// Those assets are unique and are used as the total assets |   /// Those assets are unique and are used as the total assets | ||||||
|   /// |   /// | ||||||
|   Future<void> _updateBackupAssetCount() async { |   Future<void> _updateBackupAssetCount() async { | ||||||
|     Set<String> duplicatedAssetIds = _backupService.getDuplicatedAssetIds(); |     final duplicatedAssetIds = await _backupService.getDuplicatedAssetIds(); | ||||||
|     Set<AssetEntity> assetsFromSelectedAlbums = {}; |     final Set<AssetEntity> assetsFromSelectedAlbums = {}; | ||||||
|     Set<AssetEntity> assetsFromExcludedAlbums = {}; |     final Set<AssetEntity> assetsFromExcludedAlbums = {}; | ||||||
|  |  | ||||||
|     for (var album in state.selectedBackupAlbums) { |     for (final album in state.selectedBackupAlbums) { | ||||||
|       var assets = await album.albumEntity.getAssetListRange( |       final assets = await album.albumEntity.getAssetListRange( | ||||||
|         start: 0, |         start: 0, | ||||||
|         end: await album.albumEntity.assetCountAsync, |         end: await album.albumEntity.assetCountAsync, | ||||||
|       ); |       ); | ||||||
|       assetsFromSelectedAlbums.addAll(assets); |       assetsFromSelectedAlbums.addAll(assets); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     for (var album in state.excludedBackupAlbums) { |     for (final album in state.excludedBackupAlbums) { | ||||||
|       var assets = await album.albumEntity.getAssetListRange( |       final assets = await album.albumEntity.getAssetListRange( | ||||||
|         start: 0, |         start: 0, | ||||||
|         end: await album.albumEntity.assetCountAsync, |         end: await album.albumEntity.assetCountAsync, | ||||||
|       ); |       ); | ||||||
|       assetsFromExcludedAlbums.addAll(assets); |       assetsFromExcludedAlbums.addAll(assets); | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     Set<AssetEntity> allUniqueAssets = |     final Set<AssetEntity> allUniqueAssets = | ||||||
|         assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums); |         assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums); | ||||||
|     var allAssetsInDatabase = await _backupService.getDeviceBackupAsset(); |     final allAssetsInDatabase = await _backupService.getDeviceBackupAsset(); | ||||||
|  |  | ||||||
|     if (allAssetsInDatabase == null) { |     if (allAssetsInDatabase == null) { | ||||||
|       return; |       return; | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Find asset that were backup from selected albums |     // Find asset that were backup from selected albums | ||||||
|     Set<String> selectedAlbumsBackupAssets = |     final Set<String> selectedAlbumsBackupAssets = | ||||||
|         Set.from(allUniqueAssets.map((e) => e.id)); |         Set.from(allUniqueAssets.map((e) => e.id)); | ||||||
|  |  | ||||||
|     selectedAlbumsBackupAssets |     selectedAlbumsBackupAssets | ||||||
| @@ -386,7 +358,7 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     // Save to persistent storage |     // Save to persistent storage | ||||||
|     _updatePersistentAlbumsSelection(); |     await _updatePersistentAlbumsSelection(); | ||||||
|  |  | ||||||
|     return; |     return; | ||||||
|   } |   } | ||||||
| @@ -395,7 +367,7 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|   /// which albums are selected or excluded |   /// which albums are selected or excluded | ||||||
|   /// and then update the UI according to those information |   /// and then update the UI according to those information | ||||||
|   Future<void> getBackupInfo() async { |   Future<void> getBackupInfo() async { | ||||||
|     var isEnabled = await _backgroundService.isBackgroundBackupEnabled(); |     final isEnabled = await _backgroundService.isBackgroundBackupEnabled(); | ||||||
|  |  | ||||||
|     state = state.copyWith(backgroundBackup: isEnabled); |     state = state.copyWith(backgroundBackup: isEnabled); | ||||||
|  |  | ||||||
| @@ -406,25 +378,38 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /// Save user selection of selected albums and excluded albums to |   /// Save user selection of selected albums and excluded albums to database | ||||||
|   /// Hive database |   Future<void> _updatePersistentAlbumsSelection() { | ||||||
|   void _updatePersistentAlbumsSelection() { |  | ||||||
|     final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); |     final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true); | ||||||
|     Box<HiveBackupAlbums> backupAlbumInfoBox = |     final selected = state.selectedBackupAlbums.map( | ||||||
|         Hive.box<HiveBackupAlbums>(hiveBackupInfoBox); |       (e) => BackupAlbum(e.id, e.lastBackup ?? epoch, BackupSelection.select), | ||||||
|     backupAlbumInfoBox.put( |  | ||||||
|       backupInfoKey, |  | ||||||
|       HiveBackupAlbums( |  | ||||||
|         selectedAlbumIds: state.selectedBackupAlbums.map((e) => e.id).toList(), |  | ||||||
|         excludedAlbumsIds: state.excludedBackupAlbums.map((e) => e.id).toList(), |  | ||||||
|         lastSelectedBackupTime: state.selectedBackupAlbums |  | ||||||
|             .map((e) => e.lastBackup ?? epoch) |  | ||||||
|             .toList(), |  | ||||||
|         lastExcludedBackupTime: state.excludedBackupAlbums |  | ||||||
|             .map((e) => e.lastBackup ?? epoch) |  | ||||||
|             .toList(), |  | ||||||
|       ), |  | ||||||
|     ); |     ); | ||||||
|  |     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); | ||||||
|  |     }); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /// Invoke backup process |   /// Invoke backup process | ||||||
| @@ -447,7 +432,7 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|  |  | ||||||
|       Set<AssetEntity> assetsWillBeBackup = Set.from(state.allUniqueAssets); |       Set<AssetEntity> assetsWillBeBackup = Set.from(state.allUniqueAssets); | ||||||
|       // Remove item that has already been backed up |       // Remove item that has already been backed up | ||||||
|       for (var assetId in state.allAssetsInDatabase) { |       for (final assetId in state.allAssetsInDatabase) { | ||||||
|         assetsWillBeBackup.removeWhere((e) => e.id == assetId); |         assetsWillBeBackup.removeWhere((e) => e.id == assetId); | ||||||
|       } |       } | ||||||
|  |  | ||||||
| @@ -547,7 +532,7 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> _updateServerInfo() async { |   Future<void> _updateServerInfo() async { | ||||||
|     var serverInfo = await _serverInfoService.getServerInfo(); |     final serverInfo = await _serverInfoService.getServerInfo(); | ||||||
|  |  | ||||||
|     // Update server info |     // Update server info | ||||||
|     if (serverInfo != null) { |     if (serverInfo != null) { | ||||||
| @@ -559,7 +544,7 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|  |  | ||||||
|   Future<void> _resumeBackup() async { |   Future<void> _resumeBackup() async { | ||||||
|     // Check if user is login |     // Check if user is login | ||||||
|     var accessKey = Hive.box(userInfoBox).get(accessTokenKey); |     final accessKey = Hive.box(userInfoBox).get(accessTokenKey); | ||||||
|  |  | ||||||
|     // User has been logged out return |     // User has been logged out return | ||||||
|     if (accessKey == null || !_authState.isAuthenticated) { |     if (accessKey == null || !_authState.isAuthenticated) { | ||||||
| @@ -590,65 +575,56 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<void> resumeBackup() async { |   Future<void> resumeBackup() async { | ||||||
|     // assumes the background service is currently running |     final List<BackupAlbum> selectedBackupAlbums = await _db.backupAlbums | ||||||
|     // if true, waits until it has stopped to update the app state from HiveDB |         .filter() | ||||||
|     // before actually resuming backup by calling the internal `_resumeBackup` |         .selectionEqualTo(BackupSelection.select) | ||||||
|     final BackUpProgressEnum previous = state.backupProgress; |         .findAll(); | ||||||
|     state = state.copyWith(backupProgress: BackUpProgressEnum.inBackground); |     final List<BackupAlbum> excludedBackupAlbums = await _db.backupAlbums | ||||||
|     final bool hasLock = await _backgroundService.acquireLock(); |         .filter() | ||||||
|     if (!hasLock) { |         .selectionEqualTo(BackupSelection.select) | ||||||
|       log.warning("WARNING [resumeBackup] failed to acquireLock"); |         .findAll(); | ||||||
|       return; |  | ||||||
|     } |  | ||||||
|  |  | ||||||
|     await Future.wait([ |  | ||||||
|       Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox), |  | ||||||
|       Hive.openBox<HiveDuplicatedAssets>(duplicatedAssetsBox), |  | ||||||
|       Hive.openBox(backgroundBackupInfoBox), |  | ||||||
|     ]); |  | ||||||
|     final HiveBackupAlbums? albums = |  | ||||||
|         Hive.box<HiveBackupAlbums>(hiveBackupInfoBox).get(backupInfoKey); |  | ||||||
|     Set<AvailableAlbum> selectedAlbums = state.selectedBackupAlbums; |     Set<AvailableAlbum> selectedAlbums = state.selectedBackupAlbums; | ||||||
|     Set<AvailableAlbum> excludedAlbums = state.excludedBackupAlbums; |     Set<AvailableAlbum> excludedAlbums = state.excludedBackupAlbums; | ||||||
|     if (albums != null) { |     if (selectedAlbums.isNotEmpty) { | ||||||
|       if (selectedAlbums.isNotEmpty) { |       selectedAlbums = _updateAlbumsBackupTime( | ||||||
|         selectedAlbums = _updateAlbumsBackupTime( |         selectedAlbums, | ||||||
|           selectedAlbums, |         selectedBackupAlbums, | ||||||
|           albums.selectedAlbumIds, |       ); | ||||||
|           albums.lastSelectedBackupTime, |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|  |  | ||||||
|       if (excludedAlbums.isNotEmpty) { |  | ||||||
|         excludedAlbums = _updateAlbumsBackupTime( |  | ||||||
|           excludedAlbums, |  | ||||||
|           albums.excludedAlbumsIds, |  | ||||||
|           albums.lastExcludedBackupTime, |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|     } |     } | ||||||
|     final Box backgroundBox = Hive.box(backgroundBackupInfoBox); |  | ||||||
|  |     if (excludedAlbums.isNotEmpty) { | ||||||
|  |       excludedAlbums = _updateAlbumsBackupTime( | ||||||
|  |         excludedAlbums, | ||||||
|  |         excludedBackupAlbums, | ||||||
|  |       ); | ||||||
|  |     } | ||||||
|  |     final BackUpProgressEnum previous = state.backupProgress; | ||||||
|     state = state.copyWith( |     state = state.copyWith( | ||||||
|       backupProgress: previous, |       backupProgress: BackUpProgressEnum.inBackground, | ||||||
|       selectedBackupAlbums: selectedAlbums, |       selectedBackupAlbums: selectedAlbums, | ||||||
|       excludedBackupAlbums: excludedAlbums, |       excludedBackupAlbums: excludedAlbums, | ||||||
|       backupRequireWifi: backgroundBox.get(backupRequireWifi), |       backupRequireWifi: Store.get(StoreKey.backupRequireWifi), | ||||||
|       backupRequireCharging: backgroundBox.get(backupRequireCharging), |       backupRequireCharging: Store.get(StoreKey.backupRequireCharging), | ||||||
|       backupTriggerDelay: backgroundBox.get(backupTriggerDelay), |       backupTriggerDelay: Store.get(StoreKey.backupTriggerDelay), | ||||||
|     ); |     ); | ||||||
|  |     // assumes the background service is currently running | ||||||
|  |     // if true, waits until it has stopped to start the backup | ||||||
|  |     final bool hasLock = await _backgroundService.acquireLock(); | ||||||
|  |     if (hasLock) { | ||||||
|  |       state = state.copyWith(backupProgress: previous); | ||||||
|  |     } | ||||||
|     return _resumeBackup(); |     return _resumeBackup(); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Set<AvailableAlbum> _updateAlbumsBackupTime( |   Set<AvailableAlbum> _updateAlbumsBackupTime( | ||||||
|     Set<AvailableAlbum> albums, |     Set<AvailableAlbum> albums, | ||||||
|     List<String> ids, |     List<BackupAlbum> backupAlbums, | ||||||
|     List<DateTime> times, |  | ||||||
|   ) { |   ) { | ||||||
|     Set<AvailableAlbum> result = {}; |     Set<AvailableAlbum> result = {}; | ||||||
|     for (int i = 0; i < ids.length; i++) { |     for (BackupAlbum ba in backupAlbums) { | ||||||
|       try { |       try { | ||||||
|         AvailableAlbum a = albums.firstWhere((e) => e.id == ids[i]); |         AvailableAlbum a = albums.firstWhere((e) => e.id == ba.id); | ||||||
|         result.add(a.copyWith(lastBackup: times[i])); |         result.add(a.copyWith(lastBackup: ba.lastBackup)); | ||||||
|       } on StateError { |       } on StateError { | ||||||
|         log.severe( |         log.severe( | ||||||
|           "[_updateAlbumBackupTime] failed to find album in state", |           "[_updateAlbumBackupTime] failed to find album in state", | ||||||
| @@ -667,35 +643,6 @@ class BackupNotifier extends StateNotifier<BackUpState> { | |||||||
|       AppStateEnum.detached, |       AppStateEnum.detached, | ||||||
|     ]; |     ]; | ||||||
|     if (allowedStates.contains(ref.read(appStateProvider.notifier).state)) { |     if (allowedStates.contains(ref.read(appStateProvider.notifier).state)) { | ||||||
|       try { |  | ||||||
|         if (Hive.isBoxOpen(hiveBackupInfoBox)) { |  | ||||||
|           await Hive.box<HiveBackupAlbums>(hiveBackupInfoBox).close(); |  | ||||||
|         } |  | ||||||
|       } catch (error) { |  | ||||||
|         log.info("[_notifyBackgroundServiceCanRun] failed to close box"); |  | ||||||
|       } |  | ||||||
|       try { |  | ||||||
|         if (Hive.isBoxOpen(duplicatedAssetsBox)) { |  | ||||||
|           await Hive.box<HiveDuplicatedAssets>(duplicatedAssetsBox).close(); |  | ||||||
|         } |  | ||||||
|       } catch (error, stackTrace) { |  | ||||||
|         log.severe( |  | ||||||
|           "[_notifyBackgroundServiceCanRun] failed to close box", |  | ||||||
|           error, |  | ||||||
|           stackTrace, |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|       try { |  | ||||||
|         if (Hive.isBoxOpen(backgroundBackupInfoBox)) { |  | ||||||
|           await Hive.box(backgroundBackupInfoBox).close(); |  | ||||||
|         } |  | ||||||
|       } catch (error, stackTrace) { |  | ||||||
|         log.severe( |  | ||||||
|           "[_notifyBackgroundServiceCanRun] failed to close box", |  | ||||||
|           error, |  | ||||||
|           stackTrace, |  | ||||||
|         ); |  | ||||||
|       } |  | ||||||
|       _backgroundService.releaseLock(); |       _backgroundService.releaseLock(); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| @@ -709,6 +656,7 @@ final backupProvider = | |||||||
|     ref.watch(authenticationProvider), |     ref.watch(authenticationProvider), | ||||||
|     ref.watch(backgroundServiceProvider), |     ref.watch(backgroundServiceProvider), | ||||||
|     ref.watch(galleryPermissionNotifier.notifier), |     ref.watch(galleryPermissionNotifier.notifier), | ||||||
|  |     ref.watch(dbProvider), | ||||||
|     ref, |     ref, | ||||||
|   ); |   ); | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -8,31 +8,34 @@ import 'package:flutter/material.dart'; | |||||||
| import 'package:hive/hive.dart'; | import 'package:hive/hive.dart'; | ||||||
| import 'package:hooks_riverpod/hooks_riverpod.dart'; | import 'package:hooks_riverpod/hooks_riverpod.dart'; | ||||||
| import 'package:immich_mobile/constants/hive_box.dart'; | import 'package:immich_mobile/constants/hive_box.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/backup_album.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart'; | import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart'; | import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart'; | ||||||
| import 'package:immich_mobile/shared/providers/api.provider.dart'; | import 'package:immich_mobile/shared/providers/api.provider.dart'; | ||||||
| import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; | import 'package:immich_mobile/shared/providers/db.provider.dart'; | ||||||
| import 'package:immich_mobile/shared/services/api.service.dart'; | import 'package:immich_mobile/shared/services/api.service.dart'; | ||||||
| import 'package:immich_mobile/utils/files_helper.dart'; | import 'package:immich_mobile/utils/files_helper.dart'; | ||||||
|  | import 'package:isar/isar.dart'; | ||||||
| import 'package:openapi/api.dart'; | import 'package:openapi/api.dart'; | ||||||
| import 'package:photo_manager/photo_manager.dart'; | import 'package:photo_manager/photo_manager.dart'; | ||||||
| import 'package:http_parser/http_parser.dart'; | import 'package:http_parser/http_parser.dart'; | ||||||
| import 'package:path/path.dart' as p; | import 'package:path/path.dart' as p; | ||||||
| import 'package:cancellation_token_http/http.dart' as http; | import 'package:cancellation_token_http/http.dart' as http; | ||||||
|  |  | ||||||
| import '../models/hive_duplicated_assets.model.dart'; |  | ||||||
|  |  | ||||||
| final backupServiceProvider = Provider( | final backupServiceProvider = Provider( | ||||||
|   (ref) => BackupService( |   (ref) => BackupService( | ||||||
|     ref.watch(apiServiceProvider), |     ref.watch(apiServiceProvider), | ||||||
|  |     ref.watch(dbProvider), | ||||||
|   ), |   ), | ||||||
| ); | ); | ||||||
|  |  | ||||||
| class BackupService { | class BackupService { | ||||||
|   final httpClient = http.Client(); |   final httpClient = http.Client(); | ||||||
|   final ApiService _apiService; |   final ApiService _apiService; | ||||||
|  |   final Isar _db; | ||||||
|  |  | ||||||
|   BackupService(this._apiService); |   BackupService(this._apiService, this._db); | ||||||
|  |  | ||||||
|   Future<List<String>?> getDeviceBackupAsset() async { |   Future<List<String>?> getDeviceBackupAsset() async { | ||||||
|     String deviceId = Hive.box(userInfoBox).get(deviceIdKey); |     String deviceId = Hive.box(userInfoBox).get(deviceIdKey); | ||||||
| @@ -45,32 +48,28 @@ class BackupService { | |||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   void _saveDuplicatedAssetIdToLocalStorage(List<String> deviceAssetIds) { |   Future<void> _saveDuplicatedAssetIds(List<String> deviceAssetIds) { | ||||||
|     HiveDuplicatedAssets duplicatedAssets = |     final duplicates = deviceAssetIds.map((id) => DuplicatedAsset(id)).toList(); | ||||||
|         Hive.box<HiveDuplicatedAssets>(duplicatedAssetsBox) |     return _db.writeTxn(() => _db.duplicatedAssets.putAll(duplicates)); | ||||||
|                 .get(duplicatedAssetsKey) ?? |  | ||||||
|             HiveDuplicatedAssets(duplicatedAssetIds: []); |  | ||||||
|  |  | ||||||
|     duplicatedAssets.duplicatedAssetIds = |  | ||||||
|         {...duplicatedAssets.duplicatedAssetIds, ...deviceAssetIds}.toList(); |  | ||||||
|  |  | ||||||
|     Hive.box<HiveDuplicatedAssets>(duplicatedAssetsBox) |  | ||||||
|         .put(duplicatedAssetsKey, duplicatedAssets); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   /// Get duplicated asset id from Hive storage |   /// Get duplicated asset id from database | ||||||
|   Set<String> getDuplicatedAssetIds() { |   Future<Set<String>> getDuplicatedAssetIds() async { | ||||||
|     HiveDuplicatedAssets duplicatedAssets = |     final duplicates = await _db.duplicatedAssets.where().findAll(); | ||||||
|         Hive.box<HiveDuplicatedAssets>(duplicatedAssetsBox) |     return duplicates.map((e) => e.id).toSet(); | ||||||
|                 .get(duplicatedAssetsKey) ?? |  | ||||||
|             HiveDuplicatedAssets(duplicatedAssetIds: []); |  | ||||||
|  |  | ||||||
|     return duplicatedAssets.duplicatedAssetIds.toSet(); |  | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> | ||||||
|  |       selectedAlbumsQuery() => | ||||||
|  |           _db.backupAlbums.filter().selectionEqualTo(BackupSelection.select); | ||||||
|  |   QueryBuilder<BackupAlbum, BackupAlbum, QAfterFilterCondition> | ||||||
|  |       excludedAlbumsQuery() => | ||||||
|  |           _db.backupAlbums.filter().selectionEqualTo(BackupSelection.exclude); | ||||||
|  |  | ||||||
|   /// Returns all assets newer than the last successful backup per album |   /// Returns all assets newer than the last successful backup per album | ||||||
|   Future<List<AssetEntity>> buildUploadCandidates( |   Future<List<AssetEntity>> buildUploadCandidates( | ||||||
|     HiveBackupAlbums backupAlbums, |     List<BackupAlbum> selectedBackupAlbums, | ||||||
|  |     List<BackupAlbum> excludedBackupAlbums, | ||||||
|   ) async { |   ) async { | ||||||
|     final filter = FilterOptionGroup( |     final filter = FilterOptionGroup( | ||||||
|       containsPathModified: true, |       containsPathModified: true, | ||||||
| @@ -81,66 +80,55 @@ class BackupService { | |||||||
|     ); |     ); | ||||||
|     final now = DateTime.now(); |     final now = DateTime.now(); | ||||||
|     final List<AssetPathEntity?> selectedAlbums = |     final List<AssetPathEntity?> selectedAlbums = | ||||||
|         await _loadAlbumsWithTimeFilter( |         await _loadAlbumsWithTimeFilter(selectedBackupAlbums, filter, now); | ||||||
|       backupAlbums.selectedAlbumIds, |  | ||||||
|       backupAlbums.lastSelectedBackupTime, |  | ||||||
|       filter, |  | ||||||
|       now, |  | ||||||
|     ); |  | ||||||
|     if (selectedAlbums.every((e) => e == null)) { |     if (selectedAlbums.every((e) => e == null)) { | ||||||
|       return []; |       return []; | ||||||
|     } |     } | ||||||
|     final int allIdx = selectedAlbums.indexWhere((e) => e != null && e.isAll); |     final int allIdx = selectedAlbums.indexWhere((e) => e != null && e.isAll); | ||||||
|     if (allIdx != -1) { |     if (allIdx != -1) { | ||||||
|       final List<AssetPathEntity?> excludedAlbums = |       final List<AssetPathEntity?> excludedAlbums = | ||||||
|           await _loadAlbumsWithTimeFilter( |           await _loadAlbumsWithTimeFilter(excludedBackupAlbums, filter, now); | ||||||
|         backupAlbums.excludedAlbumsIds, |  | ||||||
|         backupAlbums.lastExcludedBackupTime, |  | ||||||
|         filter, |  | ||||||
|         now, |  | ||||||
|       ); |  | ||||||
|       final List<AssetEntity> toAdd = await _fetchAssetsAndUpdateLastBackup( |       final List<AssetEntity> toAdd = await _fetchAssetsAndUpdateLastBackup( | ||||||
|         selectedAlbums.slice(allIdx, allIdx + 1), |         selectedAlbums.slice(allIdx, allIdx + 1), | ||||||
|         backupAlbums.lastSelectedBackupTime.slice(allIdx, allIdx + 1), |         selectedBackupAlbums.slice(allIdx, allIdx + 1), | ||||||
|         now, |         now, | ||||||
|       ); |       ); | ||||||
|       final List<AssetEntity> toRemove = await _fetchAssetsAndUpdateLastBackup( |       final List<AssetEntity> toRemove = await _fetchAssetsAndUpdateLastBackup( | ||||||
|         excludedAlbums, |         excludedAlbums, | ||||||
|         backupAlbums.lastExcludedBackupTime, |         excludedBackupAlbums, | ||||||
|         now, |         now, | ||||||
|       ); |       ); | ||||||
|       return toAdd.toSet().difference(toRemove.toSet()).toList(); |       return toAdd.toSet().difference(toRemove.toSet()).toList(); | ||||||
|     } else { |     } else { | ||||||
|       return await _fetchAssetsAndUpdateLastBackup( |       return await _fetchAssetsAndUpdateLastBackup( | ||||||
|         selectedAlbums, |         selectedAlbums, | ||||||
|         backupAlbums.lastSelectedBackupTime, |         selectedBackupAlbums, | ||||||
|         now, |         now, | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   Future<List<AssetPathEntity?>> _loadAlbumsWithTimeFilter( |   Future<List<AssetPathEntity?>> _loadAlbumsWithTimeFilter( | ||||||
|     List<String> albumIds, |     List<BackupAlbum> albums, | ||||||
|     List<DateTime> lastBackups, |  | ||||||
|     FilterOptionGroup filter, |     FilterOptionGroup filter, | ||||||
|     DateTime now, |     DateTime now, | ||||||
|   ) async { |   ) async { | ||||||
|     List<AssetPathEntity?> result = List.filled(albumIds.length, null); |     List<AssetPathEntity?> result = []; | ||||||
|     for (int i = 0; i < albumIds.length; i++) { |     for (BackupAlbum a in albums) { | ||||||
|       try { |       try { | ||||||
|         final AssetPathEntity album = |         final AssetPathEntity album = | ||||||
|             await AssetPathEntity.obtainPathFromProperties( |             await AssetPathEntity.obtainPathFromProperties( | ||||||
|           id: albumIds[i], |           id: a.id, | ||||||
|           optionGroup: filter.copyWith( |           optionGroup: filter.copyWith( | ||||||
|             updateTimeCond: DateTimeCond( |             updateTimeCond: DateTimeCond( | ||||||
|               // subtract 2 seconds to prevent missing assets due to rounding issues |               // subtract 2 seconds to prevent missing assets due to rounding issues | ||||||
|               min: lastBackups[i].subtract(const Duration(seconds: 2)), |               min: a.lastBackup.subtract(const Duration(seconds: 2)), | ||||||
|               max: now, |               max: now, | ||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           maxDateTimeToNow: false, |           maxDateTimeToNow: false, | ||||||
|         ); |         ); | ||||||
|         result[i] = album; |         result.add(album); | ||||||
|       } on StateError { |       } on StateError { | ||||||
|         // either there are no assets matching the filter criteria OR the album no longer exists |         // either there are no assets matching the filter criteria OR the album no longer exists | ||||||
|       } |       } | ||||||
| @@ -150,17 +138,18 @@ class BackupService { | |||||||
|  |  | ||||||
|   Future<List<AssetEntity>> _fetchAssetsAndUpdateLastBackup( |   Future<List<AssetEntity>> _fetchAssetsAndUpdateLastBackup( | ||||||
|     List<AssetPathEntity?> albums, |     List<AssetPathEntity?> albums, | ||||||
|     List<DateTime> lastBackup, |     List<BackupAlbum> backupAlbums, | ||||||
|     DateTime now, |     DateTime now, | ||||||
|   ) async { |   ) async { | ||||||
|     List<AssetEntity> result = []; |     List<AssetEntity> result = []; | ||||||
|     for (int i = 0; i < albums.length; i++) { |     for (int i = 0; i < albums.length; i++) { | ||||||
|       final AssetPathEntity? a = albums[i]; |       final AssetPathEntity? a = albums[i]; | ||||||
|       if (a != null && a.lastModified?.isBefore(lastBackup[i]) != true) { |       if (a != null && | ||||||
|  |           a.lastModified?.isBefore(backupAlbums[i].lastBackup) != true) { | ||||||
|         result.addAll( |         result.addAll( | ||||||
|           await a.getAssetListRange(start: 0, end: await a.assetCountAsync), |           await a.getAssetListRange(start: 0, end: await a.assetCountAsync), | ||||||
|         ); |         ); | ||||||
|         lastBackup[i] = now; |         backupAlbums[i].lastBackup = now; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     return result; |     return result; | ||||||
| @@ -173,7 +162,7 @@ class BackupService { | |||||||
|     if (candidates.isEmpty) { |     if (candidates.isEmpty) { | ||||||
|       return candidates; |       return candidates; | ||||||
|     } |     } | ||||||
|     final Set<String> duplicatedAssetIds = getDuplicatedAssetIds(); |     final Set<String> duplicatedAssetIds = await getDuplicatedAssetIds(); | ||||||
|     candidates = duplicatedAssetIds.isEmpty |     candidates = duplicatedAssetIds.isEmpty | ||||||
|         ? candidates |         ? candidates | ||||||
|         : candidates |         : candidates | ||||||
| @@ -261,7 +250,8 @@ class BackupService { | |||||||
|           req.fields['deviceId'] = deviceId; |           req.fields['deviceId'] = deviceId; | ||||||
|           req.fields['assetType'] = _getAssetType(entity.type); |           req.fields['assetType'] = _getAssetType(entity.type); | ||||||
|           req.fields['fileCreatedAt'] = entity.createDateTime.toIso8601String(); |           req.fields['fileCreatedAt'] = entity.createDateTime.toIso8601String(); | ||||||
|           req.fields['fileModifiedAt'] = entity.modifiedDateTime.toIso8601String(); |           req.fields['fileModifiedAt'] = | ||||||
|  |               entity.modifiedDateTime.toIso8601String(); | ||||||
|           req.fields['isFavorite'] = entity.isFavorite.toString(); |           req.fields['isFavorite'] = entity.isFavorite.toString(); | ||||||
|           req.fields['fileExtension'] = fileExtension; |           req.fields['fileExtension'] = fileExtension; | ||||||
|           req.fields['duration'] = entity.videoDuration.toString(); |           req.fields['duration'] = entity.videoDuration.toString(); | ||||||
| @@ -332,7 +322,7 @@ class BackupService { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|     if (duplicatedAssetIds.isNotEmpty) { |     if (duplicatedAssetIds.isNotEmpty) { | ||||||
|       _saveDuplicatedAssetIdToLocalStorage(duplicatedAssetIds); |       await _saveDuplicatedAssetIds(duplicatedAssetIds); | ||||||
|     } |     } | ||||||
|     return !anyErrors; |     return !anyErrors; | ||||||
|   } |   } | ||||||
|   | |||||||
| @@ -29,8 +29,8 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|     AuthenticationState authenticationState = ref.watch(authenticationProvider); |     AuthenticationState authenticationState = ref.watch(authenticationProvider); | ||||||
|     final settings = ref.watch(iOSBackgroundSettingsProvider.notifier).settings; |     final settings = ref.watch(iOSBackgroundSettingsProvider.notifier).settings; | ||||||
|  |  | ||||||
|     final appRefreshDisabled = Platform.isIOS && |     final appRefreshDisabled = | ||||||
|       settings?.appRefreshEnabled != true; |         Platform.isIOS && settings?.appRefreshEnabled != true; | ||||||
|     bool hasExclusiveAccess = |     bool hasExclusiveAccess = | ||||||
|         backupState.backupProgress != BackUpProgressEnum.inBackground; |         backupState.backupProgress != BackUpProgressEnum.inBackground; | ||||||
|     bool shouldBackup = backupState.allUniqueAssets.length - |     bool shouldBackup = backupState.allUniqueAssets.length - | ||||||
| @@ -292,15 +292,13 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|                     dense: true, |                     dense: true, | ||||||
|                     activeColor: activeColor, |                     activeColor: activeColor, | ||||||
|                     value: isWifiRequired, |                     value: isWifiRequired, | ||||||
|                     onChanged: hasExclusiveAccess |                     onChanged: (isChecked) => ref | ||||||
|                         ? (isChecked) => ref |                         .read(backupProvider.notifier) | ||||||
|                             .read(backupProvider.notifier) |                         .configureBackgroundBackup( | ||||||
|                             .configureBackgroundBackup( |                           requireWifi: isChecked, | ||||||
|                               requireWifi: isChecked, |                           onError: showErrorToUser, | ||||||
|                               onError: showErrorToUser, |                           onBatteryInfo: showBatteryOptimizationInfoToUser, | ||||||
|                               onBatteryInfo: showBatteryOptimizationInfoToUser, |                         ), | ||||||
|                             ) |  | ||||||
|                         : null, |  | ||||||
|                   ), |                   ), | ||||||
|                 if (isBackgroundEnabled) |                 if (isBackgroundEnabled) | ||||||
|                   SwitchListTile.adaptive( |                   SwitchListTile.adaptive( | ||||||
| @@ -314,21 +312,18 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|                     dense: true, |                     dense: true, | ||||||
|                     activeColor: activeColor, |                     activeColor: activeColor, | ||||||
|                     value: isChargingRequired, |                     value: isChargingRequired, | ||||||
|                     onChanged: hasExclusiveAccess |                     onChanged: (isChecked) => ref | ||||||
|                         ? (isChecked) => ref |                         .read(backupProvider.notifier) | ||||||
|                             .read(backupProvider.notifier) |                         .configureBackgroundBackup( | ||||||
|                             .configureBackgroundBackup( |                           requireCharging: isChecked, | ||||||
|                               requireCharging: isChecked, |                           onError: showErrorToUser, | ||||||
|                               onError: showErrorToUser, |                           onBatteryInfo: showBatteryOptimizationInfoToUser, | ||||||
|                               onBatteryInfo: showBatteryOptimizationInfoToUser, |                         ), | ||||||
|                             ) |  | ||||||
|                         : null, |  | ||||||
|                   ), |                   ), | ||||||
|                 if (isBackgroundEnabled && Platform.isAndroid) |                 if (isBackgroundEnabled && Platform.isAndroid) | ||||||
|                   ListTile( |                   ListTile( | ||||||
|                     isThreeLine: false, |                     isThreeLine: false, | ||||||
|                     dense: true, |                     dense: true, | ||||||
|                     enabled: hasExclusiveAccess, |  | ||||||
|                     title: const Text( |                     title: const Text( | ||||||
|                       'backup_controller_page_background_delay', |                       'backup_controller_page_background_delay', | ||||||
|                       style: TextStyle( |                       style: TextStyle( | ||||||
| @@ -339,9 +334,7 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|                     ), |                     ), | ||||||
|                     subtitle: Slider( |                     subtitle: Slider( | ||||||
|                       value: triggerDelay.value, |                       value: triggerDelay.value, | ||||||
|                       onChanged: hasExclusiveAccess |                       onChanged: (double v) => triggerDelay.value = v, | ||||||
|                           ? (double v) => triggerDelay.value = v |  | ||||||
|                           : null, |  | ||||||
|                       onChangeEnd: (double v) => ref |                       onChangeEnd: (double v) => ref | ||||||
|                           .read(backupProvider.notifier) |                           .read(backupProvider.notifier) | ||||||
|                           .configureBackgroundBackup( |                           .configureBackgroundBackup( | ||||||
| @@ -379,15 +372,13 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|           if (isBackgroundEnabled && Platform.isIOS) |           if (isBackgroundEnabled && Platform.isIOS) | ||||||
|             FutureBuilder( |             FutureBuilder( | ||||||
|               future: ref |               future: ref | ||||||
|                 .read(backgroundServiceProvider) |                   .read(backgroundServiceProvider) | ||||||
|                 .getIOSBackgroundAppRefreshEnabled(), |                   .getIOSBackgroundAppRefreshEnabled(), | ||||||
|               builder: (context, snapshot) { |               builder: (context, snapshot) { | ||||||
|                 final enabled = snapshot.data as bool?; |                 final enabled = snapshot.data as bool?; | ||||||
|                 // If it's not enabled, show them some kind of alert that says |                 // If it's not enabled, show them some kind of alert that says | ||||||
|                 // background refresh is not enabled |                 // background refresh is not enabled | ||||||
|                 if (enabled != null && !enabled) { |                 if (enabled != null && !enabled) {} | ||||||
|  |  | ||||||
|                 } |  | ||||||
|                 // If it's enabled, no need to bother them |                 // If it's enabled, no need to bother them | ||||||
|                 return Container(); |                 return Container(); | ||||||
|               }, |               }, | ||||||
| @@ -395,7 +386,7 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|           if (Platform.isIOS && isBackgroundEnabled && settings != null) |           if (Platform.isIOS && isBackgroundEnabled && settings != null) | ||||||
|             IosDebugInfoTile( |             IosDebugInfoTile( | ||||||
|               settings: settings, |               settings: settings, | ||||||
|           ), |             ), | ||||||
|         ], |         ], | ||||||
|       ); |       ); | ||||||
|     } |     } | ||||||
| @@ -403,7 +394,9 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|     Widget buildBackgroundAppRefreshWarning() { |     Widget buildBackgroundAppRefreshWarning() { | ||||||
|       return ListTile( |       return ListTile( | ||||||
|         isThreeLine: true, |         isThreeLine: true, | ||||||
|         leading: const Icon(Icons.task_outlined,), |         leading: const Icon( | ||||||
|  |           Icons.task_outlined, | ||||||
|  |         ), | ||||||
|         title: const Text( |         title: const Text( | ||||||
|           'backup_controller_page_background_app_refresh_disabled_title', |           'backup_controller_page_background_app_refresh_disabled_title', | ||||||
|           style: TextStyle( |           style: TextStyle( | ||||||
| @@ -420,7 +413,7 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|                 'backup_controller_page_background_app_refresh_disabled_content', |                 'backup_controller_page_background_app_refresh_disabled_content', | ||||||
|               ).tr(), |               ).tr(), | ||||||
|             ), |             ), | ||||||
|           ElevatedButton( |             ElevatedButton( | ||||||
|               onPressed: () => openAppSettings(), |               onPressed: () => openAppSettings(), | ||||||
|               child: const Text( |               child: const Text( | ||||||
|                 'backup_controller_page_background_app_refresh_enable_button_text', |                 'backup_controller_page_background_app_refresh_enable_button_text', | ||||||
| @@ -533,12 +526,9 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|             ), |             ), | ||||||
|           ), |           ), | ||||||
|           trailing: ElevatedButton( |           trailing: ElevatedButton( | ||||||
|             onPressed: hasExclusiveAccess |             onPressed: () { | ||||||
|                 ? () { |               AutoRouter.of(context).push(const BackupAlbumSelectionRoute()); | ||||||
|                     AutoRouter.of(context) |             }, | ||||||
|                         .push(const BackupAlbumSelectionRoute()); |  | ||||||
|                   } |  | ||||||
|                 : null, |  | ||||||
|             child: const Text( |             child: const Text( | ||||||
|               "backup_controller_page_select", |               "backup_controller_page_select", | ||||||
|               style: TextStyle( |               style: TextStyle( | ||||||
| @@ -598,28 +588,12 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|     } |     } | ||||||
|  |  | ||||||
|     buildBackgroundBackupInfo() { |     buildBackgroundBackupInfo() { | ||||||
|       return hasExclusiveAccess |       return const ListTile( | ||||||
|           ? const SizedBox.shrink() |         leading: Icon(Icons.info_outline_rounded), | ||||||
|           : Card( |         title: Text( | ||||||
|               shape: RoundedRectangleBorder( |           "Background backup is currently running, cannot start manual backup", | ||||||
|                 borderRadius: BorderRadius.circular(20), // if you need this |         ), | ||||||
|                 side: BorderSide( |       ); | ||||||
|                   color: isDarkMode |  | ||||||
|                       ? const Color.fromARGB(255, 56, 56, 56) |  | ||||||
|                       : Colors.black12, |  | ||||||
|                   width: 1, |  | ||||||
|                 ), |  | ||||||
|               ), |  | ||||||
|               elevation: 0, |  | ||||||
|               borderOnForeground: false, |  | ||||||
|               child: const Padding( |  | ||||||
|                 padding: EdgeInsets.all(16.0), |  | ||||||
|                 child: Text( |  | ||||||
|                   "Background backup is currently running, some actions are disabled", |  | ||||||
|                   style: TextStyle(fontWeight: FontWeight.bold), |  | ||||||
|                 ), |  | ||||||
|               ), |  | ||||||
|             ); |  | ||||||
|     } |     } | ||||||
|  |  | ||||||
|     return Scaffold( |     return Scaffold( | ||||||
| @@ -652,7 +626,6 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|                 style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), |                 style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16), | ||||||
|               ).tr(), |               ).tr(), | ||||||
|             ), |             ), | ||||||
|             buildBackgroundBackupInfo(), |  | ||||||
|             buildFolderSelectionTile(), |             buildFolderSelectionTile(), | ||||||
|             BackupInfoCard( |             BackupInfoCard( | ||||||
|               title: "backup_controller_page_total".tr(), |               title: "backup_controller_page_total".tr(), | ||||||
| @@ -681,22 +654,20 @@ class BackupControllerPage extends HookConsumerWidget { | |||||||
|             AnimatedSwitcher( |             AnimatedSwitcher( | ||||||
|               duration: const Duration(milliseconds: 500), |               duration: const Duration(milliseconds: 500), | ||||||
|               child: Platform.isIOS |               child: Platform.isIOS | ||||||
|               ? ( |                   ? (appRefreshDisabled | ||||||
|                 appRefreshDisabled |                       ? buildBackgroundAppRefreshWarning() | ||||||
|                   ? buildBackgroundAppRefreshWarning() |                       : buildBackgroundBackupController()) | ||||||
|                   : buildBackgroundBackupController() |                   : buildBackgroundBackupController(), | ||||||
|               ) : buildBackgroundBackupController(), |  | ||||||
|             ), |             ), | ||||||
|             const Divider(), |             const Divider(), | ||||||
|             buildStorageInformation(), |             buildStorageInformation(), | ||||||
|             const Divider(), |             const Divider(), | ||||||
|             const CurrentUploadingAssetInfoBox(), |             const CurrentUploadingAssetInfoBox(), | ||||||
|  |             if (!hasExclusiveAccess) buildBackgroundBackupInfo(), | ||||||
|             buildBackupButton() |             buildBackupButton() | ||||||
|           ], |           ], | ||||||
|         ), |         ), | ||||||
|       ), |       ), | ||||||
|     ); |     ); | ||||||
|   } |   } | ||||||
|  |  | ||||||
|  |  | ||||||
| } | } | ||||||
|   | |||||||
| @@ -1,3 +1,4 @@ | |||||||
|  | import 'package:collection/collection.dart'; | ||||||
| import 'package:immich_mobile/shared/models/user.dart'; | import 'package:immich_mobile/shared/models/user.dart'; | ||||||
| import 'package:isar/isar.dart'; | import 'package:isar/isar.dart'; | ||||||
| import 'dart:convert'; | import 'dart:convert'; | ||||||
| @@ -9,7 +10,8 @@ part 'store.g.dart'; | |||||||
| /// Can be used concurrently from multiple isolates | /// Can be used concurrently from multiple isolates | ||||||
| class Store { | class Store { | ||||||
|   static late final Isar _db; |   static late final Isar _db; | ||||||
|   static final List<dynamic> _cache = List.filled(StoreKey.values.length, null); |   static final List<dynamic> _cache = | ||||||
|  |       List.filled(StoreKey.values.map((e) => e.id).max + 1, null); | ||||||
|  |  | ||||||
|   /// Initializes the store (call exactly once per app start) |   /// Initializes the store (call exactly once per app start) | ||||||
|   static void init(Isar db) { |   static void init(Isar db) { | ||||||
| @@ -70,23 +72,44 @@ class StoreValue { | |||||||
|   int? intValue; |   int? intValue; | ||||||
|   String? strValue; |   String? strValue; | ||||||
|  |  | ||||||
|   T? _extract<T>(StoreKey key) => key.isInt |   dynamic _extract(StoreKey key) { | ||||||
|       ? (key.fromDb == null ? intValue : key.fromDb!.call(Store._db, intValue!)) |     switch (key.type) { | ||||||
|       : (key.fromJson != null |       case int: | ||||||
|           ? key.fromJson!(json.decode(strValue!)) |         return key.fromDb == null | ||||||
|           : strValue); |             ? intValue | ||||||
|   static Future<StoreValue> _of(dynamic value, StoreKey key) async => |             : key.fromDb!.call(Store._db, intValue!); | ||||||
|       StoreValue( |       case bool: | ||||||
|         key.id, |         return intValue == null ? null : intValue! == 1; | ||||||
|         intValue: key.isInt |       case DateTime: | ||||||
|             ? (key.toDb == null |         return intValue == null | ||||||
|                 ? value |  | ||||||
|                 : await key.toDb!.call(Store._db, value)) |  | ||||||
|             : null, |  | ||||||
|         strValue: key.isInt |  | ||||||
|             ? null |             ? null | ||||||
|             : (key.fromJson == null ? value : json.encode(value.toJson())), |             : DateTime.fromMicrosecondsSinceEpoch(intValue!); | ||||||
|       ); |       case String: | ||||||
|  |         return key.fromJson != null | ||||||
|  |             ? key.fromJson!.call(json.decode(strValue!)) | ||||||
|  |             : strValue; | ||||||
|  |     } | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   static Future<StoreValue> _of(dynamic value, StoreKey key) async { | ||||||
|  |     int? i; | ||||||
|  |     String? s; | ||||||
|  |     switch (key.type) { | ||||||
|  |       case int: | ||||||
|  |         i = (key.toDb == null ? value : await key.toDb!.call(Store._db, value)); | ||||||
|  |         break; | ||||||
|  |       case bool: | ||||||
|  |         i = value == null ? null : (value ? 1 : 0); | ||||||
|  |         break; | ||||||
|  |       case DateTime: | ||||||
|  |         i = value == null ? null : (value as DateTime).microsecondsSinceEpoch; | ||||||
|  |         break; | ||||||
|  |       case String: | ||||||
|  |         s = key.fromJson == null ? value : json.encode(value.toJson()); | ||||||
|  |         break; | ||||||
|  |     } | ||||||
|  |     return StoreValue(key.id, intValue: i, strValue: s); | ||||||
|  |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| /// Key for each possible value in the `Store`. | /// Key for each possible value in the `Store`. | ||||||
| @@ -94,21 +117,24 @@ class StoreValue { | |||||||
| enum StoreKey { | enum StoreKey { | ||||||
|   userRemoteId(0), |   userRemoteId(0), | ||||||
|   assetETag(1), |   assetETag(1), | ||||||
|   currentUser(2, isInt: true, fromDb: _getUser, toDb: _toUser), |   currentUser(2, type: int, fromDb: _getUser, toDb: _toUser), | ||||||
|   deviceIdHash(3, isInt: true), |   deviceIdHash(3, type: int), | ||||||
|   deviceId(4), |   deviceId(4), | ||||||
|   ; |   backupFailedSince(5, type: DateTime), | ||||||
|  |   backupRequireWifi(6, type: bool), | ||||||
|  |   backupRequireCharging(7, type: bool), | ||||||
|  |   backupTriggerDelay(8, type: int); | ||||||
|  |  | ||||||
|   const StoreKey( |   const StoreKey( | ||||||
|     this.id, { |     this.id, { | ||||||
|     this.isInt = false, |     this.type = String, | ||||||
|     this.fromDb, |     this.fromDb, | ||||||
|     this.toDb, |     this.toDb, | ||||||
|     // ignore: unused_element |     // ignore: unused_element | ||||||
|     this.fromJson, |     this.fromJson, | ||||||
|   }); |   }); | ||||||
|   final int id; |   final int id; | ||||||
|   final bool isInt; |   final Type type; | ||||||
|   final dynamic Function(Isar, int)? fromDb; |   final dynamic Function(Isar, int)? fromDb; | ||||||
|   final Future<int> Function(Isar, dynamic)? toDb; |   final Future<int> Function(Isar, dynamic)? toDb; | ||||||
|   final Function(dynamic)? fromJson; |   final Function(dynamic)? fromJson; | ||||||
|   | |||||||
| @@ -4,22 +4,102 @@ import 'package:flutter/cupertino.dart'; | |||||||
| import 'package:hive/hive.dart'; | import 'package:hive/hive.dart'; | ||||||
| import 'package:immich_mobile/constants/hive_box.dart'; | import 'package:immich_mobile/constants/hive_box.dart'; | ||||||
| import 'package:immich_mobile/modules/album/services/album_cache.service.dart'; | import 'package:immich_mobile/modules/album/services/album_cache.service.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/backup_album.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/duplicated_asset.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart'; | ||||||
|  | import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart'; | ||||||
| import 'package:immich_mobile/shared/models/store.dart'; | import 'package:immich_mobile/shared/models/store.dart'; | ||||||
| import 'package:immich_mobile/shared/services/asset_cache.service.dart'; | import 'package:immich_mobile/shared/services/asset_cache.service.dart'; | ||||||
|  | import 'package:isar/isar.dart'; | ||||||
|  |  | ||||||
| Future<void> migrateHiveToStoreIfNecessary() async { | Future<void> migrateHiveToStoreIfNecessary() async { | ||||||
|  |   await _migrateHiveBoxIfNecessary(userInfoBox, _migrateHiveUserInfoBox); | ||||||
|  |   await _migrateHiveBoxIfNecessary( | ||||||
|  |     backgroundBackupInfoBox, | ||||||
|  |     _migrateHiveBackgroundBackupInfoBox, | ||||||
|  |   ); | ||||||
|  |   await _migrateHiveBoxIfNecessary(hiveBackupInfoBox, _migrateBackupInfoBox); | ||||||
|  |   await _migrateHiveBoxIfNecessary( | ||||||
|  |     duplicatedAssetsBox, | ||||||
|  |     _migrateDuplicatedAssetsBox, | ||||||
|  |   ); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Future<void> _migrateHiveUserInfoBox(Box box) async { | ||||||
|  |   await _migrateKey(box, userIdKey, StoreKey.userRemoteId); | ||||||
|  |   await _migrateKey(box, assetEtagKey, StoreKey.assetETag); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Future<void> _migrateHiveBackgroundBackupInfoBox(Box box) async { | ||||||
|  |   await _migrateKey(box, backupFailedSince, StoreKey.backupFailedSince); | ||||||
|  |   await _migrateKey(box, backupRequireWifi, StoreKey.backupRequireWifi); | ||||||
|  |   await _migrateKey(box, backupRequireCharging, StoreKey.backupRequireCharging); | ||||||
|  |   await _migrateKey(box, backupTriggerDelay, StoreKey.backupTriggerDelay); | ||||||
|  |   return box.deleteFromDisk(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Future<void> _migrateBackupInfoBox(Box<HiveBackupAlbums> box) async { | ||||||
|  |   final Isar? db = Isar.getInstance(); | ||||||
|  |   if (db == null) { | ||||||
|  |     throw Exception("_migrateBackupInfoBox could not load database"); | ||||||
|  |   } | ||||||
|  |   final HiveBackupAlbums? infos = box.get(backupInfoKey); | ||||||
|  |   if (infos != null) { | ||||||
|  |     List<BackupAlbum> albums = []; | ||||||
|  |     for (int i = 0; i < infos.selectedAlbumIds.length; i++) { | ||||||
|  |       final album = BackupAlbum( | ||||||
|  |         infos.selectedAlbumIds[i], | ||||||
|  |         infos.lastSelectedBackupTime[i], | ||||||
|  |         BackupSelection.select, | ||||||
|  |       ); | ||||||
|  |       albums.add(album); | ||||||
|  |     } | ||||||
|  |     for (int i = 0; i < infos.excludedAlbumsIds.length; i++) { | ||||||
|  |       final album = BackupAlbum( | ||||||
|  |         infos.excludedAlbumsIds[i], | ||||||
|  |         infos.lastExcludedBackupTime[i], | ||||||
|  |         BackupSelection.exclude, | ||||||
|  |       ); | ||||||
|  |       albums.add(album); | ||||||
|  |     } | ||||||
|  |     await db.writeTxn(() => db.backupAlbums.putAll(albums)); | ||||||
|  |   } else { | ||||||
|  |     debugPrint("_migrateBackupInfoBox deletes empty box"); | ||||||
|  |   } | ||||||
|  |   return box.deleteFromDisk(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Future<void> _migrateDuplicatedAssetsBox(Box<HiveDuplicatedAssets> box) async { | ||||||
|  |   final Isar? db = Isar.getInstance(); | ||||||
|  |   if (db == null) { | ||||||
|  |     throw Exception("_migrateBackupInfoBox could not load database"); | ||||||
|  |   } | ||||||
|  |   final HiveDuplicatedAssets? duplicatedAssets = box.get(duplicatedAssetsKey); | ||||||
|  |   if (duplicatedAssets != null) { | ||||||
|  |     final duplicatedAssetIds = duplicatedAssets.duplicatedAssetIds | ||||||
|  |         .map((id) => DuplicatedAsset(id)) | ||||||
|  |         .toList(); | ||||||
|  |     await db.writeTxn(() => db.duplicatedAssets.putAll(duplicatedAssetIds)); | ||||||
|  |   } else { | ||||||
|  |     debugPrint("_migrateDuplicatedAssetsBox deletes empty box"); | ||||||
|  |   } | ||||||
|  |   return box.deleteFromDisk(); | ||||||
|  | } | ||||||
|  |  | ||||||
|  | Future<void> _migrateHiveBoxIfNecessary<T>( | ||||||
|  |   String boxName, | ||||||
|  |   Future<void> Function(Box<T>) migrate, | ||||||
|  | ) async { | ||||||
|   try { |   try { | ||||||
|     if (await Hive.boxExists(userInfoBox)) { |     if (await Hive.boxExists(boxName)) { | ||||||
|       final Box box = await Hive.openBox(userInfoBox); |       await migrate(await Hive.openBox<T>(boxName)); | ||||||
|       await _migrateSingleKey(box, userIdKey, StoreKey.userRemoteId); |  | ||||||
|       await _migrateSingleKey(box, assetEtagKey, StoreKey.assetETag); |  | ||||||
|     } |     } | ||||||
|   } catch (e) { |   } catch (e) { | ||||||
|     debugPrint("Error while migrating userInfoBox $e"); |     debugPrint("Error while migrating $boxName $e"); | ||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
| _migrateSingleKey(Box box, String hiveKey, StoreKey key) async { | _migrateKey(Box box, String hiveKey, StoreKey key) async { | ||||||
|   final String? value = box.get(hiveKey); |   final String? value = box.get(hiveKey); | ||||||
|   if (value != null) { |   if (value != null) { | ||||||
|     await Store.put(key, value); |     await Store.put(key, value); | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user