media store monitoring: pause monitoring during bulk op

This commit is contained in:
Thibault Deckers 2021-02-09 14:14:48 +09:00
parent ea3d79afbe
commit 3d12825e68
8 changed files with 34 additions and 18 deletions

View file

@ -49,7 +49,7 @@ class _AvesAppState extends State<AvesApp> {
Future<void> _appSetup; Future<void> _appSetup;
final _mediaStoreSource = MediaStoreSource(); final _mediaStoreSource = MediaStoreSource();
final Debouncer _contentChangeDebouncer = Debouncer(delay: Durations.contentChangeDebounceDelay); final Debouncer _contentChangeDebouncer = Debouncer(delay: Durations.contentChangeDebounceDelay);
final List<String> changedUris = []; final Set<String> changedUris = {};
// observers are not registered when using the same list object with different items // observers are not registered when using the same list object with different items
// the list itself needs to be reassigned // the list itself needs to be reassigned
@ -193,7 +193,7 @@ class _AvesAppState extends State<AvesApp> {
if (uri != null) changedUris.add(uri); if (uri != null) changedUris.add(uri);
if (changedUris.isNotEmpty) { if (changedUris.isNotEmpty) {
_contentChangeDebouncer(() async { _contentChangeDebouncer(() async {
final todo = List.of(changedUris); final todo = changedUris.toSet();
changedUris.clear(); changedUris.clear();
final tempUris = await _mediaStoreSource.refreshUris(todo); final tempUris = await _mediaStoreSource.refreshUris(todo);
if (tempUris.isNotEmpty) { if (tempUris.isNotEmpty) {

View file

@ -16,7 +16,7 @@ import 'package:flutter/foundation.dart';
import 'enums.dart'; import 'enums.dart';
class CollectionLens with ChangeNotifier, CollectionActivityMixin, CollectionSelectionMixin { class CollectionLens with ChangeNotifier, CollectionActivityMixin {
final CollectionSource source; final CollectionSource source;
final Set<CollectionFilter> filters; final Set<CollectionFilter> filters;
EntryGroupFactor groupFactor; EntryGroupFactor groupFactor;
@ -209,12 +209,15 @@ mixin CollectionActivityMixin {
bool get isSelecting => _activityNotifier.value == Activity.select; bool get isSelecting => _activityNotifier.value == Activity.select;
void browse() => _activityNotifier.value = Activity.browse; void browse() {
clearSelection();
void select() => _activityNotifier.value = Activity.select; _activityNotifier.value = Activity.browse;
} }
mixin CollectionSelectionMixin on CollectionActivityMixin { void select() => _activityNotifier.value = Activity.select;
// selection
final AChangeNotifier selectionChangeNotifier = AChangeNotifier(); final AChangeNotifier selectionChangeNotifier = AChangeNotifier();
final Set<AvesEntry> _selection = {}; final Set<AvesEntry> _selection = {};

View file

@ -205,6 +205,16 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
Future<void> refreshMetadata(Set<AvesEntry> entries); Future<void> refreshMetadata(Set<AvesEntry> entries);
// monitoring
bool _monitoring = true;
void pauseMonitoring() => _monitoring = false;
void resumeMonitoring() => _monitoring = true;
bool get isMonitoring => _monitoring;
// filter summary // filter summary
int count(CollectionFilter filter) { int count(CollectionFilter filter) {

View file

@ -107,12 +107,13 @@ class MediaStoreSource extends CollectionSource {
); );
} }
// returns URIs that are in the Media Store but still being processed by their owner in a temporary location // returns URIs to retry later. They could be URIs that are:
// 1) currently being processed during bulk move/deletion
// 2) registered in the Media Store but still being processed by their owner in a temporary location
// For example, when taking a picture with a Galaxy S10e default camera app, querying the Media Store // For example, when taking a picture with a Galaxy S10e default camera app, querying the Media Store
// sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg` // sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg`
Future<List<String>> refreshUris(List<String> changedUris) async { Future<Set<String>> refreshUris(Set<String> changedUris) async {
final tempUris = <String>[]; if (!_initialized || !isMonitoring) return changedUris;
if (!_initialized) return tempUris;
final uriByContentId = Map.fromEntries(changedUris.map((uri) { final uriByContentId = Map.fromEntries(changedUris.map((uri) {
if (uri == null) return null; if (uri == null) return null;
@ -129,6 +130,7 @@ class MediaStoreSource extends CollectionSource {
obsoleteContentIds.forEach(uriByContentId.remove); obsoleteContentIds.forEach(uriByContentId.remove);
// fetch new entries // fetch new entries
final tempUris = <String>{};
final newEntries = <AvesEntry>{}; final newEntries = <AvesEntry>{};
for (final kv in uriByContentId.entries) { for (final kv in uriByContentId.entries) {
final contentId = kv.key; final contentId = kv.key;

View file

@ -124,10 +124,7 @@ class _CollectionAppBarState extends State<CollectionAppBar> with SingleTickerPr
onPressed = Scaffold.of(context).openDrawer; onPressed = Scaffold.of(context).openDrawer;
tooltip = MaterialLocalizations.of(context).openAppDrawerTooltip; tooltip = MaterialLocalizations.of(context).openAppDrawerTooltip;
} else if (collection.isSelecting) { } else if (collection.isSelecting) {
onPressed = () { onPressed = collection.browse;
collection.clearSelection();
collection.browse();
};
tooltip = MaterialLocalizations.of(context).backButtonTooltip; tooltip = MaterialLocalizations.of(context).backButtonTooltip;
} }
return IconButton( return IconButton(

View file

@ -37,7 +37,6 @@ class _CollectionPageState extends State<CollectionPage> {
body: WillPopScope( body: WillPopScope(
onWillPop: () { onWillPop: () {
if (collection.isSelecting) { if (collection.isSelecting) {
collection.clearSelection();
collection.browse(); collection.browse();
return SynchronousFuture(false); return SynchronousFuture(false);
} }

View file

@ -55,7 +55,6 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
break; break;
case CollectionAction.refreshMetadata: case CollectionAction.refreshMetadata:
source.refreshMetadata(selection); source.refreshMetadata(selection);
collection.clearSelection();
collection.browse(); collection.browse();
break; break;
default: default:
@ -87,6 +86,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
// while the move is ongoing, source monitoring may remove entries from itself and the favourites repo // while the move is ongoing, source monitoring may remove entries from itself and the favourites repo
// so we save favourites beforehand, and will mark the moved entries as such after the move // so we save favourites beforehand, and will mark the moved entries as such after the move
final favouriteEntries = todoEntries.where((entry) => entry.isFavourite).toSet(); final favouriteEntries = todoEntries.where((entry) => entry.isFavourite).toSet();
source.pauseMonitoring();
showOpReport<MoveOpEvent>( showOpReport<MoveOpEvent>(
context: context, context: context,
opStream: ImageFileService.move(todoEntries, copy: copy, destinationAlbum: destinationAlbum), opStream: ImageFileService.move(todoEntries, copy: copy, destinationAlbum: destinationAlbum),
@ -108,8 +108,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
destinationAlbum: destinationAlbum, destinationAlbum: destinationAlbum,
movedOps: movedOps, movedOps: movedOps,
); );
collection.clearSelection();
collection.browse(); collection.browse();
source.resumeMonitoring();
}, },
); );
} }
@ -141,6 +141,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
if (!await checkStoragePermission(context, selection)) return; if (!await checkStoragePermission(context, selection)) return;
final selectionCount = selection.length; final selectionCount = selection.length;
source.pauseMonitoring();
showOpReport<ImageOpEvent>( showOpReport<ImageOpEvent>(
context: context, context: context,
opStream: ImageFileService.delete(selection), opStream: ImageFileService.delete(selection),
@ -153,8 +154,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}'); showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}');
} }
source.removeEntries(deletedUris); source.removeEntries(deletedUris);
collection.clearSelection();
collection.browse(); collection.browse();
source.resumeMonitoring();
}, },
); );
} }

View file

@ -87,6 +87,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
if (!await checkStoragePermission(context, selection)) return; if (!await checkStoragePermission(context, selection)) return;
final selectionCount = selection.length; final selectionCount = selection.length;
source.pauseMonitoring();
showOpReport<ImageOpEvent>( showOpReport<ImageOpEvent>(
context: context, context: context,
opStream: ImageFileService.delete(selection), opStream: ImageFileService.delete(selection),
@ -99,6 +100,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}'); showFeedback(context, 'Failed to delete ${Intl.plural(count, one: '$count item', other: '$count items')}');
} }
source.removeEntries(deletedUris); source.removeEntries(deletedUris);
source.resumeMonitoring();
}, },
); );
} }
@ -122,6 +124,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
// while the move is ongoing, source monitoring may remove entries from itself and the favourites repo // while the move is ongoing, source monitoring may remove entries from itself and the favourites repo
// so we save favourites beforehand, and will mark the moved entries as such after the move // so we save favourites beforehand, and will mark the moved entries as such after the move
final favouriteEntries = todoEntries.where((entry) => entry.isFavourite).toSet(); final favouriteEntries = todoEntries.where((entry) => entry.isFavourite).toSet();
source.pauseMonitoring();
showOpReport<MoveOpEvent>( showOpReport<MoveOpEvent>(
context: context, context: context,
opStream: ImageFileService.move(todoEntries, copy: false, destinationAlbum: destinationAlbum), opStream: ImageFileService.move(todoEntries, copy: false, destinationAlbum: destinationAlbum),
@ -148,6 +151,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
final newFilter = AlbumFilter(destinationAlbum, source.getUniqueAlbumName(destinationAlbum)); final newFilter = AlbumFilter(destinationAlbum, source.getUniqueAlbumName(destinationAlbum));
settings.pinnedFilters = settings.pinnedFilters..add(newFilter); settings.pinnedFilters = settings.pinnedFilters..add(newFilter);
} }
source.resumeMonitoring();
}, },
); );
} }