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;
final _mediaStoreSource = MediaStoreSource();
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
// the list itself needs to be reassigned
@ -193,7 +193,7 @@ class _AvesAppState extends State<AvesApp> {
if (uri != null) changedUris.add(uri);
if (changedUris.isNotEmpty) {
_contentChangeDebouncer(() async {
final todo = List.of(changedUris);
final todo = changedUris.toSet();
changedUris.clear();
final tempUris = await _mediaStoreSource.refreshUris(todo);
if (tempUris.isNotEmpty) {

View file

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

View file

@ -205,6 +205,16 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
Future<void> refreshMetadata(Set<AvesEntry> entries);
// monitoring
bool _monitoring = true;
void pauseMonitoring() => _monitoring = false;
void resumeMonitoring() => _monitoring = true;
bool get isMonitoring => _monitoring;
// filter summary
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
// sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg`
Future<List<String>> refreshUris(List<String> changedUris) async {
final tempUris = <String>[];
if (!_initialized) return tempUris;
Future<Set<String>> refreshUris(Set<String> changedUris) async {
if (!_initialized || !isMonitoring) return changedUris;
final uriByContentId = Map.fromEntries(changedUris.map((uri) {
if (uri == null) return null;
@ -129,6 +130,7 @@ class MediaStoreSource extends CollectionSource {
obsoleteContentIds.forEach(uriByContentId.remove);
// fetch new entries
final tempUris = <String>{};
final newEntries = <AvesEntry>{};
for (final kv in uriByContentId.entries) {
final contentId = kv.key;

View file

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

View file

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

View file

@ -55,7 +55,6 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
break;
case CollectionAction.refreshMetadata:
source.refreshMetadata(selection);
collection.clearSelection();
collection.browse();
break;
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
// so we save favourites beforehand, and will mark the moved entries as such after the move
final favouriteEntries = todoEntries.where((entry) => entry.isFavourite).toSet();
source.pauseMonitoring();
showOpReport<MoveOpEvent>(
context: context,
opStream: ImageFileService.move(todoEntries, copy: copy, destinationAlbum: destinationAlbum),
@ -108,8 +108,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
destinationAlbum: destinationAlbum,
movedOps: movedOps,
);
collection.clearSelection();
collection.browse();
source.resumeMonitoring();
},
);
}
@ -141,6 +141,7 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
if (!await checkStoragePermission(context, selection)) return;
final selectionCount = selection.length;
source.pauseMonitoring();
showOpReport<ImageOpEvent>(
context: context,
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')}');
}
source.removeEntries(deletedUris);
collection.clearSelection();
collection.browse();
source.resumeMonitoring();
},
);
}

View file

@ -87,6 +87,7 @@ class AlbumChipActionDelegate extends ChipActionDelegate with FeedbackMixin, Per
if (!await checkStoragePermission(context, selection)) return;
final selectionCount = selection.length;
source.pauseMonitoring();
showOpReport<ImageOpEvent>(
context: context,
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')}');
}
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
// so we save favourites beforehand, and will mark the moved entries as such after the move
final favouriteEntries = todoEntries.where((entry) => entry.isFavourite).toSet();
source.pauseMonitoring();
showOpReport<MoveOpEvent>(
context: context,
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));
settings.pinnedFilters = settings.pinnedFilters..add(newFilter);
}
source.resumeMonitoring();
},
);
}