diff --git a/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java b/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java index 3141e564b..47b6422c0 100644 --- a/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java +++ b/android/app/src/main/java/deckers/thibault/aves/model/provider/ImageProvider.java @@ -29,10 +29,8 @@ import java.util.Collection; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.Objects; import java.util.concurrent.ExecutionException; import java.util.stream.Collectors; -import java.util.stream.Stream; import deckers.thibault.aves.model.AvesImageEntry; import deckers.thibault.aves.utils.MetadataHelper; @@ -128,33 +126,53 @@ public abstract class ImageProvider { entriesByBaseContentUri.forEach((baseContentUri, entries) -> { int count = entries.size(); if (count > 0) { + String[] mimeTypes = new String[count]; String[] oldPaths = new String[count]; String[] newPaths = new String[count]; - String[] mimeTypes = new String[count]; + Map oldContentIdByPath = new HashMap<>(); + Map>> scanFutureByPath = new HashMap<>(); for (int i = 0; i < count; i++) { Map entry = entries.get(i); String displayName = (String) entry.get("displayName"); - oldPaths[i] = oldDirPath + displayName; - newPaths[i] = newDirPath + displayName; + String newPath = newDirPath + displayName; mimeTypes[i] = (String) entry.get("mimeType"); + oldPaths[i] = oldDirPath + displayName; + newPaths[i] = newPath; + oldContentIdByPath.put(newPath, (Integer) entry.get("oldContentId")); + scanFutureByPath.put(newPath, SettableFuture.create()); } MediaScannerConnection.scanFile(context, oldPaths, mimeTypes, null); - scanFutures.addAll(scanNewPaths(context, newPaths, mimeTypes, baseContentUri)); + MediaScannerConnection.scanFile(context, newPaths, mimeTypes, (path, rawUri) -> { + SettableFuture> future = scanFutureByPath.get(path); + if (future == null) { + Log.e(LOG_TAG, "no future for path=" + path); + return; + } + + if (rawUri == null) { + future.setException(new Exception("failed to get URI of item at path=" + path)); + return; + } + + // newURI is possibly a file media URI (e.g. "content://media/12a9-8b42/file/62872") + // but we need an image/video media URI (e.g. "content://media/external/images/media/62872") + long contentId = ContentUris.parseId(rawUri); + Uri contentUri = ContentUris.withAppendedId(baseContentUri, contentId); + Map newFields = new HashMap() {{ + put("path", path); + put("uri", contentUri.toString()); + put("contentId", contentId); + put("oldContentId", oldContentIdByPath.get(path)); + }}; + future.set(newFields); + }); + + scanFutures.addAll(scanFutureByPath.values()); } }); try { - List> scanResults = Futures.allAsList(scanFutures).get(); - Stream> allEntries = entriesByBaseContentUri.values().stream().flatMap(Collection::stream); - Map oldContentIdByDisplayName = allEntries.collect(Collectors.toMap( - fields -> (String) fields.get("displayName"), - fields -> (Integer) Objects.requireNonNull(fields.get("oldContentId")) - )); - scanResults.forEach(newFields -> { - String displayName = (String) newFields.get("displayName"); - newFields.put("oldContentId", oldContentIdByDisplayName.get(displayName)); - }); - callback.onSuccess(scanResults); + callback.onSuccess(Futures.allAsList(scanFutures).get()); } catch (ExecutionException | InterruptedException e) { callback.onFailure(e); } @@ -341,64 +359,6 @@ public abstract class ImageProvider { // } } - protected Collection>> scanNewPaths(final Context context, final String[] paths, final String[] mimeTypes, final Uri baseContentUri) { - Map>> scanFutures = new HashMap<>(); - for (String path : paths) { - scanFutures.put(path, SettableFuture.create()); - } - - MediaScannerConnection.scanFile(context, paths, mimeTypes, (path, rawUri) -> { - SettableFuture> future = scanFutures.get(path); - if (future == null) { - Log.e(LOG_TAG, "no future for path=" + path); - return; - } - - if (rawUri == null) { - future.setException(new Exception("failed to get URI of item at path=" + path)); - return; - } - - // newURI is possibly a file media URI (e.g. "content://media/12a9-8b42/file/62872") - // but we need an image/video media URI (e.g. "content://media/external/images/media/62872") - long contentId = ContentUris.parseId(rawUri); - Uri contentUri = ContentUris.withAppendedId(baseContentUri, contentId); - - Map newFields = new HashMap<>(); - // we retrieve updated fields as the renamed/moved file became a new entry in the Media Store - String[] projection = { - MediaStore.MediaColumns.DISPLAY_NAME, - MediaStore.MediaColumns.TITLE, - MediaStore.MediaColumns.DATE_MODIFIED, - }; - try { - Cursor cursor = context.getContentResolver().query(contentUri, projection, null, null, null); - if (cursor != null) { - if (cursor.moveToNext()) { - newFields.put("uri", contentUri.toString()); - newFields.put("contentId", contentId); - newFields.put("path", path); - newFields.put("displayName", cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME))); - newFields.put("title", cursor.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.TITLE))); - newFields.put("dateModifiedSecs", cursor.getInt(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DATE_MODIFIED))); - } - cursor.close(); - } - } catch (Exception e) { - future.setException(e); - return; - } - - if (newFields.isEmpty()) { - future.setException(new Exception("failed to get item details from provider at contentUri=" + contentUri)); - } else { - future.set(newFields); - } - }); - - return scanFutures.values(); - } - protected void scanNewPath(final Context context, final String path, final String mimeType, final ImageOpCallback callback) { MediaScannerConnection.scanFile(context, new String[]{path}, new String[]{mimeType}, (newPath, newUri) -> { long contentId = 0; diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index 3f2c173d7..89855ce28 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -88,12 +88,15 @@ class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagMixin { invalidateFilterEntryCounts(); } + // `dateModifiedSecs` changes when moving entries to another directory, + // but it does not change when renaming the containing directory Future moveEntry(ImageEntry entry, Map newFields) async { final oldContentId = entry.contentId; final newContentId = newFields['contentId'] as int; - entry.uri = newFields['uri'] as String; + final newDateModifiedSecs = newFields['dateModifiedSecs'] as int; + if (newDateModifiedSecs != null) entry.dateModifiedSecs = newDateModifiedSecs; entry.path = newFields['path'] as String; - entry.dateModifiedSecs = newFields['dateModifiedSecs'] as int; + entry.uri = newFields['uri'] as String; entry.contentId = newContentId; entry.catalogMetadata = entry.catalogMetadata?.copyWith(contentId: newContentId); entry.addressDetails = entry.addressDetails?.copyWith(contentId: newContentId);