mirror of
https://github.com/immich-app/immich.git
synced 2024-12-19 00:32:49 +02:00
c23b2479f7
let's the user configure how much to delay the trigger for running the backup whenever assets are changed on the device
698 lines
23 KiB
Dart
698 lines
23 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:cancellation_token_http/http.dart';
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:hive_flutter/hive_flutter.dart';
|
|
import 'package:hooks_riverpod/hooks_riverpod.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/backup_state.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/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/background_service/background.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/providers/authentication.provider.dart';
|
|
import 'package:immich_mobile/shared/providers/app_state.provider.dart';
|
|
import 'package:immich_mobile/shared/services/server_info.service.dart';
|
|
import 'package:logging/logging.dart';
|
|
import 'package:openapi/api.dart';
|
|
import 'package:photo_manager/photo_manager.dart';
|
|
|
|
class BackupNotifier extends StateNotifier<BackUpState> {
|
|
BackupNotifier(
|
|
this._backupService,
|
|
this._serverInfoService,
|
|
this._authState,
|
|
this._backgroundService,
|
|
this.ref,
|
|
) : super(
|
|
BackUpState(
|
|
backupProgress: BackUpProgressEnum.idle,
|
|
allAssetsInDatabase: const [],
|
|
progressInPercentage: 0,
|
|
cancelToken: CancellationToken(),
|
|
backgroundBackup: false,
|
|
backupRequireWifi: true,
|
|
backupRequireCharging: false,
|
|
backupTriggerDelay: 5000,
|
|
serverInfo: ServerInfoResponseDto(
|
|
diskAvailable: "0",
|
|
diskAvailableRaw: 0,
|
|
diskSize: "0",
|
|
diskSizeRaw: 0,
|
|
diskUsagePercentage: 0,
|
|
diskUse: "0",
|
|
diskUseRaw: 0,
|
|
),
|
|
availableAlbums: const [],
|
|
selectedBackupAlbums: const {},
|
|
excludedBackupAlbums: const {},
|
|
allUniqueAssets: const {},
|
|
selectedAlbumsBackupAssetsIds: const {},
|
|
currentUploadAsset: CurrentUploadAsset(
|
|
id: '...',
|
|
createdAt: DateTime.parse('2020-10-04'),
|
|
fileName: '...',
|
|
fileType: '...',
|
|
),
|
|
),
|
|
) {
|
|
getBackupInfo();
|
|
}
|
|
|
|
final log = Logger('BackupNotifier');
|
|
final BackupService _backupService;
|
|
final ServerInfoService _serverInfoService;
|
|
final AuthenticationState _authState;
|
|
final BackgroundService _backgroundService;
|
|
final Ref ref;
|
|
|
|
///
|
|
/// UI INTERACTION
|
|
///
|
|
/// Album selection
|
|
/// Due to the overlapping assets across multiple albums on the device
|
|
/// We have method to include and exclude albums
|
|
/// The total unique assets will be used for backing mechanism
|
|
///
|
|
void addAlbumForBackup(AvailableAlbum album) {
|
|
if (state.excludedBackupAlbums.contains(album)) {
|
|
removeExcludedAlbumForBackup(album);
|
|
}
|
|
|
|
state = state
|
|
.copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, album});
|
|
_updateBackupAssetCount();
|
|
}
|
|
|
|
void addExcludedAlbumForBackup(AvailableAlbum album) {
|
|
if (state.selectedBackupAlbums.contains(album)) {
|
|
removeAlbumForBackup(album);
|
|
}
|
|
state = state
|
|
.copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, album});
|
|
_updateBackupAssetCount();
|
|
}
|
|
|
|
void removeAlbumForBackup(AvailableAlbum album) {
|
|
Set<AvailableAlbum> currentSelectedAlbums = state.selectedBackupAlbums;
|
|
|
|
currentSelectedAlbums.removeWhere((a) => a == album);
|
|
|
|
state = state.copyWith(selectedBackupAlbums: currentSelectedAlbums);
|
|
_updateBackupAssetCount();
|
|
}
|
|
|
|
void removeExcludedAlbumForBackup(AvailableAlbum album) {
|
|
Set<AvailableAlbum> currentExcludedAlbums = state.excludedBackupAlbums;
|
|
|
|
currentExcludedAlbums.removeWhere((a) => a == album);
|
|
|
|
state = state.copyWith(excludedBackupAlbums: currentExcludedAlbums);
|
|
_updateBackupAssetCount();
|
|
}
|
|
|
|
void configureBackgroundBackup({
|
|
bool? enabled,
|
|
bool? requireWifi,
|
|
bool? requireCharging,
|
|
int? triggerDelay,
|
|
required void Function(String msg) onError,
|
|
required void Function() onBatteryInfo,
|
|
}) async {
|
|
assert(
|
|
enabled != null ||
|
|
requireWifi != null ||
|
|
requireCharging != null ||
|
|
triggerDelay != null,
|
|
);
|
|
if (Platform.isAndroid) {
|
|
final bool wasEnabled = state.backgroundBackup;
|
|
final bool wasWifi = state.backupRequireWifi;
|
|
final bool wasCharging = state.backupRequireCharging;
|
|
final int oldTriggerDelay = state.backupTriggerDelay;
|
|
state = state.copyWith(
|
|
backgroundBackup: enabled,
|
|
backupRequireWifi: requireWifi,
|
|
backupRequireCharging: requireCharging,
|
|
backupTriggerDelay: triggerDelay,
|
|
);
|
|
|
|
if (state.backgroundBackup) {
|
|
bool success = true;
|
|
if (!wasEnabled) {
|
|
if (!await _backgroundService.isIgnoringBatteryOptimizations()) {
|
|
onBatteryInfo();
|
|
}
|
|
success &= await _backgroundService.enableService(immediate: true);
|
|
}
|
|
success &= success &&
|
|
await _backgroundService.configureService(
|
|
requireUnmetered: state.backupRequireWifi,
|
|
requireCharging: state.backupRequireCharging,
|
|
triggerUpdateDelay: state.backupTriggerDelay,
|
|
triggerMaxDelay: state.backupTriggerDelay * 10,
|
|
);
|
|
if (success) {
|
|
final box = Hive.box(backgroundBackupInfoBox);
|
|
await Future.wait([
|
|
box.put(backupRequireWifi, state.backupRequireWifi),
|
|
box.put(backupRequireCharging, state.backupRequireCharging),
|
|
box.put(backupTriggerDelay, state.backupTriggerDelay),
|
|
]);
|
|
} else {
|
|
state = state.copyWith(
|
|
backgroundBackup: wasEnabled,
|
|
backupRequireWifi: wasWifi,
|
|
backupRequireCharging: wasCharging,
|
|
backupTriggerDelay: oldTriggerDelay,
|
|
);
|
|
onError("backup_controller_page_background_configure_error");
|
|
}
|
|
} else {
|
|
final bool success = await _backgroundService.disableService();
|
|
if (!success) {
|
|
state = state.copyWith(backgroundBackup: wasEnabled);
|
|
onError("backup_controller_page_background_configure_error");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
///
|
|
/// Get all album on the device
|
|
/// Get all selected and excluded album from the user's persistent storage
|
|
/// If this is the first time performing backup - set the default selected album to be
|
|
/// the one that has all assets (`Recent` on Android, `Recents` on iOS)
|
|
///
|
|
Future<void> _getBackupAlbumsInfo() async {
|
|
Stopwatch stopwatch = Stopwatch()..start();
|
|
// Get all albums on the device
|
|
List<AvailableAlbum> availableAlbums = [];
|
|
List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(
|
|
hasAll: true,
|
|
type: RequestType.common,
|
|
);
|
|
|
|
log.info('Found ${albums.length} local albums');
|
|
|
|
for (AssetPathEntity album in albums) {
|
|
AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
|
|
|
|
var assetCountInAlbum = await album.assetCountAsync;
|
|
if (assetCountInAlbum > 0) {
|
|
var assetList =
|
|
await album.getAssetListRange(start: 0, end: assetCountInAlbum);
|
|
|
|
if (assetList.isNotEmpty) {
|
|
var thumbnailAsset = assetList.first;
|
|
var thumbnailData = await thumbnailAsset
|
|
.thumbnailDataWithSize(const ThumbnailSize(512, 512));
|
|
availableAlbum =
|
|
availableAlbum.copyWith(thumbnailData: thumbnailData);
|
|
}
|
|
|
|
availableAlbums.add(availableAlbum);
|
|
}
|
|
}
|
|
|
|
state = state.copyWith(availableAlbums: availableAlbums);
|
|
|
|
// Put persistent storage info into local state of the app
|
|
// Get local storage on selected backup album
|
|
Box<HiveBackupAlbums> backupAlbumInfoBox =
|
|
Hive.box<HiveBackupAlbums>(hiveBackupInfoBox);
|
|
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.
|
|
if (backupAlbumInfo.selectedAlbumIds.isEmpty) {
|
|
log.info("First time backup; setup 'Recent(s)' album as default");
|
|
|
|
// Get album that contains all assets
|
|
var list = await PhotoManager.getAssetPathList(
|
|
hasAll: true,
|
|
onlyAll: true,
|
|
type: RequestType.common,
|
|
);
|
|
|
|
if (list.isEmpty) {
|
|
return;
|
|
}
|
|
AssetPathEntity albumHasAllAssets = list.first;
|
|
|
|
backupAlbumInfoBox.put(
|
|
backupInfoKey,
|
|
HiveBackupAlbums(
|
|
selectedAlbumIds: [albumHasAllAssets.id],
|
|
excludedAlbumsIds: [],
|
|
lastSelectedBackupTime: [
|
|
DateTime.fromMillisecondsSinceEpoch(0, isUtc: true)
|
|
],
|
|
lastExcludedBackupTime: [],
|
|
),
|
|
);
|
|
|
|
backupAlbumInfo = backupAlbumInfoBox.get(backupInfoKey);
|
|
}
|
|
|
|
// Generate AssetPathEntity from id to add to local state
|
|
try {
|
|
Set<AvailableAlbum> selectedAlbums = {};
|
|
for (var i = 0; i < backupAlbumInfo!.selectedAlbumIds.length; i++) {
|
|
var albumAsset =
|
|
await AssetPathEntity.fromId(backupAlbumInfo.selectedAlbumIds[i]);
|
|
selectedAlbums.add(
|
|
AvailableAlbum(
|
|
albumEntity: albumAsset,
|
|
lastBackup: backupAlbumInfo.lastSelectedBackupTime.length > i
|
|
? backupAlbumInfo.lastSelectedBackupTime[i]
|
|
: DateTime.fromMillisecondsSinceEpoch(0, isUtc: true),
|
|
),
|
|
);
|
|
}
|
|
|
|
Set<AvailableAlbum> excludedAlbums = {};
|
|
for (var i = 0; i < backupAlbumInfo.excludedAlbumsIds.length; i++) {
|
|
var albumAsset =
|
|
await AssetPathEntity.fromId(backupAlbumInfo.excludedAlbumsIds[i]);
|
|
excludedAlbums.add(
|
|
AvailableAlbum(
|
|
albumEntity: albumAsset,
|
|
lastBackup: backupAlbumInfo.lastExcludedBackupTime.length > i
|
|
? backupAlbumInfo.lastExcludedBackupTime[i]
|
|
: DateTime.fromMillisecondsSinceEpoch(0, isUtc: true),
|
|
),
|
|
);
|
|
}
|
|
state = state.copyWith(
|
|
selectedBackupAlbums: selectedAlbums,
|
|
excludedBackupAlbums: excludedAlbums,
|
|
);
|
|
} catch (e, stackTrace) {
|
|
log.severe("Failed to generate album from id", e, stackTrace);
|
|
}
|
|
|
|
debugPrint("_getBackupAlbumsInfo takes ${stopwatch.elapsedMilliseconds}ms");
|
|
}
|
|
|
|
///
|
|
/// From all the selected and albums assets
|
|
/// Find the assets that are not overlapping between the two sets
|
|
/// Those assets are unique and are used as the total assets
|
|
///
|
|
Future<void> _updateBackupAssetCount() async {
|
|
Set<String> duplicatedAssetIds = _backupService.getDuplicatedAssetIds();
|
|
Set<AssetEntity> assetsFromSelectedAlbums = {};
|
|
Set<AssetEntity> assetsFromExcludedAlbums = {};
|
|
|
|
for (var album in state.selectedBackupAlbums) {
|
|
var assets = await album.albumEntity.getAssetListRange(
|
|
start: 0,
|
|
end: await album.albumEntity.assetCountAsync,
|
|
);
|
|
assetsFromSelectedAlbums.addAll(assets);
|
|
}
|
|
|
|
for (var album in state.excludedBackupAlbums) {
|
|
var assets = await album.albumEntity.getAssetListRange(
|
|
start: 0,
|
|
end: await album.albumEntity.assetCountAsync,
|
|
);
|
|
assetsFromExcludedAlbums.addAll(assets);
|
|
}
|
|
|
|
Set<AssetEntity> allUniqueAssets =
|
|
assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums);
|
|
var allAssetsInDatabase = await _backupService.getDeviceBackupAsset();
|
|
|
|
if (allAssetsInDatabase == null) {
|
|
return;
|
|
}
|
|
|
|
// Find asset that were backup from selected albums
|
|
Set<String> selectedAlbumsBackupAssets =
|
|
Set.from(allUniqueAssets.map((e) => e.id));
|
|
|
|
selectedAlbumsBackupAssets
|
|
.removeWhere((assetId) => !allAssetsInDatabase.contains(assetId));
|
|
|
|
// Remove duplicated asset from all unique assets
|
|
allUniqueAssets.removeWhere(
|
|
(asset) => duplicatedAssetIds.contains(asset.id),
|
|
);
|
|
|
|
if (allUniqueAssets.isEmpty) {
|
|
log.info("Not found albums or assets on the device to backup");
|
|
state = state.copyWith(
|
|
backupProgress: BackUpProgressEnum.idle,
|
|
allAssetsInDatabase: allAssetsInDatabase,
|
|
allUniqueAssets: {},
|
|
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
|
|
);
|
|
return;
|
|
} else {
|
|
state = state.copyWith(
|
|
allAssetsInDatabase: allAssetsInDatabase,
|
|
allUniqueAssets: allUniqueAssets,
|
|
selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
|
|
);
|
|
}
|
|
|
|
// Save to persistent storage
|
|
_updatePersistentAlbumsSelection();
|
|
|
|
return;
|
|
}
|
|
|
|
/// Get all necessary information for calculating the available albums,
|
|
/// which albums are selected or excluded
|
|
/// and then update the UI according to those information
|
|
Future<void> getBackupInfo() async {
|
|
var isEnabled = await _backgroundService.isBackgroundBackupEnabled();
|
|
|
|
state = state.copyWith(backgroundBackup: isEnabled);
|
|
|
|
if (state.backupProgress != BackUpProgressEnum.inBackground) {
|
|
await _getBackupAlbumsInfo();
|
|
await _updateServerInfo();
|
|
await _updateBackupAssetCount();
|
|
}
|
|
}
|
|
|
|
/// Save user selection of selected albums and excluded albums to
|
|
/// Hive database
|
|
void _updatePersistentAlbumsSelection() {
|
|
final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
|
|
Box<HiveBackupAlbums> backupAlbumInfoBox =
|
|
Hive.box<HiveBackupAlbums>(hiveBackupInfoBox);
|
|
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(),
|
|
),
|
|
);
|
|
}
|
|
|
|
/// Invoke backup process
|
|
Future<void> startBackupProcess() async {
|
|
debugPrint("Start backup process");
|
|
assert(state.backupProgress == BackUpProgressEnum.idle);
|
|
state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
|
|
|
|
await getBackupInfo();
|
|
|
|
var authResult = await PhotoManager.requestPermissionExtend();
|
|
if (authResult.isAuth) {
|
|
await PhotoManager.clearFileCache();
|
|
|
|
if (state.allUniqueAssets.isEmpty) {
|
|
log.info("No Asset On Device - Abort Backup Process");
|
|
state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
|
|
return;
|
|
}
|
|
|
|
Set<AssetEntity> assetsWillBeBackup = Set.from(state.allUniqueAssets);
|
|
// Remove item that has already been backed up
|
|
for (var assetId in state.allAssetsInDatabase) {
|
|
assetsWillBeBackup.removeWhere((e) => e.id == assetId);
|
|
}
|
|
|
|
if (assetsWillBeBackup.isEmpty) {
|
|
state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
|
|
}
|
|
|
|
// Perform Backup
|
|
state = state.copyWith(cancelToken: CancellationToken());
|
|
await _backupService.backupAsset(
|
|
assetsWillBeBackup,
|
|
state.cancelToken,
|
|
_onAssetUploaded,
|
|
_onUploadProgress,
|
|
_onSetCurrentBackupAsset,
|
|
_onBackupError,
|
|
);
|
|
await _notifyBackgroundServiceCanRun();
|
|
} else {
|
|
PhotoManager.openSetting();
|
|
}
|
|
}
|
|
|
|
void _onBackupError(ErrorUploadAsset errorAssetInfo) {
|
|
ref.watch(errorBackupListProvider.notifier).add(errorAssetInfo);
|
|
}
|
|
|
|
void _onSetCurrentBackupAsset(CurrentUploadAsset currentUploadAsset) {
|
|
state = state.copyWith(currentUploadAsset: currentUploadAsset);
|
|
}
|
|
|
|
void cancelBackup() {
|
|
if (state.backupProgress != BackUpProgressEnum.inProgress) {
|
|
_notifyBackgroundServiceCanRun();
|
|
}
|
|
state.cancelToken.cancel();
|
|
state = state.copyWith(
|
|
backupProgress: BackUpProgressEnum.idle,
|
|
progressInPercentage: 0.0,
|
|
);
|
|
}
|
|
|
|
void _onAssetUploaded(
|
|
String deviceAssetId,
|
|
String deviceId,
|
|
bool isDuplicated,
|
|
) {
|
|
if (isDuplicated) {
|
|
state = state.copyWith(
|
|
allUniqueAssets: state.allUniqueAssets
|
|
.where((asset) => asset.id != deviceAssetId)
|
|
.toSet(),
|
|
);
|
|
} else {
|
|
state = state.copyWith(
|
|
selectedAlbumsBackupAssetsIds: {
|
|
...state.selectedAlbumsBackupAssetsIds,
|
|
deviceAssetId
|
|
},
|
|
allAssetsInDatabase: [...state.allAssetsInDatabase, deviceAssetId],
|
|
);
|
|
}
|
|
|
|
if (state.allUniqueAssets.length -
|
|
state.selectedAlbumsBackupAssetsIds.length ==
|
|
0) {
|
|
final latestAssetBackup =
|
|
state.allUniqueAssets.map((e) => e.modifiedDateTime).reduce(
|
|
(v, e) => e.isAfter(v) ? e : v,
|
|
);
|
|
state = state.copyWith(
|
|
selectedBackupAlbums: state.selectedBackupAlbums
|
|
.map((e) => e.copyWith(lastBackup: latestAssetBackup))
|
|
.toSet(),
|
|
excludedBackupAlbums: state.excludedBackupAlbums
|
|
.map((e) => e.copyWith(lastBackup: latestAssetBackup))
|
|
.toSet(),
|
|
backupProgress: BackUpProgressEnum.done,
|
|
progressInPercentage: 0.0,
|
|
);
|
|
_updatePersistentAlbumsSelection();
|
|
}
|
|
|
|
_updateServerInfo();
|
|
}
|
|
|
|
void _onUploadProgress(int sent, int total) {
|
|
state = state.copyWith(
|
|
progressInPercentage: (sent.toDouble() / total.toDouble() * 100),
|
|
);
|
|
}
|
|
|
|
Future<void> _updateServerInfo() async {
|
|
var serverInfo = await _serverInfoService.getServerInfo();
|
|
|
|
// Update server info
|
|
if (serverInfo != null) {
|
|
state = state.copyWith(
|
|
serverInfo: serverInfo,
|
|
);
|
|
}
|
|
}
|
|
|
|
Future<void> _resumeBackup() async {
|
|
// Check if user is login
|
|
var accessKey = Hive.box(userInfoBox).get(accessTokenKey);
|
|
|
|
// User has been logged out return
|
|
if (accessKey == null || !_authState.isAuthenticated) {
|
|
log.info("[_resumeBackup] not authenticated - abort");
|
|
return;
|
|
}
|
|
|
|
// Check if this device is enable backup by the user
|
|
if ((_authState.deviceInfo.deviceId == _authState.deviceId) &&
|
|
_authState.deviceInfo.isAutoBackup) {
|
|
// check if backup is alreayd in process - then return
|
|
if (state.backupProgress == BackUpProgressEnum.inProgress) {
|
|
log.info("[_resumeBackup] Backup is already in progress - abort");
|
|
return;
|
|
}
|
|
|
|
if (state.backupProgress == BackUpProgressEnum.inBackground) {
|
|
log.info("[_resumeBackup] Background backup is running - abort");
|
|
return;
|
|
}
|
|
|
|
// Run backup
|
|
log.info("[_resumeBackup] Start back up");
|
|
await startBackupProcess();
|
|
}
|
|
|
|
return;
|
|
}
|
|
|
|
Future<void> resumeBackup() async {
|
|
if (Platform.isAndroid) {
|
|
// assumes the background service is currently running
|
|
// if true, waits until it has stopped to update the app state from HiveDB
|
|
// before actually resuming backup by calling the internal `_resumeBackup`
|
|
final BackUpProgressEnum previous = state.backupProgress;
|
|
state = state.copyWith(backupProgress: BackUpProgressEnum.inBackground);
|
|
final bool hasLock = await _backgroundService.acquireLock();
|
|
if (!hasLock) {
|
|
log.warning("WARNING [resumeBackup] failed to acquireLock");
|
|
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> excludedAlbums = state.excludedBackupAlbums;
|
|
if (albums != null) {
|
|
selectedAlbums = _updateAlbumsBackupTime(
|
|
selectedAlbums,
|
|
albums.selectedAlbumIds,
|
|
albums.lastSelectedBackupTime,
|
|
);
|
|
excludedAlbums = _updateAlbumsBackupTime(
|
|
excludedAlbums,
|
|
albums.excludedAlbumsIds,
|
|
albums.lastExcludedBackupTime,
|
|
);
|
|
}
|
|
final Box backgroundBox = Hive.box(backgroundBackupInfoBox);
|
|
state = state.copyWith(
|
|
backupProgress: previous,
|
|
selectedBackupAlbums: selectedAlbums,
|
|
excludedBackupAlbums: excludedAlbums,
|
|
backupRequireWifi: backgroundBox.get(backupRequireWifi),
|
|
backupRequireCharging: backgroundBox.get(backupRequireCharging),
|
|
backupTriggerDelay: backgroundBox.get(backupTriggerDelay),
|
|
);
|
|
}
|
|
return _resumeBackup();
|
|
}
|
|
|
|
Set<AvailableAlbum> _updateAlbumsBackupTime(
|
|
Set<AvailableAlbum> albums,
|
|
List<String> ids,
|
|
List<DateTime> times,
|
|
) {
|
|
Set<AvailableAlbum> result = {};
|
|
for (int i = 0; i < ids.length; i++) {
|
|
try {
|
|
AvailableAlbum a = albums.firstWhere((e) => e.id == ids[i]);
|
|
result.add(a.copyWith(lastBackup: times[i]));
|
|
} on StateError {
|
|
log.severe(
|
|
"[_updateAlbumBackupTime] failed to find album in state",
|
|
"State Error",
|
|
StackTrace.current,
|
|
);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
Future<void> _notifyBackgroundServiceCanRun() async {
|
|
const allowedStates = [
|
|
AppStateEnum.inactive,
|
|
AppStateEnum.paused,
|
|
AppStateEnum.detached,
|
|
];
|
|
if (Platform.isAndroid &&
|
|
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();
|
|
}
|
|
}
|
|
}
|
|
|
|
final backupProvider =
|
|
StateNotifierProvider<BackupNotifier, BackUpState>((ref) {
|
|
return BackupNotifier(
|
|
ref.watch(backupServiceProvider),
|
|
ref.watch(serverInfoServiceProvider),
|
|
ref.watch(authenticationProvider),
|
|
ref.watch(backgroundServiceProvider),
|
|
ref,
|
|
);
|
|
});
|