delete_file_util.dart 7.9 KB

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