2021-05-03 21:14:44 +00:00
|
|
|
import 'dart:async';
|
2021-06-28 12:54:57 +00:00
|
|
|
import 'dart:io';
|
|
|
|
import 'dart:math';
|
2021-05-03 21:14:44 +00:00
|
|
|
|
2021-06-28 15:52:21 +00:00
|
|
|
import 'package:flutter/material.dart';
|
2021-05-03 21:14:44 +00:00
|
|
|
import 'package:logging/logging.dart';
|
|
|
|
import 'package:photo_manager/photo_manager.dart';
|
2021-06-28 19:03:53 +00:00
|
|
|
import 'package:photos/core/constants.dart';
|
2021-05-03 21:14:44 +00:00
|
|
|
import 'package:photos/core/event_bus.dart';
|
|
|
|
import 'package:photos/db/files_db.dart';
|
|
|
|
import 'package:photos/events/collection_updated_event.dart';
|
|
|
|
import 'package:photos/events/files_updated_event.dart';
|
2023-02-22 07:22:57 +00:00
|
|
|
import "package:photos/events/force_reload_trash_page_event.dart";
|
2021-05-03 21:14:44 +00:00
|
|
|
import 'package:photos/events/local_photos_updated_event.dart';
|
2023-04-07 06:46:18 +00:00
|
|
|
import "package:photos/generated/l10n.dart";
|
2023-08-25 04:39:30 +00:00
|
|
|
import 'package:photos/models/file/file.dart';
|
2023-09-02 06:57:04 +00:00
|
|
|
import "package:photos/models/files_split.dart";
|
2022-12-05 11:08:21 +00:00
|
|
|
import 'package:photos/models/selected_files.dart';
|
2021-10-12 20:25:23 +00:00
|
|
|
import 'package:photos/models/trash_item_request.dart';
|
2021-06-14 16:24:15 +00:00
|
|
|
import 'package:photos/services/remote_sync_service.dart';
|
2021-05-03 21:14:44 +00:00
|
|
|
import 'package:photos/services/sync_service.dart';
|
2021-10-12 20:25:23 +00:00
|
|
|
import 'package:photos/services/trash_sync_service.dart';
|
2022-07-01 14:18:05 +00:00
|
|
|
import 'package:photos/ui/common/linear_progress_dialog.dart';
|
2023-01-06 07:24:00 +00:00
|
|
|
import 'package:photos/ui/components/action_sheet_widget.dart';
|
2023-03-10 08:08:51 +00:00
|
|
|
import 'package:photos/ui/components/buttons/button_widget.dart';
|
2023-01-06 07:24:00 +00:00
|
|
|
import 'package:photos/ui/components/models/button_type.dart';
|
2023-05-17 05:32:00 +00:00
|
|
|
import "package:photos/utils/device_info.dart";
|
2021-05-03 21:14:44 +00:00
|
|
|
import 'package:photos/utils/dialog_util.dart';
|
2021-10-30 05:51:23 +00:00
|
|
|
import 'package:photos/utils/file_util.dart';
|
2021-05-03 21:14:44 +00:00
|
|
|
import 'package:photos/utils/toast_util.dart';
|
|
|
|
|
|
|
|
final _logger = Logger("DeleteFileUtil");
|
|
|
|
|
|
|
|
Future<void> deleteFilesFromEverywhere(
|
2022-06-11 08:23:52 +00:00
|
|
|
BuildContext context,
|
2023-08-24 16:56:24 +00:00
|
|
|
List<EnteFile> files,
|
2022-06-11 08:23:52 +00:00
|
|
|
) async {
|
2022-09-12 11:04:48 +00:00
|
|
|
_logger.info("Trying to deleteFilesFromEverywhere " + files.toString());
|
2021-07-24 14:41:28 +00:00
|
|
|
final List<String> localAssetIDs = [];
|
|
|
|
final List<String> localSharedMediaIDs = [];
|
2021-06-13 19:49:25 +00:00
|
|
|
final List<String> alreadyDeletedIDs = []; // to ignore already deleted files
|
2022-05-12 12:19:36 +00:00
|
|
|
bool hasLocalOnlyFiles = false;
|
2021-05-03 21:14:44 +00:00
|
|
|
for (final file in files) {
|
2021-09-15 20:50:13 +00:00
|
|
|
if (file.localID != null) {
|
2021-07-24 14:41:28 +00:00
|
|
|
if (!(await _localFileExist(file))) {
|
2021-06-13 20:09:06 +00:00
|
|
|
_logger.warning("Already deleted " + file.toString());
|
2022-12-30 10:42:54 +00:00
|
|
|
alreadyDeletedIDs.add(file.localID!);
|
2022-09-23 01:48:25 +00:00
|
|
|
} else if (file.isSharedMediaToAppSandbox) {
|
2022-12-30 10:42:54 +00:00
|
|
|
localSharedMediaIDs.add(file.localID!);
|
2021-06-13 19:49:25 +00:00
|
|
|
} else {
|
2022-12-30 10:42:54 +00:00
|
|
|
localAssetIDs.add(file.localID!);
|
2021-06-13 19:49:25 +00:00
|
|
|
}
|
2021-05-03 21:14:44 +00:00
|
|
|
}
|
2022-05-12 12:19:36 +00:00
|
|
|
if (file.uploadedFileID == null) {
|
|
|
|
hasLocalOnlyFiles = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (hasLocalOnlyFiles && Platform.isAndroid) {
|
|
|
|
final shouldProceed = await shouldProceedWithDeletion(context);
|
|
|
|
if (!shouldProceed) {
|
|
|
|
return;
|
|
|
|
}
|
2021-05-03 21:14:44 +00:00
|
|
|
}
|
2021-09-15 20:44:52 +00:00
|
|
|
Set<String> deletedIDs = <String>{};
|
2021-05-03 21:14:44 +00:00
|
|
|
try {
|
2021-09-15 20:50:13 +00:00
|
|
|
deletedIDs =
|
|
|
|
(await PhotoManager.editor.deleteWithIds(localAssetIDs)).toSet();
|
2021-05-03 21:14:44 +00:00
|
|
|
} catch (e, s) {
|
|
|
|
_logger.severe("Could not delete file", e, s);
|
|
|
|
}
|
2021-07-24 14:41:28 +00:00
|
|
|
deletedIDs.addAll(await _tryDeleteSharedMediaFiles(localSharedMediaIDs));
|
2021-09-15 20:44:52 +00:00
|
|
|
final updatedCollectionIDs = <int>{};
|
2021-10-12 20:25:23 +00:00
|
|
|
final List<TrashRequest> uploadedFilesToBeTrashed = [];
|
2023-08-24 16:56:24 +00:00
|
|
|
final List<EnteFile> deletedFiles = [];
|
2021-05-03 21:14:44 +00:00
|
|
|
for (final file in files) {
|
|
|
|
if (file.localID != null) {
|
2021-06-13 19:49:25 +00:00
|
|
|
// Remove only those files that have already been removed from disk
|
|
|
|
if (deletedIDs.contains(file.localID) ||
|
|
|
|
alreadyDeletedIDs.contains(file.localID)) {
|
2021-05-03 21:14:44 +00:00
|
|
|
deletedFiles.add(file);
|
2021-10-21 12:08:12 +00:00
|
|
|
if (file.uploadedFileID != null) {
|
2021-10-26 13:32:33 +00:00
|
|
|
uploadedFilesToBeTrashed
|
2022-12-30 10:42:54 +00:00
|
|
|
.add(TrashRequest(file.uploadedFileID!, file.collectionID!));
|
|
|
|
updatedCollectionIDs.add(file.collectionID!);
|
2021-05-03 21:14:44 +00:00
|
|
|
} else {
|
2021-07-24 17:22:46 +00:00
|
|
|
await FilesDB.instance.deleteLocalFile(file);
|
2021-05-03 21:14:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
} else {
|
2022-12-30 10:42:54 +00:00
|
|
|
updatedCollectionIDs.add(file.collectionID!);
|
2021-05-03 21:26:23 +00:00
|
|
|
deletedFiles.add(file);
|
2021-10-26 13:32:33 +00:00
|
|
|
uploadedFilesToBeTrashed
|
2022-12-30 10:42:54 +00:00
|
|
|
.add(TrashRequest(file.uploadedFileID!, file.collectionID!));
|
2021-05-03 21:14:44 +00:00
|
|
|
}
|
|
|
|
}
|
2021-10-12 20:25:23 +00:00
|
|
|
if (uploadedFilesToBeTrashed.isNotEmpty) {
|
2021-05-03 21:14:44 +00:00
|
|
|
try {
|
2021-10-26 13:32:33 +00:00
|
|
|
final fileIDs =
|
|
|
|
uploadedFilesToBeTrashed.map((item) => item.fileID).toList();
|
|
|
|
await TrashSyncService.instance
|
|
|
|
.trashFilesOnServer(uploadedFilesToBeTrashed);
|
|
|
|
await FilesDB.instance.deleteMultipleUploadedFiles(fileIDs);
|
2021-05-03 21:14:44 +00:00
|
|
|
} catch (e) {
|
|
|
|
_logger.severe(e);
|
2023-11-30 05:19:35 +00:00
|
|
|
showGenericErrorDialog(context: context, error: e);
|
2021-09-15 20:44:52 +00:00
|
|
|
rethrow;
|
2021-05-03 21:14:44 +00:00
|
|
|
}
|
|
|
|
for (final collectionID in updatedCollectionIDs) {
|
2022-06-11 08:23:52 +00:00
|
|
|
Bus.instance.fire(
|
|
|
|
CollectionUpdatedEvent(
|
|
|
|
collectionID,
|
|
|
|
deletedFiles
|
|
|
|
.where((file) => file.collectionID == collectionID)
|
|
|
|
.toList(),
|
2022-11-11 11:30:37 +00:00
|
|
|
"deleteFilesEverywhere",
|
2022-06-11 08:23:52 +00:00
|
|
|
type: EventType.deletedFromEverywhere,
|
|
|
|
),
|
|
|
|
);
|
2021-05-03 21:14:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
if (deletedFiles.isNotEmpty) {
|
2022-06-11 08:23:52 +00:00
|
|
|
Bus.instance.fire(
|
|
|
|
LocalPhotosUpdatedEvent(
|
|
|
|
deletedFiles,
|
|
|
|
type: EventType.deletedFromEverywhere,
|
2022-11-11 13:09:22 +00:00
|
|
|
source: "deleteFilesEverywhere",
|
2022-06-11 08:23:52 +00:00
|
|
|
),
|
|
|
|
);
|
2022-05-12 12:19:36 +00:00
|
|
|
if (hasLocalOnlyFiles && Platform.isAndroid) {
|
2023-04-07 06:46:18 +00:00
|
|
|
showShortToast(context, S.of(context).filesDeleted);
|
2022-05-12 12:19:36 +00:00
|
|
|
} else {
|
2023-04-07 06:46:18 +00:00
|
|
|
showShortToast(context, S.of(context).movedToTrash);
|
2022-05-12 12:19:36 +00:00
|
|
|
}
|
2021-05-03 21:14:44 +00:00
|
|
|
}
|
2021-10-12 20:25:23 +00:00
|
|
|
if (uploadedFilesToBeTrashed.isNotEmpty) {
|
2021-06-14 16:24:15 +00:00
|
|
|
RemoteSyncService.instance.sync(silently: true);
|
2021-05-03 21:14:44 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-09-15 20:50:13 +00:00
|
|
|
Future<void> deleteFilesFromRemoteOnly(
|
2022-06-11 08:23:52 +00:00
|
|
|
BuildContext context,
|
2023-08-24 16:56:24 +00:00
|
|
|
List<EnteFile> files,
|
2022-06-11 08:23:52 +00:00
|
|
|
) async {
|
2021-10-18 04:43:25 +00:00
|
|
|
files.removeWhere((element) => element.uploadedFileID == null);
|
2021-10-26 13:32:33 +00:00
|
|
|
if (files.isEmpty) {
|
2023-04-07 06:46:18 +00:00
|
|
|
showToast(context, S.of(context).selectedFilesAreNotOnEnte);
|
2021-10-20 14:02:10 +00:00
|
|
|
return;
|
|
|
|
}
|
2022-06-11 08:23:52 +00:00
|
|
|
_logger.info(
|
2022-09-12 11:04:48 +00:00
|
|
|
"Trying to deleteFilesFromRemoteOnly " +
|
|
|
|
files.map((f) => f.uploadedFileID).toString(),
|
2022-06-11 08:23:52 +00:00
|
|
|
);
|
2021-09-15 20:50:13 +00:00
|
|
|
final updatedCollectionIDs = <int>{};
|
2021-10-18 04:43:25 +00:00
|
|
|
final List<int> uploadedFileIDs = [];
|
|
|
|
final List<TrashRequest> trashRequests = [];
|
2021-09-15 20:50:13 +00:00
|
|
|
for (final file in files) {
|
2022-12-30 10:42:54 +00:00
|
|
|
updatedCollectionIDs.add(file.collectionID!);
|
|
|
|
uploadedFileIDs.add(file.uploadedFileID!);
|
|
|
|
trashRequests.add(TrashRequest(file.uploadedFileID!, file.collectionID!));
|
2021-09-15 20:50:13 +00:00
|
|
|
}
|
|
|
|
try {
|
2021-10-18 04:43:25 +00:00
|
|
|
await TrashSyncService.instance.trashFilesOnServer(trashRequests);
|
|
|
|
await FilesDB.instance.deleteMultipleUploadedFiles(uploadedFileIDs);
|
2021-09-23 07:03:13 +00:00
|
|
|
} catch (e, s) {
|
|
|
|
_logger.severe("Failed to delete files from remote", e, s);
|
2023-11-30 05:19:35 +00:00
|
|
|
showGenericErrorDialog(context: context, error: e);
|
2021-09-15 20:50:13 +00:00
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
for (final collectionID in updatedCollectionIDs) {
|
2022-06-11 08:23:52 +00:00
|
|
|
Bus.instance.fire(
|
|
|
|
CollectionUpdatedEvent(
|
|
|
|
collectionID,
|
|
|
|
files.where((file) => file.collectionID == collectionID).toList(),
|
2022-11-11 11:30:37 +00:00
|
|
|
"deleteFromRemoteOnly",
|
2022-06-11 08:23:52 +00:00
|
|
|
type: EventType.deletedFromRemote,
|
|
|
|
),
|
|
|
|
);
|
2021-09-15 20:50:13 +00:00
|
|
|
}
|
2022-11-11 13:09:22 +00:00
|
|
|
Bus.instance.fire(
|
|
|
|
LocalPhotosUpdatedEvent(
|
|
|
|
files,
|
|
|
|
type: EventType.deletedFromRemote,
|
|
|
|
source: "deleteFromRemoteOnly",
|
|
|
|
),
|
|
|
|
);
|
2021-10-20 14:50:51 +00:00
|
|
|
SyncService.instance.sync();
|
2021-09-15 20:50:13 +00:00
|
|
|
RemoteSyncService.instance.sync(silently: true);
|
|
|
|
}
|
|
|
|
|
2021-05-03 21:14:44 +00:00
|
|
|
Future<void> deleteFilesOnDeviceOnly(
|
2022-06-11 08:23:52 +00:00
|
|
|
BuildContext context,
|
2023-08-24 16:56:24 +00:00
|
|
|
List<EnteFile> files,
|
2022-06-11 08:23:52 +00:00
|
|
|
) async {
|
2022-09-12 11:04:48 +00:00
|
|
|
_logger.info("Trying to deleteFilesOnDeviceOnly" + files.toString());
|
2021-07-24 14:41:28 +00:00
|
|
|
final List<String> localAssetIDs = [];
|
|
|
|
final List<String> localSharedMediaIDs = [];
|
2021-06-13 19:49:25 +00:00
|
|
|
final List<String> alreadyDeletedIDs = []; // to ignore already deleted files
|
2022-05-12 12:19:36 +00:00
|
|
|
bool hasLocalOnlyFiles = false;
|
2021-05-03 21:14:44 +00:00
|
|
|
for (final file in files) {
|
|
|
|
if (file.localID != null) {
|
2021-07-24 14:41:28 +00:00
|
|
|
if (!(await _localFileExist(file))) {
|
2021-06-13 20:09:06 +00:00
|
|
|
_logger.warning("Already deleted " + file.toString());
|
2022-12-30 10:42:54 +00:00
|
|
|
alreadyDeletedIDs.add(file.localID!);
|
2022-09-23 01:48:25 +00:00
|
|
|
} else if (file.isSharedMediaToAppSandbox) {
|
2022-12-30 10:42:54 +00:00
|
|
|
localSharedMediaIDs.add(file.localID!);
|
2021-06-13 19:49:25 +00:00
|
|
|
} else {
|
2022-12-30 10:42:54 +00:00
|
|
|
localAssetIDs.add(file.localID!);
|
2021-06-13 19:49:25 +00:00
|
|
|
}
|
2021-05-03 21:14:44 +00:00
|
|
|
}
|
2022-05-12 12:19:36 +00:00
|
|
|
if (file.uploadedFileID == null) {
|
|
|
|
hasLocalOnlyFiles = true;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
if (hasLocalOnlyFiles && Platform.isAndroid) {
|
|
|
|
final shouldProceed = await shouldProceedWithDeletion(context);
|
|
|
|
if (!shouldProceed) {
|
|
|
|
return;
|
|
|
|
}
|
2021-05-03 21:14:44 +00:00
|
|
|
}
|
2021-09-15 20:44:52 +00:00
|
|
|
Set<String> deletedIDs = <String>{};
|
2021-05-25 12:26:31 +00:00
|
|
|
try {
|
2021-09-15 20:50:13 +00:00
|
|
|
deletedIDs =
|
|
|
|
(await PhotoManager.editor.deleteWithIds(localAssetIDs)).toSet();
|
2021-05-25 12:26:31 +00:00
|
|
|
} catch (e, s) {
|
|
|
|
_logger.severe("Could not delete file", e, s);
|
|
|
|
}
|
2021-07-24 14:41:28 +00:00
|
|
|
deletedIDs.addAll(await _tryDeleteSharedMediaFiles(localSharedMediaIDs));
|
2023-08-24 16:56:24 +00:00
|
|
|
final List<EnteFile> deletedFiles = [];
|
2021-05-03 21:14:44 +00:00
|
|
|
for (final file in files) {
|
|
|
|
// Remove only those files that have been removed from disk
|
2021-06-13 19:49:25 +00:00
|
|
|
if (deletedIDs.contains(file.localID) ||
|
|
|
|
alreadyDeletedIDs.contains(file.localID)) {
|
2021-05-03 21:14:44 +00:00
|
|
|
deletedFiles.add(file);
|
|
|
|
file.localID = null;
|
|
|
|
FilesDB.instance.update(file);
|
|
|
|
}
|
|
|
|
}
|
2021-06-13 19:49:25 +00:00
|
|
|
if (deletedFiles.isNotEmpty || alreadyDeletedIDs.isNotEmpty) {
|
2022-06-11 08:23:52 +00:00
|
|
|
Bus.instance.fire(
|
|
|
|
LocalPhotosUpdatedEvent(
|
|
|
|
deletedFiles,
|
|
|
|
type: EventType.deletedFromDevice,
|
2022-11-11 13:09:22 +00:00
|
|
|
source: "deleteFilesOnDeviceOnly",
|
2022-06-11 08:23:52 +00:00
|
|
|
),
|
|
|
|
);
|
2021-05-03 21:14:44 +00:00
|
|
|
}
|
|
|
|
}
|
2021-06-28 10:12:12 +00:00
|
|
|
|
2023-08-24 16:56:24 +00:00
|
|
|
Future<bool> deleteFromTrash(BuildContext context, List<EnteFile> files) async {
|
2023-01-27 08:42:15 +00:00
|
|
|
bool didDeletionStart = false;
|
2023-02-09 13:18:01 +00:00
|
|
|
final actionResult = await showChoiceActionSheet(
|
2022-06-11 08:23:52 +00:00
|
|
|
context,
|
2023-04-07 06:46:18 +00:00
|
|
|
title: S.of(context).permanentlyDelete,
|
|
|
|
body: S.of(context).thisActionCannotBeUndone,
|
|
|
|
firstButtonLabel: S.of(context).delete,
|
2023-01-09 12:02:13 +00:00
|
|
|
isCritical: true,
|
|
|
|
firstButtonOnTap: () async {
|
|
|
|
try {
|
2023-01-27 08:42:15 +00:00
|
|
|
didDeletionStart = true;
|
2023-01-09 12:02:13 +00:00
|
|
|
await TrashSyncService.instance.deleteFromTrash(files);
|
|
|
|
Bus.instance.fire(
|
|
|
|
FilesUpdatedEvent(
|
|
|
|
files,
|
|
|
|
type: EventType.deletedFromEverywhere,
|
|
|
|
source: "deleteFromTrash",
|
|
|
|
),
|
|
|
|
);
|
2023-02-22 07:22:57 +00:00
|
|
|
//the FilesUpdateEvent is not reloading trash on premanently removing
|
|
|
|
//files, so need to fire ForceReloadTrashPageEvent
|
|
|
|
Bus.instance.fire(ForceReloadTrashPageEvent());
|
2023-01-09 12:02:13 +00:00
|
|
|
} catch (e, s) {
|
|
|
|
_logger.info("failed to delete from trash", e, s);
|
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
},
|
2022-06-11 08:23:52 +00:00
|
|
|
);
|
2023-02-09 13:18:01 +00:00
|
|
|
|
|
|
|
if (actionResult?.action == null ||
|
|
|
|
actionResult!.action == ButtonAction.cancel) {
|
|
|
|
return didDeletionStart ? true : false;
|
|
|
|
} else if (actionResult.action == ButtonAction.error) {
|
2023-11-30 05:19:35 +00:00
|
|
|
await showGenericErrorDialog(
|
|
|
|
context: context,
|
|
|
|
error: actionResult.exception,
|
|
|
|
);
|
2021-10-13 09:25:04 +00:00
|
|
|
return false;
|
2023-01-09 12:02:13 +00:00
|
|
|
} else {
|
|
|
|
return true;
|
2021-10-12 22:51:19 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-10-25 14:57:41 +00:00
|
|
|
Future<bool> emptyTrash(BuildContext context) async {
|
2023-02-09 13:18:01 +00:00
|
|
|
final actionResult = await showChoiceActionSheet(
|
2022-06-11 08:23:52 +00:00
|
|
|
context,
|
2023-04-07 06:46:18 +00:00
|
|
|
title: S.of(context).emptyTrash,
|
|
|
|
body: S.of(context).permDeleteWarning,
|
|
|
|
firstButtonLabel: S.of(context).empty,
|
2023-01-09 12:17:16 +00:00
|
|
|
isCritical: true,
|
|
|
|
firstButtonOnTap: () async {
|
|
|
|
try {
|
|
|
|
await TrashSyncService.instance.emptyTrash();
|
|
|
|
} catch (e, s) {
|
|
|
|
_logger.info("failed empty trash", e, s);
|
|
|
|
rethrow;
|
|
|
|
}
|
|
|
|
},
|
2022-06-11 08:23:52 +00:00
|
|
|
);
|
2023-02-09 13:18:01 +00:00
|
|
|
if (actionResult?.action == null ||
|
|
|
|
actionResult!.action == ButtonAction.cancel) {
|
2021-10-25 14:57:41 +00:00
|
|
|
return false;
|
2023-02-09 13:18:01 +00:00
|
|
|
} else if (actionResult.action == ButtonAction.error) {
|
2023-11-30 05:19:35 +00:00
|
|
|
await showGenericErrorDialog(
|
|
|
|
context: context,
|
|
|
|
error: actionResult.exception,
|
|
|
|
);
|
2021-10-25 14:57:41 +00:00
|
|
|
return false;
|
2023-01-09 12:17:16 +00:00
|
|
|
} else {
|
|
|
|
return true;
|
2021-10-25 14:57:41 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-06-28 16:32:29 +00:00
|
|
|
Future<bool> deleteLocalFiles(
|
2022-06-11 08:23:52 +00:00
|
|
|
BuildContext context,
|
|
|
|
List<String> localIDs,
|
|
|
|
) async {
|
2021-06-28 16:32:29 +00:00
|
|
|
final List<String> deletedIDs = [];
|
2021-07-24 14:41:28 +00:00
|
|
|
final List<String> localAssetIDs = [];
|
|
|
|
final List<String> localSharedMediaIDs = [];
|
|
|
|
for (String id in localIDs) {
|
2022-09-20 11:53:32 +00:00
|
|
|
if (id.startsWith(oldSharedMediaIdentifier) ||
|
|
|
|
id.startsWith(sharedMediaIdentifier)) {
|
2021-07-24 14:41:28 +00:00
|
|
|
localSharedMediaIDs.add(id);
|
|
|
|
} else {
|
|
|
|
localAssetIDs.add(id);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
deletedIDs.addAll(await _tryDeleteSharedMediaFiles(localSharedMediaIDs));
|
|
|
|
|
2023-05-17 05:32:00 +00:00
|
|
|
final bool shouldDeleteInBatches =
|
|
|
|
await isAndroidSDKVersionLowerThan(android11SDKINT);
|
|
|
|
if (shouldDeleteInBatches) {
|
|
|
|
deletedIDs.addAll(await deleteLocalFilesInBatches(context, localAssetIDs));
|
2021-06-28 12:54:57 +00:00
|
|
|
} else {
|
2021-07-24 14:41:28 +00:00
|
|
|
deletedIDs.addAll(await _deleteLocalFilesInOneShot(context, localAssetIDs));
|
2021-06-28 10:12:12 +00:00
|
|
|
}
|
2023-05-17 05:44:36 +00:00
|
|
|
// In IOS, the library returns no error and fail to delete any file is
|
|
|
|
// there's any shared file. As a stop-gap solution, we initiate deletion in
|
2023-08-11 14:06:23 +00:00
|
|
|
// batches. Similar in Android, for large number of files, we have observed
|
|
|
|
// that the library fails to delete any file. So, we initiate deletion in
|
|
|
|
// batches.
|
|
|
|
if (deletedIDs.isEmpty) {
|
|
|
|
deletedIDs.addAll(
|
|
|
|
await deleteLocalFilesInBatches(
|
|
|
|
context,
|
|
|
|
localAssetIDs,
|
|
|
|
maximumBatchSize: 1000,
|
|
|
|
minimumBatchSize: 10,
|
|
|
|
),
|
|
|
|
);
|
2023-05-17 05:44:36 +00:00
|
|
|
_logger
|
|
|
|
.severe("iOS free-space fallback, deleted ${deletedIDs.length} files "
|
|
|
|
"in batches}");
|
|
|
|
}
|
2021-06-28 10:12:12 +00:00
|
|
|
if (deletedIDs.isNotEmpty) {
|
|
|
|
final deletedFiles = await FilesDB.instance.getLocalFiles(deletedIDs);
|
|
|
|
await FilesDB.instance.deleteLocalFiles(deletedIDs);
|
|
|
|
_logger.info(deletedFiles.length.toString() + " files deleted locally");
|
2022-11-11 13:09:22 +00:00
|
|
|
Bus.instance.fire(
|
|
|
|
LocalPhotosUpdatedEvent(deletedFiles, source: "deleteLocal"),
|
|
|
|
);
|
2021-06-28 16:34:06 +00:00
|
|
|
return true;
|
2021-06-28 16:32:29 +00:00
|
|
|
} else {
|
2023-04-07 06:46:18 +00:00
|
|
|
showToast(context, S.of(context).couldNotFreeUpSpace);
|
2021-06-28 16:32:29 +00:00
|
|
|
return false;
|
2021-06-28 10:12:12 +00:00
|
|
|
}
|
|
|
|
}
|
2021-06-28 15:52:21 +00:00
|
|
|
|
2021-06-28 18:32:09 +00:00
|
|
|
Future<List<String>> _deleteLocalFilesInOneShot(
|
2022-06-11 08:23:52 +00:00
|
|
|
BuildContext context,
|
|
|
|
List<String> localIDs,
|
|
|
|
) async {
|
2022-07-03 10:35:46 +00:00
|
|
|
_logger.info('starting _deleteLocalFilesInOneShot for ${localIDs.length}');
|
2021-06-28 16:32:29 +00:00
|
|
|
final List<String> deletedIDs = [];
|
2022-06-11 08:23:52 +00:00
|
|
|
final dialog = createProgressDialog(
|
|
|
|
context,
|
|
|
|
"Deleting " + localIDs.length.toString() + " backed up files...",
|
|
|
|
);
|
2021-06-28 15:52:21 +00:00
|
|
|
await dialog.show();
|
|
|
|
try {
|
|
|
|
deletedIDs.addAll(await PhotoManager.editor.deleteWithIds(localIDs));
|
|
|
|
} catch (e, s) {
|
|
|
|
_logger.severe("Could not delete files ", e, s);
|
|
|
|
}
|
2022-07-04 06:02:17 +00:00
|
|
|
_logger.info(
|
|
|
|
'_deleteLocalFilesInOneShot deleted ${deletedIDs.length} out '
|
|
|
|
'of ${localIDs.length}',
|
|
|
|
);
|
2021-06-28 15:52:21 +00:00
|
|
|
await dialog.hide();
|
2021-06-28 16:32:29 +00:00
|
|
|
return deletedIDs;
|
2021-06-28 15:52:21 +00:00
|
|
|
}
|
|
|
|
|
2022-11-24 10:54:59 +00:00
|
|
|
Future<List<String>> deleteLocalFilesInBatches(
|
2022-06-11 08:23:52 +00:00
|
|
|
BuildContext context,
|
2023-08-11 14:06:23 +00:00
|
|
|
List<String> localIDs, {
|
|
|
|
int minimumParts = 10,
|
|
|
|
int minimumBatchSize = 1,
|
|
|
|
int maximumBatchSize = 100,
|
|
|
|
}) async {
|
2021-06-28 15:52:21 +00:00
|
|
|
final dialogKey = GlobalKey<LinearProgressDialogState>();
|
|
|
|
final dialog = LinearProgressDialog(
|
2022-05-30 11:05:08 +00:00
|
|
|
"Deleting " + localIDs.length.toString() + " backed up files...",
|
2021-06-28 15:52:21 +00:00
|
|
|
key: dialogKey,
|
|
|
|
);
|
|
|
|
showDialog(
|
|
|
|
context: context,
|
|
|
|
builder: (context) {
|
|
|
|
return dialog;
|
|
|
|
},
|
2021-06-29 04:26:20 +00:00
|
|
|
barrierColor: Colors.black.withOpacity(0.85),
|
2021-06-28 15:52:21 +00:00
|
|
|
);
|
2021-06-28 16:08:07 +00:00
|
|
|
final batchSize = min(
|
2022-06-11 08:23:52 +00:00
|
|
|
max(minimumBatchSize, (localIDs.length / minimumParts).round()),
|
|
|
|
maximumBatchSize,
|
|
|
|
);
|
2021-06-28 16:32:29 +00:00
|
|
|
final List<String> deletedIDs = [];
|
2021-06-28 15:52:21 +00:00
|
|
|
for (int index = 0; index < localIDs.length; index += batchSize) {
|
|
|
|
if (dialogKey.currentState != null) {
|
2022-12-30 10:42:54 +00:00
|
|
|
dialogKey.currentState!.setProgress(index / localIDs.length);
|
2021-06-28 15:52:21 +00:00
|
|
|
}
|
|
|
|
final ids = localIDs
|
|
|
|
.getRange(index, min(localIDs.length, index + batchSize))
|
|
|
|
.toList();
|
|
|
|
_logger.info("Trying to delete " + ids.toString());
|
|
|
|
try {
|
|
|
|
deletedIDs.addAll(await PhotoManager.editor.deleteWithIds(ids));
|
|
|
|
_logger.info("Deleted " + ids.toString());
|
|
|
|
} catch (e, s) {
|
|
|
|
_logger.severe("Could not delete batch " + ids.toString(), e, s);
|
|
|
|
for (final id in ids) {
|
|
|
|
try {
|
|
|
|
deletedIDs.addAll(await PhotoManager.editor.deleteWithIds([id]));
|
|
|
|
_logger.info("Deleted " + id);
|
|
|
|
} catch (e, s) {
|
|
|
|
_logger.severe("Could not delete file " + id, e, s);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2022-12-30 10:42:54 +00:00
|
|
|
Navigator.of(dialogKey.currentContext!, rootNavigator: true).pop('dialog');
|
2021-06-28 16:32:29 +00:00
|
|
|
return deletedIDs;
|
2021-06-28 15:52:21 +00:00
|
|
|
}
|
2021-07-24 14:41:28 +00:00
|
|
|
|
2023-08-24 16:56:24 +00:00
|
|
|
Future<bool> _localFileExist(EnteFile file) {
|
2022-09-23 01:48:25 +00:00
|
|
|
if (file.isSharedMediaToAppSandbox) {
|
2023-08-24 16:56:24 +00:00
|
|
|
final localFile = File(getSharedMediaFilePath(file));
|
2021-07-24 14:41:28 +00:00
|
|
|
return localFile.exists();
|
|
|
|
} else {
|
2022-09-23 01:48:25 +00:00
|
|
|
return file.getAsset.then((asset) {
|
2021-07-24 14:41:28 +00:00
|
|
|
if (asset == null) {
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
return asset.exists;
|
|
|
|
});
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
Future<List<String>> _tryDeleteSharedMediaFiles(List<String> localIDs) {
|
|
|
|
final List<String> actuallyDeletedIDs = [];
|
|
|
|
try {
|
2022-12-30 10:42:54 +00:00
|
|
|
return Future.forEach<String>(localIDs, (id) async {
|
2022-08-29 14:43:31 +00:00
|
|
|
final String localPath = getSharedMediaPathFromLocalID(id);
|
2021-07-24 14:41:28 +00:00
|
|
|
try {
|
2021-09-09 05:50:55 +00:00
|
|
|
// verify the file exists as the OS may have already deleted it from cache
|
2023-08-24 16:56:24 +00:00
|
|
|
if (File(localPath).existsSync()) {
|
|
|
|
await File(localPath).delete();
|
2021-09-09 05:50:55 +00:00
|
|
|
}
|
2021-07-24 14:41:28 +00:00
|
|
|
actuallyDeletedIDs.add(id);
|
|
|
|
} catch (e, s) {
|
|
|
|
_logger.warning("Could not delete file " + id, e, s);
|
|
|
|
// server log shouldn't contain localId
|
|
|
|
_logger.severe("Could not delete file ", e, s);
|
|
|
|
}
|
|
|
|
}).then((ignore) {
|
|
|
|
return actuallyDeletedIDs;
|
|
|
|
});
|
|
|
|
} catch (e, s) {
|
|
|
|
_logger.severe("Unexpected error while deleting share media files", e, s);
|
|
|
|
return Future.value(actuallyDeletedIDs);
|
|
|
|
}
|
2021-07-24 17:22:46 +00:00
|
|
|
}
|
2022-05-12 12:19:36 +00:00
|
|
|
|
|
|
|
Future<bool> shouldProceedWithDeletion(BuildContext context) async {
|
2023-02-09 13:18:01 +00:00
|
|
|
final actionResult = await showChoiceActionSheet(
|
2022-05-12 12:19:36 +00:00
|
|
|
context,
|
2023-04-07 06:46:18 +00:00
|
|
|
title: S.of(context).permanentlyDeleteFromDevice,
|
|
|
|
body: S.of(context).someOfTheFilesYouAreTryingToDeleteAre,
|
|
|
|
firstButtonLabel: S.of(context).delete,
|
2023-01-27 12:36:52 +00:00
|
|
|
isCritical: true,
|
2022-05-12 12:19:36 +00:00
|
|
|
);
|
2023-02-09 13:18:01 +00:00
|
|
|
if (actionResult?.action == null) {
|
2023-01-27 12:07:33 +00:00
|
|
|
return false;
|
|
|
|
} else {
|
2023-02-09 13:18:01 +00:00
|
|
|
return actionResult!.action == ButtonAction.first;
|
2023-01-27 12:07:33 +00:00
|
|
|
}
|
2022-05-12 12:19:36 +00:00
|
|
|
}
|
2022-12-05 11:08:21 +00:00
|
|
|
|
2023-01-06 07:24:00 +00:00
|
|
|
Future<void> showDeleteSheet(
|
|
|
|
BuildContext context,
|
|
|
|
SelectedFiles selectedFiles,
|
2023-09-02 06:57:04 +00:00
|
|
|
FilesSplit filesSplit,
|
2023-01-06 07:24:00 +00:00
|
|
|
) async {
|
2023-09-02 06:57:04 +00:00
|
|
|
if (selectedFiles.files.length != filesSplit.count) {
|
|
|
|
throw AssertionError("Unexpected state, #{selectedFiles.files.length} != "
|
|
|
|
"${filesSplit.count}");
|
|
|
|
}
|
|
|
|
final List<EnteFile> deletableFiles =
|
|
|
|
filesSplit.ownedByCurrentUser + filesSplit.pendingUploads;
|
|
|
|
if (deletableFiles.isEmpty && filesSplit.ownedByOtherUsers.isNotEmpty) {
|
|
|
|
showShortToast(context, S.of(context).cannotDeleteSharedFiles);
|
|
|
|
return;
|
2022-12-05 11:08:21 +00:00
|
|
|
}
|
2023-09-02 06:57:04 +00:00
|
|
|
final containsUploadedFile = deletableFiles.any((f) => f.isUploaded);
|
|
|
|
final containsLocalFile = deletableFiles.any((f) => f.localID != null);
|
|
|
|
|
2023-01-06 07:24:00 +00:00
|
|
|
final List<ButtonWidget> buttons = [];
|
|
|
|
final bool isBothLocalAndRemote = containsUploadedFile && containsLocalFile;
|
|
|
|
final bool isLocalOnly = !containsUploadedFile;
|
|
|
|
final bool isRemoteOnly = !containsLocalFile;
|
2023-04-07 06:46:18 +00:00
|
|
|
final String? bodyHighlight = isBothLocalAndRemote
|
|
|
|
? S.of(context).theyWillBeDeletedFromAllAlbums
|
|
|
|
: null;
|
2023-01-06 07:24:00 +00:00
|
|
|
String body = "";
|
|
|
|
if (isBothLocalAndRemote) {
|
2023-04-07 06:46:18 +00:00
|
|
|
body = S.of(context).someItemsAreInBothEnteAndYourDevice;
|
2023-01-06 07:24:00 +00:00
|
|
|
} else if (isRemoteOnly) {
|
2023-04-07 06:46:18 +00:00
|
|
|
body = S.of(context).selectedItemsWillBeDeletedFromAllAlbumsAndMoved;
|
2023-01-06 07:24:00 +00:00
|
|
|
} else if (isLocalOnly) {
|
2023-04-07 06:46:18 +00:00
|
|
|
body = S.of(context).theseItemsWillBeDeletedFromYourDevice;
|
2023-01-06 07:24:00 +00:00
|
|
|
} else {
|
|
|
|
throw AssertionError("Unexpected state");
|
|
|
|
}
|
|
|
|
// Add option to delete from ente
|
|
|
|
if (isBothLocalAndRemote || isRemoteOnly) {
|
|
|
|
buttons.add(
|
|
|
|
ButtonWidget(
|
2023-04-07 06:46:18 +00:00
|
|
|
labelText: isBothLocalAndRemote
|
|
|
|
? S.of(context).deleteFromEnte
|
|
|
|
: S.of(context).yesDelete,
|
2023-01-06 07:24:00 +00:00
|
|
|
buttonType: ButtonType.neutral,
|
|
|
|
buttonSize: ButtonSize.large,
|
|
|
|
shouldStickToDarkTheme: true,
|
|
|
|
buttonAction: ButtonAction.first,
|
|
|
|
shouldSurfaceExecutionStates: true,
|
|
|
|
isInAlert: true,
|
|
|
|
onTap: () async {
|
2022-12-05 11:08:21 +00:00
|
|
|
await deleteFilesFromRemoteOnly(
|
|
|
|
context,
|
2023-09-02 06:57:04 +00:00
|
|
|
deletableFiles,
|
2023-02-09 13:18:01 +00:00
|
|
|
).then(
|
|
|
|
(value) {
|
2023-04-07 06:46:18 +00:00
|
|
|
showShortToast(context, S.of(context).movedToTrash);
|
2023-02-09 13:18:01 +00:00
|
|
|
},
|
|
|
|
onError: (e, s) {
|
2023-11-30 05:19:35 +00:00
|
|
|
showGenericErrorDialog(context: context, error: e);
|
2023-02-09 13:18:01 +00:00
|
|
|
},
|
2022-12-05 11:08:21 +00:00
|
|
|
);
|
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
2023-01-06 07:24:00 +00:00
|
|
|
}
|
|
|
|
// Add option to delete from local
|
|
|
|
if (isBothLocalAndRemote || isLocalOnly) {
|
|
|
|
buttons.add(
|
|
|
|
ButtonWidget(
|
2023-04-07 06:46:18 +00:00
|
|
|
labelText: isBothLocalAndRemote
|
|
|
|
? S.of(context).deleteFromDevice
|
|
|
|
: S.of(context).yesDelete,
|
2023-01-06 07:24:00 +00:00
|
|
|
buttonType: ButtonType.neutral,
|
|
|
|
buttonSize: ButtonSize.large,
|
|
|
|
shouldStickToDarkTheme: true,
|
|
|
|
buttonAction: ButtonAction.second,
|
|
|
|
shouldSurfaceExecutionStates: false,
|
|
|
|
isInAlert: true,
|
|
|
|
onTap: () async {
|
2023-09-02 06:57:04 +00:00
|
|
|
await deleteFilesOnDeviceOnly(context, deletableFiles);
|
2022-12-05 11:08:21 +00:00
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
2023-01-06 07:24:00 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
if (isBothLocalAndRemote) {
|
|
|
|
buttons.add(
|
|
|
|
ButtonWidget(
|
2023-04-07 06:46:18 +00:00
|
|
|
labelText: S.of(context).deleteFromBoth,
|
2023-01-06 07:24:00 +00:00
|
|
|
buttonType: ButtonType.neutral,
|
|
|
|
buttonSize: ButtonSize.large,
|
|
|
|
shouldStickToDarkTheme: true,
|
|
|
|
buttonAction: ButtonAction.third,
|
|
|
|
shouldSurfaceExecutionStates: true,
|
|
|
|
isInAlert: true,
|
|
|
|
onTap: () async {
|
2022-12-05 11:08:21 +00:00
|
|
|
await deleteFilesFromEverywhere(
|
|
|
|
context,
|
2023-09-02 06:57:04 +00:00
|
|
|
deletableFiles,
|
2022-12-05 11:08:21 +00:00
|
|
|
);
|
|
|
|
},
|
|
|
|
),
|
|
|
|
);
|
|
|
|
}
|
2023-01-06 07:24:00 +00:00
|
|
|
buttons.add(
|
2023-04-07 06:46:18 +00:00
|
|
|
ButtonWidget(
|
|
|
|
labelText: S.of(context).cancel,
|
2023-01-06 07:24:00 +00:00
|
|
|
buttonType: ButtonType.secondary,
|
|
|
|
buttonSize: ButtonSize.large,
|
|
|
|
shouldStickToDarkTheme: true,
|
|
|
|
buttonAction: ButtonAction.fourth,
|
|
|
|
isInAlert: true,
|
2022-12-05 11:08:21 +00:00
|
|
|
),
|
|
|
|
);
|
2023-02-09 13:18:01 +00:00
|
|
|
final actionResult = await showActionSheet(
|
2022-12-05 11:08:21 +00:00
|
|
|
context: context,
|
2023-01-06 07:24:00 +00:00
|
|
|
buttons: buttons,
|
|
|
|
actionSheetType: ActionSheetType.defaultActionSheet,
|
|
|
|
body: body,
|
|
|
|
bodyHighlight: bodyHighlight,
|
2022-12-05 11:08:21 +00:00
|
|
|
);
|
2023-02-09 13:18:01 +00:00
|
|
|
if (actionResult?.action != null &&
|
|
|
|
actionResult!.action == ButtonAction.error) {
|
2023-11-30 05:19:35 +00:00
|
|
|
showGenericErrorDialog(context: context, error: actionResult.exception);
|
2023-01-06 07:24:00 +00:00
|
|
|
} else {
|
|
|
|
selectedFiles.clearAll();
|
|
|
|
}
|
2022-12-05 11:08:21 +00:00
|
|
|
}
|