From b5fea82fce8d22980eb8c574e6dcbaadafbc81c2 Mon Sep 17 00:00:00 2001 From: Thibault Deckers Date: Tue, 29 Oct 2024 23:11:35 +0100 Subject: [PATCH] loader/completer review --- lib/model/entry/entry.dart | 12 +++--- lib/model/source/media_store_source.dart | 11 ++---- lib/model/source/trash.dart | 8 ++-- lib/services/app_service.dart | 10 ++--- lib/services/common/service_policy.dart | 14 +++---- lib/services/intent_service.dart | 10 ++--- lib/services/media/media_fetch_service.dart | 10 ++--- lib/services/storage_service.dart | 38 +++++++++---------- lib/utils/android_file_utils.dart | 13 +++---- .../common/action_mixins/feedback.dart | 6 +-- lib/widgets/home_page.dart | 6 +-- lib/widgets/viewer/view/histogram.dart | 27 +++++++------ 12 files changed, 79 insertions(+), 86 deletions(-) diff --git a/lib/model/entry/entry.dart b/lib/model/entry/entry.dart index a72423746..0a3f6ab98 100644 --- a/lib/model/entry/entry.dart +++ b/lib/model/entry/entry.dart @@ -461,17 +461,17 @@ class AvesEntry with AvesEntryBase { } Future delete() { - final completer = Completer(); + final opCompleter = Completer(); mediaEditService.delete(entries: {this}).listen( - (event) => completer.complete(event.success && !event.skipped), - onError: completer.completeError, + (event) => opCompleter.complete(event.success && !event.skipped), + onError: opCompleter.completeError, onDone: () { - if (!completer.isCompleted) { - completer.complete(false); + if (!opCompleter.isCompleted) { + opCompleter.complete(false); } }, ); - return completer.future; + return opCompleter.future; } // when the MIME type or the image itself changed (e.g. after rotation) diff --git a/lib/model/source/media_store_source.dart b/lib/model/source/media_store_source.dart index fa2f44df6..88acbcaed 100644 --- a/lib/model/source/media_store_source.dart +++ b/lib/model/source/media_store_source.dart @@ -23,6 +23,7 @@ class MediaStoreSource extends CollectionSource { int? _lastGeneration; SourceScope _loadedScope, _targetScope; bool _canAnalyze = true; + Future? _essentialLoader; @override set canAnalyze(bool enabled) => _canAnalyze = enabled; @@ -41,7 +42,8 @@ class MediaStoreSource extends CollectionSource { }) async { _targetScope = scope; await reportService.log('$runtimeType init target scope=$scope'); - await _loadEssentials(); + _essentialLoader ??= _loadEssentials(); + await _essentialLoader; addDirectories(albums: settings.pinnedFilters.whereType().map((v) => v.album).toSet()); await updateGeneration(); unawaited(_loadEntries( @@ -50,11 +52,7 @@ class MediaStoreSource extends CollectionSource { )); } - bool _areEssentialsLoaded = false; - Future _loadEssentials() async { - if (_areEssentialsLoaded) return; - final stopwatch = Stopwatch()..start(); state = SourceState.loading; await localMediaDb.init(); @@ -72,7 +70,6 @@ class MediaStoreSource extends CollectionSource { } } await loadDates(); - _areEssentialsLoaded = true; debugPrint('$runtimeType load essentials complete in ${stopwatch.elapsed.inMilliseconds}ms'); } @@ -260,7 +257,7 @@ class MediaStoreSource extends CollectionSource { // sometimes yields an entry with its temporary path: `/data/sec/camera/!@#$%^..._temp.jpg` @override Future> refreshUris(Set changedUris, {AnalysisController? analysisController}) async { - if (!canRefresh || !_areEssentialsLoaded || !isReady) return changedUris; + if (!canRefresh || _essentialLoader == null || !isReady) return changedUris; state = SourceState.loading; diff --git a/lib/model/source/trash.dart b/lib/model/source/trash.dart index b97ef24fa..f3107cbf5 100644 --- a/lib/model/source/trash.dart +++ b/lib/model/source/trash.dart @@ -24,18 +24,18 @@ mixin TrashMixin on SourceBase { if (expiredEntries.isEmpty) return {}; final processed = {}; - final completer = Completer>(); + final opCompleter = Completer>(); mediaEditService.delete(entries: expiredEntries).listen( processed.add, - onError: completer.completeError, + onError: opCompleter.completeError, onDone: () async { final successOps = processed.where((e) => e.success).toSet(); final deletedOps = successOps.where((e) => !e.skipped).toSet(); final deletedUris = deletedOps.map((event) => event.uri).toSet(); - completer.complete(deletedUris); + opCompleter.complete(deletedUris); }, ); - return await completer.future; + return await opCompleter.future; } Future> recoverUntrackedTrashItems() async { diff --git a/lib/services/app_service.dart b/lib/services/app_service.dart index eee552b1c..9bfb04dc2 100644 --- a/lib/services/app_service.dart +++ b/lib/services/app_service.dart @@ -104,21 +104,21 @@ class PlatformAppService implements AppService { @override Future> edit(String uri, String mimeType) async { try { - final completer = Completer(); + final opCompleter = Completer(); _stream.receiveBroadcastStream({ 'op': 'edit', 'uri': uri, 'mimeType': mimeType, }).listen( - (data) => completer.complete(data as Map?), - onError: completer.completeError, + (data) => opCompleter.complete(data as Map?), + onError: opCompleter.completeError, onDone: () { - if (!completer.isCompleted) completer.complete({'error': 'cancelled'}); + if (!opCompleter.isCompleted) opCompleter.complete({'error': 'cancelled'}); }, cancelOnError: true, ); // `await` here, so that `completeError` will be caught below - final result = await completer.future; + final result = await opCompleter.future; if (result == null) return {'error': 'cancelled'}; return result.cast(); } on PlatformException catch (e, stack) { diff --git a/lib/services/common/service_policy.dart b/lib/services/common/service_policy.dart index e2535533d..1b53ccc75 100644 --- a/lib/services/common/service_policy.dart +++ b/lib/services/common/service_policy.dart @@ -24,32 +24,32 @@ class ServicePolicy { int priority = ServiceCallPriority.normal, Object? key, }) { - Completer completer; + Completer taskCompleter; _Task task; key ??= platformCall.hashCode; final toResume = _paused.remove(key); if (toResume != null) { priority = toResume.$1; task = toResume.$2 as _Task; - completer = task.completer; + taskCompleter = task.completer; } else { - completer = Completer(); + taskCompleter = Completer(); task = _Task( () async { try { - completer.complete(await platformCall()); + taskCompleter.complete(await platformCall()); } catch (error, stack) { - completer.completeError(error, stack); + taskCompleter.completeError(error, stack); } _runningQueue.remove(key); _pickNext(); }, - completer, + taskCompleter, ); } _getQueue(priority)[key] = task; _pickNext(); - return completer.future; + return taskCompleter.future; } Future? resume(Object key) { diff --git a/lib/services/intent_service.dart b/lib/services/intent_service.dart index 5b1f1bd3d..9a750d668 100644 --- a/lib/services/intent_service.dart +++ b/lib/services/intent_service.dart @@ -47,23 +47,23 @@ class IntentService { static Future?> pickCollectionFilters(Set? initialFilters) async { try { - final completer = Completer?>(); + final opCompleter = Completer?>(); _stream.receiveBroadcastStream({ 'op': 'pickCollectionFilters', 'initialFilters': initialFilters?.map((filter) => filter.toJson()).toList(), }).listen( (data) { final result = (data as List?)?.cast().map(CollectionFilter.fromJson).nonNulls.toSet(); - completer.complete(result); + opCompleter.complete(result); }, - onError: completer.completeError, + onError: opCompleter.completeError, onDone: () { - if (!completer.isCompleted) completer.complete(null); + if (!opCompleter.isCompleted) opCompleter.complete(null); }, cancelOnError: true, ); // `await` here, so that `completeError` will be caught below - return await completer.future; + return await opCompleter.future; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } diff --git a/lib/services/media/media_fetch_service.dart b/lib/services/media/media_fetch_service.dart index 756e1d58e..bdbcca44a 100644 --- a/lib/services/media/media_fetch_service.dart +++ b/lib/services/media/media_fetch_service.dart @@ -120,7 +120,7 @@ class PlatformMediaFetchService implements MediaFetchService { BytesReceivedCallback? onBytesReceived, }) async { try { - final completer = Completer.sync(); + final opCompleter = Completer(); final sink = OutputBuffer(); var bytesReceived = 0; _byteStream.receiveBroadcastStream({ @@ -139,20 +139,20 @@ class PlatformMediaFetchService implements MediaFetchService { try { onBytesReceived(bytesReceived, sizeBytes); } catch (error, stack) { - completer.completeError(error, stack); + opCompleter.completeError(error, stack); return; } } }, - onError: completer.completeError, + onError: opCompleter.completeError, onDone: () { sink.close(); - completer.complete(sink.bytes); + opCompleter.complete(sink.bytes); }, cancelOnError: true, ); // `await` here, so that `completeError` will be caught below - return await completer.future; + return await opCompleter.future; } on PlatformException catch (e, stack) { if (_isUnknownVisual(mimeType)) { await reportService.recordError(e, stack); diff --git a/lib/services/storage_service.dart b/lib/services/storage_service.dart index bb597a7a4..62589161b 100644 --- a/lib/services/storage_service.dart +++ b/lib/services/storage_service.dart @@ -265,20 +265,20 @@ class PlatformStorageService implements StorageService { @override Future requestDirectoryAccess(String path) async { try { - final completer = Completer(); + final opCompleter = Completer(); _stream.receiveBroadcastStream({ 'op': 'requestDirectoryAccess', 'path': path, }).listen( - (data) => completer.complete(data as bool), - onError: completer.completeError, + (data) => opCompleter.complete(data as bool), + onError: opCompleter.completeError, onDone: () { - if (!completer.isCompleted) completer.complete(false); + if (!opCompleter.isCompleted) opCompleter.complete(false); }, cancelOnError: true, ); // `await` here, so that `completeError` will be caught below - return await completer.future; + return await opCompleter.future; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -289,21 +289,21 @@ class PlatformStorageService implements StorageService { @override Future requestMediaFileAccess(List uris, List mimeTypes) async { try { - final completer = Completer(); + final opCompleter = Completer(); _stream.receiveBroadcastStream({ 'op': 'requestMediaFileAccess', 'uris': uris, 'mimeTypes': mimeTypes, }).listen( - (data) => completer.complete(data as bool), - onError: completer.completeError, + (data) => opCompleter.complete(data as bool), + onError: opCompleter.completeError, onDone: () { - if (!completer.isCompleted) completer.complete(false); + if (!opCompleter.isCompleted) opCompleter.complete(false); }, cancelOnError: true, ); // `await` here, so that `completeError` will be caught below - return await completer.future; + return await opCompleter.future; } on PlatformException catch (e, stack) { final message = e.message; // mute issue in the specific case when an item: @@ -320,22 +320,22 @@ class PlatformStorageService implements StorageService { @override Future createFile(String name, String mimeType, Uint8List bytes) async { try { - final completer = Completer(); + final opCompleter = Completer(); _stream.receiveBroadcastStream({ 'op': 'createFile', 'name': name, 'mimeType': mimeType, 'bytes': bytes, }).listen( - (data) => completer.complete(data as bool?), - onError: completer.completeError, + (data) => opCompleter.complete(data as bool?), + onError: opCompleter.completeError, onDone: () { - if (!completer.isCompleted) completer.complete(false); + if (!opCompleter.isCompleted) opCompleter.complete(false); }, cancelOnError: true, ); // `await` here, so that `completeError` will be caught below - return await completer.future; + return await opCompleter.future; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -345,7 +345,7 @@ class PlatformStorageService implements StorageService { @override Future openFile([String? mimeType]) async { try { - final completer = Completer.sync(); + final opCompleter = Completer(); final sink = OutputBuffer(); _stream.receiveBroadcastStream({ 'op': 'openFile', @@ -355,15 +355,15 @@ class PlatformStorageService implements StorageService { final chunk = data as Uint8List; sink.add(chunk); }, - onError: completer.completeError, + onError: opCompleter.completeError, onDone: () { sink.close(); - completer.complete(sink.bytes); + opCompleter.complete(sink.bytes); }, cancelOnError: true, ); // `await` here, so that `completeError` will be caught below - return await completer.future; + return await opCompleter.future; } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } diff --git a/lib/utils/android_file_utils.dart b/lib/utils/android_file_utils.dart index b11f2f1d7..12b277132 100644 --- a/lib/utils/android_file_utils.dart +++ b/lib/utils/android_file_utils.dart @@ -1,3 +1,5 @@ +import 'dart:async'; + import 'package:aves/model/app_inventory.dart'; import 'package:aves/model/vaults/vaults.dart'; import 'package:aves/services/common/services.dart'; @@ -7,8 +9,6 @@ import 'package:flutter/foundation.dart'; final AndroidFileUtils androidFileUtils = AndroidFileUtils._private(); -enum _State { uninitialized, initializing, initialized } - class AndroidFileUtils { // cf https://developer.android.com/reference/android/content/ContentResolver#SCHEME_CONTENT static const contentScheme = 'content'; @@ -29,16 +29,13 @@ class AndroidFileUtils { late final String dcimPath, downloadPath, moviesPath, picturesPath, avesVideoCapturesPath; late final Set videoCapturesPaths; Set storageVolumes = {}; - _State _initialized = _State.uninitialized; + Future? _loader; AndroidFileUtils._private(); Future init() async { - if (_initialized == _State.uninitialized) { - _initialized = _State.initializing; - await _doInit(); - _initialized = _State.initialized; - } + _loader ??= _doInit(); + await _loader; } Future _doInit() async { diff --git a/lib/widgets/common/action_mixins/feedback.dart b/lib/widgets/common/action_mixins/feedback.dart index ccc562fa1..10de21fbb 100644 --- a/lib/widgets/common/action_mixins/feedback.dart +++ b/lib/widgets/common/action_mixins/feedback.dart @@ -138,7 +138,7 @@ mixin FeedbackMixin { VoidCallback? onCancel, Future Function(Set processed)? onDone, }) async { - final completer = Completer(); + final opCompleter = Completer(); await showDialog( context: context, barrierDismissible: false, @@ -149,12 +149,12 @@ mixin FeedbackMixin { onDone: (processed) async { Navigator.maybeOf(context)?.pop(); await onDone?.call(processed); - completer.complete(); + opCompleter.complete(); }, ), routeSettings: const RouteSettings(name: ReportOverlay.routeName), ); - return completer.future; + return opCompleter.future; } } diff --git a/lib/widgets/home_page.dart b/lib/widgets/home_page.dart index 8d48b5323..ec6478290 100644 --- a/lib/widgets/home_page.dart +++ b/lib/widgets/home_page.dart @@ -309,18 +309,18 @@ class _HomePageState extends State { final album = viewerEntry.directory; if (album != null) { // wait for collection to pass the `loading` state - final completer = Completer(); + final loadingCompleter = Completer(); final stateNotifier = source.stateNotifier; void _onSourceStateChanged() { if (stateNotifier.value != SourceState.loading) { stateNotifier.removeListener(_onSourceStateChanged); - completer.complete(); + loadingCompleter.complete(); } } stateNotifier.addListener(_onSourceStateChanged); _onSourceStateChanged(); - await completer.future; + await loadingCompleter.future; collection = CollectionLens( source: source, diff --git a/lib/widgets/viewer/view/histogram.dart b/lib/widgets/viewer/view/histogram.dart index 72f278101..3436fac9a 100644 --- a/lib/widgets/viewer/view/histogram.dart +++ b/lib/widgets/viewer/view/histogram.dart @@ -13,29 +13,28 @@ typedef HistogramLevels = Map>; mixin HistogramMixin { HistogramLevels _levels = {}; - Completer? _completer; + Future? _loader; static const int bins = 256; Future getHistogramLevels(ImageInfo info, bool forceUpdate) async { if (_levels.isEmpty || forceUpdate) { - if (_completer == null) { - _completer = Completer(); - final data = (await info.image.toByteData(format: ImageByteFormat.rawStraightRgba))!; - _levels = switch (settings.overlayHistogramStyle) { - OverlayHistogramStyle.rgb => await compute(_computeRgbLevels, data), - OverlayHistogramStyle.luminance => await compute(_computeLuminanceLevels, data), - _ => >{}, - }; - _completer?.complete(); - } else { - await _completer?.future; - _completer = null; - } + _loader ??= _getLevels(info); + await _loader; + _loader = null; } return _levels; } + Future _getLevels(ImageInfo info) async { + final data = (await info.image.toByteData(format: ImageByteFormat.rawStraightRgba))!; + _levels = switch (settings.overlayHistogramStyle) { + OverlayHistogramStyle.rgb => await compute(_computeRgbLevels, data), + OverlayHistogramStyle.luminance => await compute(_computeLuminanceLevels, data), + _ => >{}, + }; + } + static HistogramLevels _computeRgbLevels(ByteData data) { final redLevels = List.filled(bins, 0); final greenLevels = List.filled(bins, 0);