From afc8fbf7b21f9ae2a5bc0dc87a536fb7101d4e71 Mon Sep 17 00:00:00 2001 From: ashilkn Date: Wed, 26 Feb 2025 19:41:57 +0530 Subject: [PATCH] [mob][photos] If free up space fails the first time, retry after removing non-existing assets (only for android) --- mobile/lib/ui/tools/free_space_page.dart | 11 +++- mobile/lib/utils/delete_file_util.dart | 79 +++++++++++++++++++++++- 2 files changed, 88 insertions(+), 2 deletions(-) diff --git a/mobile/lib/ui/tools/free_space_page.dart b/mobile/lib/ui/tools/free_space_page.dart index 3b60096d1f..a8d6a42742 100644 --- a/mobile/lib/ui/tools/free_space_page.dart +++ b/mobile/lib/ui/tools/free_space_page.dart @@ -1,3 +1,5 @@ +import "dart:io"; + import 'package:flutter/material.dart'; import 'package:intl/intl.dart'; import 'package:logging/logging.dart'; @@ -6,6 +8,7 @@ import 'package:photos/models/backup_status.dart'; import 'package:photos/ui/common/gradient_button.dart'; import 'package:photos/utils/data_util.dart'; import 'package:photos/utils/delete_file_util.dart'; +import "package:photos/utils/toast_util.dart"; class FreeSpacePage extends StatefulWidget { final BackupStatus status; @@ -163,9 +166,15 @@ class _FreeSpacePageState extends State { } Future _freeStorage(BackupStatus status) async { - final result = await deleteLocalFiles(context, status.localIDs); + bool result = await deleteLocalFiles(context, status.localIDs); + if (result == false && Platform.isAndroid) { + result = await retryFreeUpSpaceAfterRemovingNonExistingAssets(context); + } + if (result) { Navigator.of(context).pop(true); + } else { + showToast(context, S.of(context).couldNotFreeUpSpace); } } } diff --git a/mobile/lib/utils/delete_file_util.dart b/mobile/lib/utils/delete_file_util.dart index 03e5455a88..a715c3f154 100644 --- a/mobile/lib/utils/delete_file_util.dart +++ b/mobile/lib/utils/delete_file_util.dart @@ -14,10 +14,12 @@ import "package:photos/events/force_reload_trash_page_event.dart"; import 'package:photos/events/local_photos_updated_event.dart'; import "package:photos/generated/l10n.dart"; import 'package:photos/models/api/collection/trash_item_request.dart'; +import "package:photos/models/backup_status.dart"; import 'package:photos/models/file/file.dart'; import "package:photos/models/files_split.dart"; import 'package:photos/models/selected_files.dart'; import "package:photos/service_locator.dart"; +import "package:photos/services/local_sync_service.dart"; import 'package:photos/services/remote_sync_service.dart'; import 'package:photos/services/sync_service.dart'; import 'package:photos/ui/common/linear_progress_dialog.dart'; @@ -333,6 +335,8 @@ Future deleteLocalFiles( final List alreadyDeletedIDs = []; // to ignore already deleted files try { + final dialog = createProgressDialog(context, "Loading..."); + await dialog.show(); for (final file in files) { if (!(await _localFileExist(file))) { _logger.warning("Already deleted " + file.toString()); @@ -347,6 +351,8 @@ Future deleteLocalFiles( deletedIDs.addAll(alreadyDeletedIDs); deletedIDs.addAll(await _tryDeleteSharedMediaFiles(localSharedMediaIDs)); + await dialog.hide(); + final bool shouldDeleteInBatches = await isAndroidSDKVersionLowerThan(android11SDKINT); if (shouldDeleteInBatches) { @@ -389,7 +395,78 @@ Future deleteLocalFiles( } } catch (e, s) { _logger.severe("Could not delete local files", e, s); - showToast(context, S.of(context).couldNotFreeUpSpace); + return false; + } +} + +/// Only to be used on Android +Future retryFreeUpSpaceAfterRemovingNonExistingAssets( + BuildContext context, +) async { + _logger.info("Retrying free up space after removing non-existing assets"); + try { + final dialog = + createProgressDialog(context, "Please wait, this will take a while..."); + await dialog.show(); + + final stopwatch = Stopwatch()..start(); + final res = await PhotoManager.editor.android.removeAllNoExistsAsset(); + if (res == false) { + _logger.warning("Failed to remove non-existing assets"); + } + _logger.info( + "removeAllNoExistsAsset took: ${stopwatch.elapsedMilliseconds}ms", + ); + await LocalSyncService.instance.sync(); + + late final BackupStatus status; + final List deletedIDs = []; + final List localAssetIDs = []; + final List localSharedMediaIDs = []; + status = await SyncService.instance.getBackupStatus(); + + for (String localID in status.localIDs) { + if (localID.startsWith(oldSharedMediaIdentifier) || + localID.startsWith(sharedMediaIdentifier)) { + localSharedMediaIDs.add(localID); + } else { + localAssetIDs.add(localID); + } + } + deletedIDs.addAll(await _tryDeleteSharedMediaFiles(localSharedMediaIDs)); + + await dialog.hide(); + + final bool shouldDeleteInBatches = + await isAndroidSDKVersionLowerThan(android11SDKINT); + if (shouldDeleteInBatches) { + _logger.info("Deleting in batches"); + deletedIDs + .addAll(await deleteLocalFilesInBatches(context, localAssetIDs)); + } else { + _logger.info("Deleting in one shot"); + deletedIDs + .addAll(await _deleteLocalFilesInOneShot(context, localAssetIDs)); + } + + if (deletedIDs.isNotEmpty) { + final deletedFiles = await FilesDB.instance.getLocalFiles(deletedIDs); + await FilesDB.instance.deleteLocalFiles(deletedIDs); + _logger.info(deletedFiles.length.toString() + " files deleted locally"); + Bus.instance.fire( + LocalPhotosUpdatedEvent(deletedFiles, source: "deleteLocal"), + ); + return true; + } else { + //On android 10, even if files were deleted, deletedIDs is empty. + //This is a workaround so that users are not shown an error message on + //android 10 + if (!await isAndroidSDKVersionLowerThan(android11SDKINT)) { + return false; + } + return true; + } + } catch (e) { return false; } }