delete_file_util.dart 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221
  1. import 'dart:async';
  2. import 'dart:io';
  3. import 'dart:math';
  4. import 'package:flutter/material.dart';
  5. import 'package:flutter/widgets.dart';
  6. import 'package:logging/logging.dart';
  7. import 'package:photo_manager/photo_manager.dart';
  8. import 'package:photos/core/event_bus.dart';
  9. import 'package:photos/db/files_db.dart';
  10. import 'package:photos/events/collection_updated_event.dart';
  11. import 'package:photos/events/files_updated_event.dart';
  12. import 'package:photos/events/local_photos_updated_event.dart';
  13. import 'package:photos/models/file.dart';
  14. import 'package:photos/services/remote_sync_service.dart';
  15. import 'package:photos/services/sync_service.dart';
  16. import 'package:photos/ui/linear_progress_dialog.dart';
  17. import 'package:photos/utils/dialog_util.dart';
  18. import 'package:photos/utils/toast_util.dart';
  19. final _logger = Logger("DeleteFileUtil");
  20. Future<void> deleteFilesFromEverywhere(
  21. BuildContext context, List<File> files) async {
  22. final dialog = createProgressDialog(context, "deleting...");
  23. await dialog.show();
  24. _logger.info("Trying to delete files " + files.toString());
  25. final List<String> localIDs = [];
  26. final List<String> alreadyDeletedIDs = []; // to ignore already deleted files
  27. for (final file in files) {
  28. if (file.localID != null) {
  29. final asset = await file.getAsset();
  30. if (asset == null || !(await asset.exists)) {
  31. _logger.warning("Already deleted " + file.toString());
  32. alreadyDeletedIDs.add(file.localID);
  33. } else {
  34. localIDs.add(file.localID);
  35. }
  36. }
  37. }
  38. Set<String> deletedIDs = Set<String>();
  39. try {
  40. deletedIDs = (await PhotoManager.editor.deleteWithIds(localIDs)).toSet();
  41. } catch (e, s) {
  42. _logger.severe("Could not delete file", e, s);
  43. }
  44. final updatedCollectionIDs = Set<int>();
  45. final List<int> uploadedFileIDsToBeDeleted = [];
  46. final List<File> deletedFiles = [];
  47. for (final file in files) {
  48. if (file.localID != null) {
  49. // Remove only those files that have already been removed from disk
  50. if (deletedIDs.contains(file.localID) ||
  51. alreadyDeletedIDs.contains(file.localID)) {
  52. deletedFiles.add(file);
  53. if (file.uploadedFileID != null) {
  54. uploadedFileIDsToBeDeleted.add(file.uploadedFileID);
  55. updatedCollectionIDs.add(file.collectionID);
  56. } else {
  57. await FilesDB.instance.deleteLocalFile(file.localID);
  58. }
  59. }
  60. } else {
  61. updatedCollectionIDs.add(file.collectionID);
  62. deletedFiles.add(file);
  63. uploadedFileIDsToBeDeleted.add(file.uploadedFileID);
  64. }
  65. }
  66. if (uploadedFileIDsToBeDeleted.isNotEmpty) {
  67. try {
  68. await SyncService.instance
  69. .deleteFilesOnServer(uploadedFileIDsToBeDeleted);
  70. await FilesDB.instance
  71. .deleteMultipleUploadedFiles(uploadedFileIDsToBeDeleted);
  72. } catch (e) {
  73. _logger.severe(e);
  74. await dialog.hide();
  75. showGenericErrorDialog(context);
  76. throw e;
  77. }
  78. for (final collectionID in updatedCollectionIDs) {
  79. Bus.instance.fire(CollectionUpdatedEvent(
  80. collectionID,
  81. deletedFiles
  82. .where((file) => file.collectionID == collectionID)
  83. .toList(),
  84. type: EventType.deleted,
  85. ));
  86. }
  87. }
  88. if (deletedFiles.isNotEmpty) {
  89. Bus.instance
  90. .fire(LocalPhotosUpdatedEvent(deletedFiles, type: EventType.deleted));
  91. }
  92. await dialog.hide();
  93. showToast("deleted from everywhere");
  94. if (uploadedFileIDsToBeDeleted.isNotEmpty) {
  95. RemoteSyncService.instance.sync(silently: true);
  96. }
  97. }
  98. Future<void> deleteFilesOnDeviceOnly(
  99. BuildContext context, List<File> files) async {
  100. final dialog = createProgressDialog(context, "deleting...");
  101. await dialog.show();
  102. _logger.info("Trying to delete files " + files.toString());
  103. final List<String> localIDs = [];
  104. final List<String> alreadyDeletedIDs = []; // to ignore already deleted files
  105. for (final file in files) {
  106. if (file.localID != null) {
  107. final asset = await file.getAsset();
  108. if (asset == null || !(await asset.exists)) {
  109. _logger.warning("Already deleted " + file.toString());
  110. alreadyDeletedIDs.add(file.localID);
  111. } else {
  112. localIDs.add(file.localID);
  113. }
  114. }
  115. }
  116. Set<String> deletedIDs = Set<String>();
  117. try {
  118. deletedIDs = (await PhotoManager.editor.deleteWithIds(localIDs)).toSet();
  119. } catch (e, s) {
  120. _logger.severe("Could not delete file", e, s);
  121. }
  122. final List<File> deletedFiles = [];
  123. for (final file in files) {
  124. // Remove only those files that have been removed from disk
  125. if (deletedIDs.contains(file.localID) ||
  126. alreadyDeletedIDs.contains(file.localID)) {
  127. deletedFiles.add(file);
  128. file.localID = null;
  129. FilesDB.instance.update(file);
  130. }
  131. }
  132. if (deletedFiles.isNotEmpty || alreadyDeletedIDs.isNotEmpty) {
  133. Bus.instance
  134. .fire(LocalPhotosUpdatedEvent(deletedFiles, type: EventType.deleted));
  135. }
  136. await dialog.hide();
  137. }
  138. Future<bool> deleteLocalFiles(
  139. BuildContext context, List<String> localIDs) async {
  140. final List<String> deletedIDs = [];
  141. if (Platform.isAndroid) {
  142. deletedIDs.addAll(await _deleteLocalFilesOnAndroid(context, localIDs));
  143. } else {
  144. deletedIDs.addAll(await _deleteLocalFilesOnIOS(context, localIDs));
  145. }
  146. if (deletedIDs.isNotEmpty) {
  147. final deletedFiles = await FilesDB.instance.getLocalFiles(deletedIDs);
  148. await FilesDB.instance.deleteLocalFiles(deletedIDs);
  149. _logger.info(deletedFiles.length.toString() + " files deleted locally");
  150. Bus.instance
  151. .fire(LocalPhotosUpdatedEvent(deletedFiles, type: EventType.deleted));
  152. return true;
  153. } else {
  154. return false;
  155. }
  156. }
  157. Future<List<String>> _deleteLocalFilesOnIOS(BuildContext context, List<String> localIDs) async {
  158. final List<String> deletedIDs = [];
  159. final dialog = createProgressDialog(context,
  160. "deleting " + localIDs.length.toString() + " backed up files...");
  161. await dialog.show();
  162. try {
  163. deletedIDs.addAll(await PhotoManager.editor.deleteWithIds(localIDs));
  164. } catch (e, s) {
  165. _logger.severe("Could not delete files ", e, s);
  166. }
  167. await dialog.hide();
  168. return deletedIDs;
  169. }
  170. Future<List<String>> _deleteLocalFilesOnAndroid(BuildContext context, List<String> localIDs) async {
  171. final dialogKey = GlobalKey<LinearProgressDialogState>();
  172. final dialog = LinearProgressDialog(
  173. "deleting " + localIDs.length.toString() + " backed up files...",
  174. key: dialogKey,
  175. );
  176. showDialog(
  177. context: context,
  178. builder: (context) {
  179. return dialog;
  180. },
  181. );
  182. const minimumParts = 10;
  183. const minimumBatchSize = 1;
  184. const maximumBatchSize = 100;
  185. final batchSize = min(
  186. max(minimumBatchSize, (localIDs.length / minimumParts).round()),
  187. maximumBatchSize);
  188. final List<String> deletedIDs = [];
  189. for (int index = 0; index < localIDs.length; index += batchSize) {
  190. if (dialogKey.currentState != null) {
  191. dialogKey.currentState.setProgress(index / localIDs.length);
  192. }
  193. final ids = localIDs
  194. .getRange(index, min(localIDs.length, index + batchSize))
  195. .toList();
  196. _logger.info("Trying to delete " + ids.toString());
  197. try {
  198. deletedIDs.addAll(await PhotoManager.editor.deleteWithIds(ids));
  199. _logger.info("Deleted " + ids.toString());
  200. } catch (e, s) {
  201. _logger.severe("Could not delete batch " + ids.toString(), e, s);
  202. for (final id in ids) {
  203. try {
  204. deletedIDs.addAll(await PhotoManager.editor.deleteWithIds([id]));
  205. _logger.info("Deleted " + id);
  206. } catch (e, s) {
  207. _logger.severe("Could not delete file " + id, e, s);
  208. }
  209. }
  210. }
  211. }
  212. Navigator.of(dialogKey.currentContext, rootNavigator: true).pop('dialog');
  213. return deletedIDs;
  214. }