backup.provider.dart 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689
  1. import 'dart:io';
  2. import 'package:cancellation_token_http/http.dart';
  3. import 'package:flutter/widgets.dart';
  4. import 'package:hive_flutter/hive_flutter.dart';
  5. import 'package:hooks_riverpod/hooks_riverpod.dart';
  6. import 'package:immich_mobile/constants/hive_box.dart';
  7. import 'package:immich_mobile/modules/backup/models/available_album.model.dart';
  8. import 'package:immich_mobile/modules/backup/models/backup_state.model.dart';
  9. import 'package:immich_mobile/modules/backup/models/current_upload_asset.model.dart';
  10. import 'package:immich_mobile/modules/backup/models/error_upload_asset.model.dart';
  11. import 'package:immich_mobile/modules/backup/models/hive_backup_albums.model.dart';
  12. import 'package:immich_mobile/modules/backup/models/hive_duplicated_assets.model.dart';
  13. import 'package:immich_mobile/modules/backup/providers/error_backup_list.provider.dart';
  14. import 'package:immich_mobile/modules/backup/background_service/background.service.dart';
  15. import 'package:immich_mobile/modules/backup/services/backup.service.dart';
  16. import 'package:immich_mobile/modules/login/models/authentication_state.model.dart';
  17. import 'package:immich_mobile/modules/login/providers/authentication.provider.dart';
  18. import 'package:immich_mobile/shared/providers/app_state.provider.dart';
  19. import 'package:immich_mobile/shared/services/server_info.service.dart';
  20. import 'package:logging/logging.dart';
  21. import 'package:openapi/api.dart';
  22. import 'package:photo_manager/photo_manager.dart';
  23. class BackupNotifier extends StateNotifier<BackUpState> {
  24. BackupNotifier(
  25. this._backupService,
  26. this._serverInfoService,
  27. this._authState,
  28. this._backgroundService,
  29. this.ref,
  30. ) : super(
  31. BackUpState(
  32. backupProgress: BackUpProgressEnum.idle,
  33. allAssetsInDatabase: const [],
  34. progressInPercentage: 0,
  35. cancelToken: CancellationToken(),
  36. backgroundBackup: false,
  37. backupRequireWifi: true,
  38. backupRequireCharging: false,
  39. serverInfo: ServerInfoResponseDto(
  40. diskAvailable: "0",
  41. diskAvailableRaw: 0,
  42. diskSize: "0",
  43. diskSizeRaw: 0,
  44. diskUsagePercentage: 0,
  45. diskUse: "0",
  46. diskUseRaw: 0,
  47. ),
  48. availableAlbums: const [],
  49. selectedBackupAlbums: const {},
  50. excludedBackupAlbums: const {},
  51. allUniqueAssets: const {},
  52. selectedAlbumsBackupAssetsIds: const {},
  53. currentUploadAsset: CurrentUploadAsset(
  54. id: '...',
  55. createdAt: DateTime.parse('2020-10-04'),
  56. fileName: '...',
  57. fileType: '...',
  58. ),
  59. ),
  60. ) {
  61. getBackupInfo();
  62. }
  63. final log = Logger('BackupNotifier');
  64. final BackupService _backupService;
  65. final ServerInfoService _serverInfoService;
  66. final AuthenticationState _authState;
  67. final BackgroundService _backgroundService;
  68. final Ref ref;
  69. var isGettingBackupInfo = false;
  70. ///
  71. /// UI INTERACTION
  72. ///
  73. /// Album selection
  74. /// Due to the overlapping assets across multiple albums on the device
  75. /// We have method to include and exclude albums
  76. /// The total unique assets will be used for backing mechanism
  77. ///
  78. void addAlbumForBackup(AvailableAlbum album) {
  79. if (state.excludedBackupAlbums.contains(album)) {
  80. removeExcludedAlbumForBackup(album);
  81. }
  82. state = state
  83. .copyWith(selectedBackupAlbums: {...state.selectedBackupAlbums, album});
  84. _updateBackupAssetCount();
  85. }
  86. void addExcludedAlbumForBackup(AvailableAlbum album) {
  87. if (state.selectedBackupAlbums.contains(album)) {
  88. removeAlbumForBackup(album);
  89. }
  90. state = state
  91. .copyWith(excludedBackupAlbums: {...state.excludedBackupAlbums, album});
  92. _updateBackupAssetCount();
  93. }
  94. void removeAlbumForBackup(AvailableAlbum album) {
  95. Set<AvailableAlbum> currentSelectedAlbums = state.selectedBackupAlbums;
  96. currentSelectedAlbums.removeWhere((a) => a == album);
  97. state = state.copyWith(selectedBackupAlbums: currentSelectedAlbums);
  98. _updateBackupAssetCount();
  99. }
  100. void removeExcludedAlbumForBackup(AvailableAlbum album) {
  101. Set<AvailableAlbum> currentExcludedAlbums = state.excludedBackupAlbums;
  102. currentExcludedAlbums.removeWhere((a) => a == album);
  103. state = state.copyWith(excludedBackupAlbums: currentExcludedAlbums);
  104. _updateBackupAssetCount();
  105. }
  106. void configureBackgroundBackup({
  107. bool? enabled,
  108. bool? requireWifi,
  109. bool? requireCharging,
  110. required void Function(String msg) onError,
  111. required void Function() onBatteryInfo,
  112. }) async {
  113. assert(enabled != null || requireWifi != null || requireCharging != null);
  114. if (Platform.isAndroid) {
  115. final bool wasEnabled = state.backgroundBackup;
  116. final bool wasWifi = state.backupRequireWifi;
  117. final bool wasCharing = state.backupRequireCharging;
  118. state = state.copyWith(
  119. backgroundBackup: enabled,
  120. backupRequireWifi: requireWifi,
  121. backupRequireCharging: requireCharging,
  122. );
  123. if (state.backgroundBackup) {
  124. bool success = true;
  125. if (!wasEnabled) {
  126. if (!await _backgroundService.isIgnoringBatteryOptimizations()) {
  127. onBatteryInfo();
  128. }
  129. success &= await _backgroundService.enableService(immediate: true);
  130. }
  131. success &= success &&
  132. await _backgroundService.configureService(
  133. requireUnmetered: state.backupRequireWifi,
  134. requireCharging: state.backupRequireCharging,
  135. );
  136. if (success) {
  137. await Hive.box(backgroundBackupInfoBox)
  138. .put(backupRequireWifi, state.backupRequireWifi);
  139. await Hive.box(backgroundBackupInfoBox)
  140. .put(backupRequireCharging, state.backupRequireCharging);
  141. } else {
  142. state = state.copyWith(
  143. backgroundBackup: wasEnabled,
  144. backupRequireWifi: wasWifi,
  145. backupRequireCharging: wasCharing,
  146. );
  147. onError("backup_controller_page_background_configure_error");
  148. }
  149. } else {
  150. final bool success = await _backgroundService.disableService();
  151. if (!success) {
  152. state = state.copyWith(backgroundBackup: wasEnabled);
  153. onError("backup_controller_page_background_configure_error");
  154. }
  155. }
  156. }
  157. }
  158. ///
  159. /// Get all album on the device
  160. /// Get all selected and excluded album from the user's persistent storage
  161. /// If this is the first time performing backup - set the default selected album to be
  162. /// the one that has all assets (`Recent` on Android, `Recents` on iOS)
  163. ///
  164. Future<void> _getBackupAlbumsInfo() async {
  165. Stopwatch stopwatch = Stopwatch()..start();
  166. // Get all albums on the device
  167. List<AvailableAlbum> availableAlbums = [];
  168. List<AssetPathEntity> albums = await PhotoManager.getAssetPathList(
  169. hasAll: true,
  170. type: RequestType.common,
  171. );
  172. log.info('Found ${albums.length} local albums');
  173. for (AssetPathEntity album in albums) {
  174. AvailableAlbum availableAlbum = AvailableAlbum(albumEntity: album);
  175. var assetCountInAlbum = await album.assetCountAsync;
  176. if (assetCountInAlbum > 0) {
  177. var assetList =
  178. await album.getAssetListRange(start: 0, end: assetCountInAlbum);
  179. if (assetList.isNotEmpty) {
  180. var thumbnailAsset = assetList.first;
  181. var thumbnailData = await thumbnailAsset
  182. .thumbnailDataWithSize(const ThumbnailSize(512, 512));
  183. availableAlbum =
  184. availableAlbum.copyWith(thumbnailData: thumbnailData);
  185. }
  186. availableAlbums.add(availableAlbum);
  187. }
  188. }
  189. state = state.copyWith(availableAlbums: availableAlbums);
  190. // Put persistent storage info into local state of the app
  191. // Get local storage on selected backup album
  192. Box<HiveBackupAlbums> backupAlbumInfoBox =
  193. Hive.box<HiveBackupAlbums>(hiveBackupInfoBox);
  194. HiveBackupAlbums? backupAlbumInfo = backupAlbumInfoBox.get(
  195. backupInfoKey,
  196. defaultValue: HiveBackupAlbums(
  197. selectedAlbumIds: [],
  198. excludedAlbumsIds: [],
  199. lastSelectedBackupTime: [],
  200. lastExcludedBackupTime: [],
  201. ),
  202. );
  203. if (backupAlbumInfo == null) {
  204. log.severe(
  205. "backupAlbumInfo == null",
  206. "Failed to get Hive backup album information",
  207. );
  208. return;
  209. }
  210. // First time backup - set isAll album is the default one for backup.
  211. if (backupAlbumInfo.selectedAlbumIds.isEmpty) {
  212. log.info("First time backup; setup 'Recent(s)' album as default");
  213. // Get album that contains all assets
  214. var list = await PhotoManager.getAssetPathList(
  215. hasAll: true,
  216. onlyAll: true,
  217. type: RequestType.common,
  218. );
  219. if (list.isEmpty) {
  220. return;
  221. }
  222. AssetPathEntity albumHasAllAssets = list.first;
  223. backupAlbumInfoBox.put(
  224. backupInfoKey,
  225. HiveBackupAlbums(
  226. selectedAlbumIds: [albumHasAllAssets.id],
  227. excludedAlbumsIds: [],
  228. lastSelectedBackupTime: [
  229. DateTime.fromMillisecondsSinceEpoch(0, isUtc: true)
  230. ],
  231. lastExcludedBackupTime: [],
  232. ),
  233. );
  234. backupAlbumInfo = backupAlbumInfoBox.get(backupInfoKey);
  235. }
  236. // Generate AssetPathEntity from id to add to local state
  237. try {
  238. Set<AvailableAlbum> selectedAlbums = {};
  239. for (var i = 0; i < backupAlbumInfo!.selectedAlbumIds.length; i++) {
  240. var albumAsset =
  241. await AssetPathEntity.fromId(backupAlbumInfo.selectedAlbumIds[i]);
  242. selectedAlbums.add(
  243. AvailableAlbum(
  244. albumEntity: albumAsset,
  245. lastBackup: backupAlbumInfo.lastSelectedBackupTime.length > i
  246. ? backupAlbumInfo.lastSelectedBackupTime[i]
  247. : DateTime.fromMillisecondsSinceEpoch(0, isUtc: true),
  248. ),
  249. );
  250. }
  251. Set<AvailableAlbum> excludedAlbums = {};
  252. for (var i = 0; i < backupAlbumInfo.excludedAlbumsIds.length; i++) {
  253. var albumAsset =
  254. await AssetPathEntity.fromId(backupAlbumInfo.excludedAlbumsIds[i]);
  255. excludedAlbums.add(
  256. AvailableAlbum(
  257. albumEntity: albumAsset,
  258. lastBackup: backupAlbumInfo.lastExcludedBackupTime.length > i
  259. ? backupAlbumInfo.lastExcludedBackupTime[i]
  260. : DateTime.fromMillisecondsSinceEpoch(0, isUtc: true),
  261. ),
  262. );
  263. }
  264. state = state.copyWith(
  265. selectedBackupAlbums: selectedAlbums,
  266. excludedBackupAlbums: excludedAlbums,
  267. );
  268. } catch (e, stackTrace) {
  269. log.severe("Failed to generate album from id", e, stackTrace);
  270. }
  271. debugPrint("_getBackupAlbumsInfo takes ${stopwatch.elapsedMilliseconds}ms");
  272. }
  273. ///
  274. /// From all the selected and albums assets
  275. /// Find the assets that are not overlapping between the two sets
  276. /// Those assets are unique and are used as the total assets
  277. ///
  278. Future<void> _updateBackupAssetCount() async {
  279. Set<String> duplicatedAssetIds = _backupService.getDuplicatedAssetIds();
  280. Set<AssetEntity> assetsFromSelectedAlbums = {};
  281. Set<AssetEntity> assetsFromExcludedAlbums = {};
  282. for (var album in state.selectedBackupAlbums) {
  283. var assets = await album.albumEntity.getAssetListRange(
  284. start: 0,
  285. end: await album.albumEntity.assetCountAsync,
  286. );
  287. assetsFromSelectedAlbums.addAll(assets);
  288. }
  289. for (var album in state.excludedBackupAlbums) {
  290. var assets = await album.albumEntity.getAssetListRange(
  291. start: 0,
  292. end: await album.albumEntity.assetCountAsync,
  293. );
  294. assetsFromExcludedAlbums.addAll(assets);
  295. }
  296. Set<AssetEntity> allUniqueAssets =
  297. assetsFromSelectedAlbums.difference(assetsFromExcludedAlbums);
  298. var allAssetsInDatabase = await _backupService.getDeviceBackupAsset();
  299. if (allAssetsInDatabase == null) {
  300. return;
  301. }
  302. // Find asset that were backup from selected albums
  303. Set<String> selectedAlbumsBackupAssets =
  304. Set.from(allUniqueAssets.map((e) => e.id));
  305. selectedAlbumsBackupAssets
  306. .removeWhere((assetId) => !allAssetsInDatabase.contains(assetId));
  307. // Remove duplicated asset from all unique assets
  308. allUniqueAssets.removeWhere(
  309. (asset) => duplicatedAssetIds.contains(asset.id),
  310. );
  311. if (allUniqueAssets.isEmpty) {
  312. log.info("Not found albums or assets on the device to backup");
  313. state = state.copyWith(
  314. backupProgress: BackUpProgressEnum.idle,
  315. allAssetsInDatabase: allAssetsInDatabase,
  316. allUniqueAssets: {},
  317. selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
  318. );
  319. return;
  320. } else {
  321. state = state.copyWith(
  322. allAssetsInDatabase: allAssetsInDatabase,
  323. allUniqueAssets: allUniqueAssets,
  324. selectedAlbumsBackupAssetsIds: selectedAlbumsBackupAssets,
  325. );
  326. }
  327. // Save to persistent storage
  328. _updatePersistentAlbumsSelection();
  329. return;
  330. }
  331. /// Get all necessary information for calculating the available albums,
  332. /// which albums are selected or excluded
  333. /// and then update the UI according to those information
  334. Future<void> getBackupInfo() async {
  335. if (!isGettingBackupInfo) {
  336. isGettingBackupInfo = true;
  337. var isEnabled = await _backgroundService.isBackgroundBackupEnabled();
  338. state = state.copyWith(backgroundBackup: isEnabled);
  339. if (state.backupProgress != BackUpProgressEnum.inBackground) {
  340. await _getBackupAlbumsInfo();
  341. await _updateServerInfo();
  342. await _updateBackupAssetCount();
  343. }
  344. isGettingBackupInfo = false;
  345. }
  346. }
  347. /// Save user selection of selected albums and excluded albums to
  348. /// Hive database
  349. void _updatePersistentAlbumsSelection() {
  350. final epoch = DateTime.fromMillisecondsSinceEpoch(0, isUtc: true);
  351. Box<HiveBackupAlbums> backupAlbumInfoBox =
  352. Hive.box<HiveBackupAlbums>(hiveBackupInfoBox);
  353. backupAlbumInfoBox.put(
  354. backupInfoKey,
  355. HiveBackupAlbums(
  356. selectedAlbumIds: state.selectedBackupAlbums.map((e) => e.id).toList(),
  357. excludedAlbumsIds: state.excludedBackupAlbums.map((e) => e.id).toList(),
  358. lastSelectedBackupTime: state.selectedBackupAlbums
  359. .map((e) => e.lastBackup ?? epoch)
  360. .toList(),
  361. lastExcludedBackupTime: state.excludedBackupAlbums
  362. .map((e) => e.lastBackup ?? epoch)
  363. .toList(),
  364. ),
  365. );
  366. }
  367. /// Invoke backup process
  368. Future<void> startBackupProcess() async {
  369. assert(state.backupProgress == BackUpProgressEnum.idle);
  370. state = state.copyWith(backupProgress: BackUpProgressEnum.inProgress);
  371. await getBackupInfo();
  372. var authResult = await PhotoManager.requestPermissionExtend();
  373. if (authResult.isAuth) {
  374. await PhotoManager.clearFileCache();
  375. if (state.allUniqueAssets.isEmpty) {
  376. log.info("No Asset On Device - Abort Backup Process");
  377. state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
  378. return;
  379. }
  380. Set<AssetEntity> assetsWillBeBackup = Set.from(state.allUniqueAssets);
  381. // Remove item that has already been backed up
  382. for (var assetId in state.allAssetsInDatabase) {
  383. assetsWillBeBackup.removeWhere((e) => e.id == assetId);
  384. }
  385. if (assetsWillBeBackup.isEmpty) {
  386. state = state.copyWith(backupProgress: BackUpProgressEnum.idle);
  387. }
  388. // Perform Backup
  389. state = state.copyWith(cancelToken: CancellationToken());
  390. await _backupService.backupAsset(
  391. assetsWillBeBackup,
  392. state.cancelToken,
  393. _onAssetUploaded,
  394. _onUploadProgress,
  395. _onSetCurrentBackupAsset,
  396. _onBackupError,
  397. );
  398. await _notifyBackgroundServiceCanRun();
  399. } else {
  400. PhotoManager.openSetting();
  401. }
  402. }
  403. void _onBackupError(ErrorUploadAsset errorAssetInfo) {
  404. ref.watch(errorBackupListProvider.notifier).add(errorAssetInfo);
  405. }
  406. void _onSetCurrentBackupAsset(CurrentUploadAsset currentUploadAsset) {
  407. state = state.copyWith(currentUploadAsset: currentUploadAsset);
  408. }
  409. void cancelBackup() {
  410. if (state.backupProgress != BackUpProgressEnum.inProgress) {
  411. _notifyBackgroundServiceCanRun();
  412. }
  413. state.cancelToken.cancel();
  414. state = state.copyWith(
  415. backupProgress: BackUpProgressEnum.idle,
  416. progressInPercentage: 0.0,
  417. );
  418. }
  419. void _onAssetUploaded(
  420. String deviceAssetId,
  421. String deviceId,
  422. bool isDuplicated,
  423. ) {
  424. if (isDuplicated) {
  425. state = state.copyWith(
  426. allUniqueAssets: state.allUniqueAssets
  427. .where((asset) => asset.id != deviceAssetId)
  428. .toSet(),
  429. );
  430. } else {
  431. state = state.copyWith(
  432. selectedAlbumsBackupAssetsIds: {
  433. ...state.selectedAlbumsBackupAssetsIds,
  434. deviceAssetId
  435. },
  436. allAssetsInDatabase: [...state.allAssetsInDatabase, deviceAssetId],
  437. );
  438. }
  439. if (state.allUniqueAssets.length -
  440. state.selectedAlbumsBackupAssetsIds.length ==
  441. 0) {
  442. final latestAssetBackup =
  443. state.allUniqueAssets.map((e) => e.modifiedDateTime).reduce(
  444. (v, e) => e.isAfter(v) ? e : v,
  445. );
  446. state = state.copyWith(
  447. selectedBackupAlbums: state.selectedBackupAlbums
  448. .map((e) => e.copyWith(lastBackup: latestAssetBackup))
  449. .toSet(),
  450. excludedBackupAlbums: state.excludedBackupAlbums
  451. .map((e) => e.copyWith(lastBackup: latestAssetBackup))
  452. .toSet(),
  453. backupProgress: BackUpProgressEnum.done,
  454. progressInPercentage: 0.0,
  455. );
  456. _updatePersistentAlbumsSelection();
  457. }
  458. _updateServerInfo();
  459. }
  460. void _onUploadProgress(int sent, int total) {
  461. state = state.copyWith(
  462. progressInPercentage: (sent.toDouble() / total.toDouble() * 100),
  463. );
  464. }
  465. Future<void> _updateServerInfo() async {
  466. var serverInfo = await _serverInfoService.getServerInfo();
  467. // Update server info
  468. if (serverInfo != null) {
  469. state = state.copyWith(
  470. serverInfo: serverInfo,
  471. );
  472. }
  473. }
  474. Future<void> _resumeBackup() async {
  475. // Check if user is login
  476. var accessKey = Hive.box(userInfoBox).get(accessTokenKey);
  477. // User has been logged out return
  478. if (accessKey == null || !_authState.isAuthenticated) {
  479. log.info("[_resumeBackup] not authenticated - abort");
  480. return;
  481. }
  482. // Check if this device is enable backup by the user
  483. if ((_authState.deviceInfo.deviceId == _authState.deviceId) &&
  484. _authState.deviceInfo.isAutoBackup) {
  485. // check if backup is alreayd in process - then return
  486. if (state.backupProgress == BackUpProgressEnum.inProgress) {
  487. log.info("[_resumeBackup] Backup is already in progress - abort");
  488. return;
  489. }
  490. if (state.backupProgress == BackUpProgressEnum.inBackground) {
  491. log.info("[_resumeBackup] Background backup is running - abort");
  492. return;
  493. }
  494. // Run backup
  495. log.info("[_resumeBackup] Start back up");
  496. await startBackupProcess();
  497. }
  498. return;
  499. }
  500. Future<void> resumeBackup() async {
  501. if (Platform.isAndroid) {
  502. // assumes the background service is currently running
  503. // if true, waits until it has stopped to update the app state from HiveDB
  504. // before actually resuming backup by calling the internal `_resumeBackup`
  505. final BackUpProgressEnum previous = state.backupProgress;
  506. state = state.copyWith(backupProgress: BackUpProgressEnum.inBackground);
  507. final bool hasLock = await _backgroundService.acquireLock();
  508. if (!hasLock) {
  509. log.warning("WARNING [resumeBackup] failed to acquireLock");
  510. return;
  511. }
  512. await Future.wait([
  513. Hive.openBox<HiveBackupAlbums>(hiveBackupInfoBox),
  514. Hive.openBox<HiveDuplicatedAssets>(duplicatedAssetsBox),
  515. Hive.openBox(backgroundBackupInfoBox),
  516. ]);
  517. final HiveBackupAlbums? albums =
  518. Hive.box<HiveBackupAlbums>(hiveBackupInfoBox).get(backupInfoKey);
  519. Set<AvailableAlbum> selectedAlbums = state.selectedBackupAlbums;
  520. Set<AvailableAlbum> excludedAlbums = state.excludedBackupAlbums;
  521. if (albums != null) {
  522. selectedAlbums = _updateAlbumsBackupTime(
  523. selectedAlbums,
  524. albums.selectedAlbumIds,
  525. albums.lastSelectedBackupTime,
  526. );
  527. excludedAlbums = _updateAlbumsBackupTime(
  528. excludedAlbums,
  529. albums.excludedAlbumsIds,
  530. albums.lastExcludedBackupTime,
  531. );
  532. }
  533. final Box backgroundBox = Hive.box(backgroundBackupInfoBox);
  534. state = state.copyWith(
  535. backupProgress: previous,
  536. selectedBackupAlbums: selectedAlbums,
  537. excludedBackupAlbums: excludedAlbums,
  538. backupRequireWifi: backgroundBox.get(backupRequireWifi),
  539. backupRequireCharging: backgroundBox.get(backupRequireCharging),
  540. );
  541. }
  542. return _resumeBackup();
  543. }
  544. Set<AvailableAlbum> _updateAlbumsBackupTime(
  545. Set<AvailableAlbum> albums,
  546. List<String> ids,
  547. List<DateTime> times,
  548. ) {
  549. Set<AvailableAlbum> result = {};
  550. for (int i = 0; i < ids.length; i++) {
  551. try {
  552. AvailableAlbum a = albums.firstWhere((e) => e.id == ids[i]);
  553. result.add(a.copyWith(lastBackup: times[i]));
  554. } on StateError {
  555. log.severe(
  556. "[_updateAlbumBackupTime] failed to find album in state",
  557. "State Error",
  558. StackTrace.current,
  559. );
  560. }
  561. }
  562. return result;
  563. }
  564. Future<void> _notifyBackgroundServiceCanRun() async {
  565. const allowedStates = [
  566. AppStateEnum.inactive,
  567. AppStateEnum.paused,
  568. AppStateEnum.detached,
  569. ];
  570. if (Platform.isAndroid &&
  571. allowedStates.contains(ref.read(appStateProvider.notifier).state)) {
  572. try {
  573. if (Hive.isBoxOpen(hiveBackupInfoBox)) {
  574. await Hive.box<HiveBackupAlbums>(hiveBackupInfoBox).close();
  575. }
  576. } catch (error) {
  577. log.info("[_notifyBackgroundServiceCanRun] failed to close box");
  578. }
  579. try {
  580. if (Hive.isBoxOpen(duplicatedAssetsBox)) {
  581. await Hive.box<HiveDuplicatedAssets>(duplicatedAssetsBox).close();
  582. }
  583. } catch (error, stackTrace) {
  584. log.severe(
  585. "[_notifyBackgroundServiceCanRun] failed to close box",
  586. error,
  587. stackTrace,
  588. );
  589. }
  590. try {
  591. if (Hive.isBoxOpen(backgroundBackupInfoBox)) {
  592. await Hive.box(backgroundBackupInfoBox).close();
  593. }
  594. } catch (error, stackTrace) {
  595. log.severe(
  596. "[_notifyBackgroundServiceCanRun] failed to close box",
  597. error,
  598. stackTrace,
  599. );
  600. }
  601. _backgroundService.releaseLock();
  602. }
  603. }
  604. }
  605. final backupProvider =
  606. StateNotifierProvider<BackupNotifier, BackUpState>((ref) {
  607. return BackupNotifier(
  608. ref.watch(backupServiceProvider),
  609. ref.watch(serverInfoServiceProvider),
  610. ref.watch(authenticationProvider),
  611. ref.watch(backgroundServiceProvider),
  612. ref,
  613. );
  614. });