mirror of
https://github.com/immich-app/immich.git
synced 2025-05-31 04:05:39 -04: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:
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);
|
||||||
|
Loading…
x
Reference in New Issue
Block a user