mirror of
https://github.com/immich-app/immich.git
synced 2024-12-19 00:32:49 +02:00
4fe535e5e8
This change greatly reduces the chance that a backup is not performed when a new photo/video is made. Instead of combining the change trigger and additonal constraints (wifi or charging) into a single worker, these aspects are now separated. Thus, it is now reliably possible to take pictures while the wifi constraint is not satisfied and upload them hours/days later once connected to wifi without taking a new photo. As a positive side effect, this simplifies the error/retry handling by directly leveraging Android's WorkManager without workarounds. The separation also allows to notify the currently running BackupWorker that new assets were added while backing up other assets to also upload those newly added assets. Further, a new tiny service checks if the app is killed, to reschedule the content change worker and allow to detect the first new photo. Bonus: The home screen now shows backup as enabled if background backup is active. * use separate worker/task for listening on changed/added assets * use separate worker/task for performing the backup * content observer worker enqueues backup worker on each new asset * wifi/charging constraints only apply to backup worker * backupworker is notified of assets added while running to re-run * new service to catch app being killed to workaround WorkManager issue
628 lines
21 KiB
Dart
628 lines
21 KiB
Dart
import 'dart:io';
|
|
|
|
import 'package:cancellation_token_http/http.dart';
|
|
import 'package:flutter/foundation.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/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: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,
|
|
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 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,
|
|
required void Function(String msg) onError,
|
|
required void Function() onBatteryInfo,
|
|
}) async {
|
|
assert(enabled != null || requireWifi != null || requireCharging != null);
|
|
if (Platform.isAndroid) {
|
|
final bool wasEnabled = state.backgroundBackup;
|
|
final bool wasWifi = state.backupRequireWifi;
|
|
final bool wasCharing = state.backupRequireCharging;
|
|
state = state.copyWith(
|
|
backgroundBackup: enabled,
|
|
backupRequireWifi: requireWifi,
|
|
backupRequireCharging: requireCharging,
|
|
);
|
|
|
|
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,
|
|
);
|
|
if (success) {
|
|
await Hive.box(backgroundBackupInfoBox)
|
|
.put(backupRequireWifi, state.backupRequireWifi);
|
|
await Hive.box(backgroundBackupInfoBox)
|
|
.put(backupRequireCharging, state.backupRequireCharging);
|
|
} else {
|
|
state = state.copyWith(
|
|
backgroundBackup: wasEnabled,
|
|
backupRequireWifi: wasWifi,
|
|
backupRequireCharging: wasCharing,
|
|
);
|
|
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 {
|
|
// Get all albums on the device
|
|
List<AvailableAlbum> availableAlbums = [];
|
|
List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(
|
|
hasAll: true,
|
|
type: RequestType.common,
|
|
);
|
|
|
|
for (AssetPathEntity album in albums) {
|
|
AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
|
|
|
|
var assetList =
|
|
await album.getAssetListRange(start: 0, end: album.assetCount);
|
|
|
|
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) {
|
|
debugPrint("[ERROR] getting Hive backup album infomation");
|
|
return;
|
|
}
|
|
|
|
// First time backup - set isAll album is the default one for backup.
|
|
if (backupAlbumInfo.selectedAlbumIds.isEmpty) {
|
|
debugPrint("First time backup setup recent 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) {
|
|
debugPrint("[ERROR] Failed to generate album from id $e");
|
|
}
|
|
}
|
|
|
|
///
|
|
/// 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<AssetEntity> assetsFromSelectedAlbums = {};
|
|
Set<AssetEntity> assetsFromExcludedAlbums = {};
|
|
|
|
for (var album in state.selectedBackupAlbums) {
|
|
var assets = await album.albumEntity
|
|
.getAssetListRange(start: 0, end: album.assetCount);
|
|
assetsFromSelectedAlbums.addAll(assets);
|
|
}
|
|
|
|
for (var album in state.excludedBackupAlbums) {
|
|
var assets = await album.albumEntity
|
|
.getAssetListRange(start: 0, end: album.assetCount);
|
|
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));
|
|
|
|
if (allUniqueAssets.isEmpty) {
|
|
debugPrint("No Asset On Device");
|
|
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 {
|
|
final bool isEnabled = await _backgroundService.isBackgroundBackupEnabled();
|
|
state = state.copyWith(backgroundBackup: isEnabled);
|
|
if (state.backupProgress != BackUpProgressEnum.inBackground) {
|
|
await Future.wait([
|
|
_getBackupAlbumsInfo(),
|
|
_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 {
|
|
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) {
|
|
debugPrint("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) {
|
|
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) {
|
|
debugPrint("[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) {
|
|
debugPrint("[resumeBackup] Backup is already in progress - abort");
|
|
return;
|
|
}
|
|
|
|
if (state.backupProgress == BackUpProgressEnum.inBackground) {
|
|
debugPrint("[resumeBackup] Background backup is running - abort");
|
|
return;
|
|
}
|
|
|
|
// Run backup
|
|
debugPrint("[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) {
|
|
return;
|
|
}
|
|
Box<HiveBackupAlbums> box =
|
|
await Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox);
|
|
HiveBackupAlbums? albums = box.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 = await Hive.openBox(backgroundBackupInfoBox);
|
|
state = state.copyWith(
|
|
backupProgress: previous,
|
|
selectedBackupAlbums: selectedAlbums,
|
|
excludedBackupAlbums: excludedAlbums,
|
|
backupRequireWifi: backgroundBox.get(backupRequireWifi),
|
|
backupRequireCharging: backgroundBox.get(backupRequireCharging),
|
|
);
|
|
}
|
|
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 {
|
|
debugPrint("[_updateAlbumBackupTime] failed to find album in state");
|
|
}
|
|
}
|
|
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) {
|
|
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
|
}
|
|
try {
|
|
if (Hive.isBoxOpen(backgroundBackupInfoBox)) {
|
|
await Hive.box(backgroundBackupInfoBox).close();
|
|
}
|
|
} catch (error) {
|
|
debugPrint("[_notifyBackgroundServiceCanRun] failed to close box");
|
|
}
|
|
_backgroundService.releaseLock();
|
|
}
|
|
}
|
|
}
|
|
|
|
final backupProvider =
|
|
StateNotifierProvider<BackupNotifier, BackUpState>((ref) {
|
|
return BackupNotifier(
|
|
ref.watch(backupServiceProvider),
|
|
ref.watch(serverInfoServiceProvider),
|
|
ref.watch(authenticationProvider),
|
|
ref.watch(backgroundServiceProvider),
|
|
ref,
|
|
);
|
|
});
|