ente/lib/utils/delete_file_util.dart

232 lines
7.9 KiB
Dart
Raw Normal View History

import 'dart:async';
2021-06-28 12:54:57 +00:00
import 'dart:io';
import 'dart:math';
import 'package:device_info/device_info.dart';
2021-06-28 15:52:21 +00:00
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:logging/logging.dart';
import 'package:photo_manager/photo_manager.dart';
import 'package:photos/core/constants.dart';
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';
import 'package:photos/events/local_photos_updated_event.dart';
import 'package:photos/models/file.dart';
import 'package:photos/services/remote_sync_service.dart';
import 'package:photos/services/sync_service.dart';
2021-06-28 15:52:21 +00:00
import 'package:photos/ui/linear_progress_dialog.dart';
import 'package:photos/utils/dialog_util.dart';
import 'package:photos/utils/toast_util.dart';
final _logger = Logger("DeleteFileUtil");
Future<void> deleteFilesFromEverywhere(
BuildContext context, List<File> files) async {
final dialog = createProgressDialog(context, "deleting...");
await dialog.show();
2021-06-13 20:09:06 +00:00
_logger.info("Trying to delete files " + files.toString());
final List<String> localIDs = [];
final List<String> alreadyDeletedIDs = []; // to ignore already deleted files
for (final file in files) {
if (file.localID != null) {
final asset = await file.getAsset();
if (asset == null || !(await asset.exists)) {
2021-06-13 20:09:06 +00:00
_logger.warning("Already deleted " + file.toString());
alreadyDeletedIDs.add(file.localID);
} else {
localIDs.add(file.localID);
}
}
}
2021-06-13 20:38:26 +00:00
Set<String> deletedIDs = Set<String>();
try {
deletedIDs = (await PhotoManager.editor.deleteWithIds(localIDs)).toSet();
} catch (e, s) {
_logger.severe("Could not delete file", e, s);
}
final updatedCollectionIDs = Set<int>();
final List<int> uploadedFileIDsToBeDeleted = [];
final List<File> deletedFiles = [];
for (final file in files) {
if (file.localID != null) {
// Remove only those files that have already been removed from disk
if (deletedIDs.contains(file.localID) ||
alreadyDeletedIDs.contains(file.localID)) {
deletedFiles.add(file);
if (file.uploadedFileID != null) {
uploadedFileIDsToBeDeleted.add(file.uploadedFileID);
updatedCollectionIDs.add(file.collectionID);
} else {
await FilesDB.instance.deleteLocalFile(file.localID);
}
}
} else {
updatedCollectionIDs.add(file.collectionID);
deletedFiles.add(file);
uploadedFileIDsToBeDeleted.add(file.uploadedFileID);
}
}
if (uploadedFileIDsToBeDeleted.isNotEmpty) {
try {
await SyncService.instance
.deleteFilesOnServer(uploadedFileIDsToBeDeleted);
await FilesDB.instance
.deleteMultipleUploadedFiles(uploadedFileIDsToBeDeleted);
} catch (e) {
_logger.severe(e);
await dialog.hide();
showGenericErrorDialog(context);
throw e;
}
for (final collectionID in updatedCollectionIDs) {
Bus.instance.fire(CollectionUpdatedEvent(
collectionID,
deletedFiles
.where((file) => file.collectionID == collectionID)
.toList(),
type: EventType.deleted,
));
}
}
if (deletedFiles.isNotEmpty) {
Bus.instance
.fire(LocalPhotosUpdatedEvent(deletedFiles, type: EventType.deleted));
}
await dialog.hide();
showToast("deleted from everywhere");
if (uploadedFileIDsToBeDeleted.isNotEmpty) {
RemoteSyncService.instance.sync(silently: true);
}
}
Future<void> deleteFilesOnDeviceOnly(
BuildContext context, List<File> files) async {
final dialog = createProgressDialog(context, "deleting...");
await dialog.show();
2021-06-13 20:09:06 +00:00
_logger.info("Trying to delete files " + files.toString());
final List<String> localIDs = [];
final List<String> alreadyDeletedIDs = []; // to ignore already deleted files
for (final file in files) {
if (file.localID != null) {
final asset = await file.getAsset();
if (asset == null || !(await asset.exists)) {
2021-06-13 20:09:06 +00:00
_logger.warning("Already deleted " + file.toString());
alreadyDeletedIDs.add(file.localID);
} else {
localIDs.add(file.localID);
}
}
}
2021-06-13 20:38:26 +00:00
Set<String> deletedIDs = Set<String>();
2021-05-25 12:26:31 +00:00
try {
deletedIDs = (await PhotoManager.editor.deleteWithIds(localIDs)).toSet();
} catch (e, s) {
_logger.severe("Could not delete file", e, s);
}
final List<File> deletedFiles = [];
for (final file in files) {
// Remove only those files that have been removed from disk
if (deletedIDs.contains(file.localID) ||
alreadyDeletedIDs.contains(file.localID)) {
deletedFiles.add(file);
file.localID = null;
FilesDB.instance.update(file);
}
}
if (deletedFiles.isNotEmpty || alreadyDeletedIDs.isNotEmpty) {
Bus.instance
.fire(LocalPhotosUpdatedEvent(deletedFiles, type: EventType.deleted));
}
await dialog.hide();
}
2021-06-28 10:12:12 +00:00
2021-06-28 16:32:29 +00:00
Future<bool> deleteLocalFiles(
2021-06-28 15:52:21 +00:00
BuildContext context, List<String> localIDs) async {
2021-06-28 16:32:29 +00:00
final List<String> deletedIDs = [];
2021-06-28 12:54:57 +00:00
if (Platform.isAndroid) {
final androidInfo = await DeviceInfoPlugin().androidInfo;
if (androidInfo.version.sdkInt < ANDROID_11_SDK_INT) {
deletedIDs.addAll(await _deleteLocalFilesInBatches(context, localIDs));
} else {
deletedIDs.addAll(await _deleteLocalFilesInOneShot(context, localIDs));
}
2021-06-28 12:54:57 +00:00
} else {
2021-06-28 18:32:09 +00:00
deletedIDs.addAll(await _deleteLocalFilesInOneShot(context, localIDs));
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");
Bus.instance
.fire(LocalPhotosUpdatedEvent(deletedFiles, type: EventType.deleted));
2021-06-28 16:34:06 +00:00
return true;
2021-06-28 16:32:29 +00:00
} else {
2021-06-28 16:51:52 +00:00
showToast("could not free up space");
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(
BuildContext context, List<String> localIDs) async {
2021-06-28 16:32:29 +00:00
final List<String> deletedIDs = [];
2021-06-28 16:08:07 +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);
}
await dialog.hide();
2021-06-28 16:32:29 +00:00
return deletedIDs;
2021-06-28 15:52:21 +00:00
}
2021-06-28 18:32:09 +00:00
Future<List<String>> _deleteLocalFilesInBatches(
BuildContext context, List<String> localIDs) async {
2021-06-28 15:52:21 +00:00
final dialogKey = GlobalKey<LinearProgressDialogState>();
final dialog = LinearProgressDialog(
2021-06-28 16:08:07 +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-28 16:08:07 +00:00
const minimumParts = 10;
const minimumBatchSize = 1;
const maximumBatchSize = 100;
final batchSize = min(
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) {
dialogKey.currentState.setProgress(index / localIDs.length);
}
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);
}
}
}
}
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
}