#251 #119 clean obsolete Media Store entry when trying to move to bin entry backed by a file that no longer exists

This commit is contained in:
Thibault Deckers 2022-05-22 19:10:41 +09:00
parent 4f5664a80a
commit 13bac7937b
6 changed files with 46 additions and 30 deletions

View file

@ -1045,6 +1045,9 @@ abstract class ImageProvider {
// used when skipping a move/creation op because the target file already exists // used when skipping a move/creation op because the target file already exists
val skippedFieldMap: HashMap<String, Any?> = hashMapOf("skipped" to true) val skippedFieldMap: HashMap<String, Any?> = hashMapOf("skipped" to true)
// used when deleting instead of moving to bin because the target file no longer exists
val deletedFieldMap: HashMap<String, Any?> = hashMapOf("deleted" to true)
fun isMediaUriPermissionGranted(context: Context, uri: Uri, mimeType: String): Boolean { fun isMediaUriPermissionGranted(context: Context, uri: Uri, mimeType: String): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val safeUri = StorageUtils.getMediaStoreScopedStorageSafeUri(uri, mimeType) val safeUri = StorageUtils.getMediaStoreScopedStorageSafeUri(uri, mimeType)

View file

@ -443,18 +443,23 @@ class MediaStoreImageProvider : ImageProvider() {
if (effectiveTargetDir != null) { if (effectiveTargetDir != null) {
val newFields = if (isCancelledOp()) skippedFieldMap else { val newFields = if (isCancelledOp()) skippedFieldMap else {
val sourceFile = File(sourcePath) val sourceFile = File(sourcePath)
moveSingle( if (!sourceFile.exists() && toBin) {
activity = activity, delete(activity, sourceUri, sourcePath, mimeType = mimeType)
sourceFile = sourceFile, deletedFieldMap
sourceUri = sourceUri, } else {
targetDir = effectiveTargetDir, moveSingle(
targetDirDocFile = targetDirDocFile, activity = activity,
desiredName = desiredName ?: sourceFile.name, sourceFile = sourceFile,
nameConflictStrategy = nameConflictStrategy, sourceUri = sourceUri,
mimeType = mimeType, targetDir = effectiveTargetDir,
copy = copy, targetDirDocFile = targetDirDocFile,
toBin = toBin, desiredName = desiredName ?: sourceFile.name,
) nameConflictStrategy = nameConflictStrategy,
mimeType = mimeType,
copy = copy,
toBin = toBin,
)
}
} }
result["newFields"] = newFields result["newFields"] = newFields
result["success"] = true result["success"] = true

View file

@ -28,29 +28,29 @@ class ImageOpEvent extends Equatable {
@immutable @immutable
class MoveOpEvent extends ImageOpEvent { class MoveOpEvent extends ImageOpEvent {
final Map newFields; final Map newFields;
final bool deleted;
@override @override
List<Object?> get props => [success, skipped, uri, newFields]; List<Object?> get props => [success, skipped, uri, newFields, deleted];
const MoveOpEvent({ const MoveOpEvent({
required bool success, required super.success,
required bool skipped, required super.skipped,
required String uri, required super.uri,
required this.newFields, required this.newFields,
}) : super( required this.deleted,
success: success, });
skipped: skipped,
uri: uri,
);
factory MoveOpEvent.fromMap(Map map) { factory MoveOpEvent.fromMap(Map map) {
final newFields = map['newFields'] ?? {}; final newFields = map['newFields'] ?? {};
final skipped = (map['skipped'] ?? false) || (newFields['skipped'] ?? false); final skipped = (map['skipped'] ?? false) || (newFields['skipped'] ?? false);
final deleted = (map['deleted'] ?? false) || (newFields['deleted'] ?? false);
return MoveOpEvent( return MoveOpEvent(
success: (map['success'] ?? false) || skipped, success: (map['success'] ?? false) || skipped,
skipped: skipped, skipped: skipped,
uri: map['uri'], uri: map['uri'],
newFields: newFields, newFields: newFields,
deleted: deleted,
); );
} }
} }
@ -63,16 +63,13 @@ class ExportOpEvent extends MoveOpEvent {
List<Object?> get props => [success, skipped, uri, pageId, newFields]; List<Object?> get props => [success, skipped, uri, pageId, newFields];
const ExportOpEvent({ const ExportOpEvent({
required bool success, required super.success,
required bool skipped, required super.skipped,
required String uri, required super.uri,
this.pageId, this.pageId,
required Map newFields, required super.newFields,
}) : super( }) : super(
success: success, deleted: false,
skipped: skipped,
uri: uri,
newFields: newFields,
); );
factory ExportOpEvent.fromMap(Map map) { factory ExportOpEvent.fromMap(Map map) {

View file

@ -132,7 +132,9 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
onCancel: () => mediaFileService.cancelFileOp(opId), onCancel: () => mediaFileService.cancelFileOp(opId),
onDone: (processed) async { onDone: (processed) async {
final successOps = processed.where((v) => v.success).toSet(); final successOps = processed.where((v) => v.success).toSet();
final movedOps = successOps.where((v) => !v.skipped).toSet();
// move
final movedOps = successOps.where((v) => !v.skipped && !v.deleted).toSet();
final movedEntries = movedOps.map((v) => v.uri).map((uri) => entries.firstWhereOrNull((entry) => entry.uri == uri)).whereNotNull().toSet(); final movedEntries = movedOps.map((v) => v.uri).map((uri) => entries.firstWhereOrNull((entry) => entry.uri == uri)).whereNotNull().toSet();
await source.updateAfterMove( await source.updateAfterMove(
todoEntries: entries, todoEntries: entries,
@ -140,6 +142,12 @@ mixin EntryStorageMixin on FeedbackMixin, PermissionAwareMixin, SizeAwareMixin {
destinationAlbums: destinationAlbums, destinationAlbums: destinationAlbums,
movedOps: movedOps, movedOps: movedOps,
); );
// delete (when trying to move to bin obsolete entries)
final deletedOps = successOps.where((v) => v.deleted).toSet();
final deletedUris = deletedOps.map((event) => event.uri).toSet();
await source.removeEntries(deletedUris, includeTrash: true);
source.resumeMonitoring(); source.resumeMonitoring();
// cleanup // cleanup

View file

@ -25,6 +25,7 @@ class FakeMediaFileService extends Fake implements MediaFileService {
'path': '${entry.directory}/$newName', 'path': '${entry.directory}/$newName',
'dateModifiedSecs': FakeMediaStoreService.dateSecs, 'dateModifiedSecs': FakeMediaStoreService.dateSecs,
}, },
deleted: false,
)); ));
} }
} }

View file

@ -57,6 +57,7 @@ class FakeMediaStoreService extends Fake implements MediaStoreService {
'path': entry.path!.replaceFirst(sourceAlbum, destinationAlbum), 'path': entry.path!.replaceFirst(sourceAlbum, destinationAlbum),
'dateModifiedSecs': FakeMediaStoreService.dateSecs, 'dateModifiedSecs': FakeMediaStoreService.dateSecs,
}, },
deleted: false,
); );
} }
@ -73,6 +74,7 @@ class FakeMediaStoreService extends Fake implements MediaStoreService {
'path': entry.path!.replaceFirst(oldName, newName), 'path': entry.path!.replaceFirst(oldName, newName),
'dateModifiedSecs': FakeMediaStoreService.dateSecs, 'dateModifiedSecs': FakeMediaStoreService.dateSecs,
}, },
deleted: false,
); );
} }
} }