loader/completer review

This commit is contained in:
Thibault Deckers 2024-10-29 23:11:35 +01:00
parent f0e048e340
commit b5fea82fce
12 changed files with 79 additions and 86 deletions

View file

@ -461,17 +461,17 @@ class AvesEntry with AvesEntryBase {
}
Future<bool> delete() {
final completer = Completer<bool>();
final opCompleter = Completer<bool>();
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)

View file

@ -23,6 +23,7 @@ class MediaStoreSource extends CollectionSource {
int? _lastGeneration;
SourceScope _loadedScope, _targetScope;
bool _canAnalyze = true;
Future<void>? _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<AlbumFilter>().map((v) => v.album).toSet());
await updateGeneration();
unawaited(_loadEntries(
@ -50,11 +52,7 @@ class MediaStoreSource extends CollectionSource {
));
}
bool _areEssentialsLoaded = false;
Future<void> _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<Set<String>> refreshUris(Set<String> changedUris, {AnalysisController? analysisController}) async {
if (!canRefresh || !_areEssentialsLoaded || !isReady) return changedUris;
if (!canRefresh || _essentialLoader == null || !isReady) return changedUris;
state = SourceState.loading;

View file

@ -24,18 +24,18 @@ mixin TrashMixin on SourceBase {
if (expiredEntries.isEmpty) return {};
final processed = <ImageOpEvent>{};
final completer = Completer<Set<String>>();
final opCompleter = Completer<Set<String>>();
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<Set<AvesEntry>> recoverUntrackedTrashItems() async {

View file

@ -104,21 +104,21 @@ class PlatformAppService implements AppService {
@override
Future<Map<String, dynamic>> edit(String uri, String mimeType) async {
try {
final completer = Completer<Map?>();
final opCompleter = Completer<Map?>();
_stream.receiveBroadcastStream(<String, dynamic>{
'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<String, dynamic>();
} on PlatformException catch (e, stack) {

View file

@ -24,32 +24,32 @@ class ServicePolicy {
int priority = ServiceCallPriority.normal,
Object? key,
}) {
Completer<T> completer;
Completer<T> taskCompleter;
_Task<T> task;
key ??= platformCall.hashCode;
final toResume = _paused.remove(key);
if (toResume != null) {
priority = toResume.$1;
task = toResume.$2 as _Task<T>;
completer = task.completer;
taskCompleter = task.completer;
} else {
completer = Completer<T>();
taskCompleter = Completer<T>();
task = _Task<T>(
() 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<T>? resume<T>(Object key) {

View file

@ -47,23 +47,23 @@ class IntentService {
static Future<Set<CollectionFilter>?> pickCollectionFilters(Set<CollectionFilter>? initialFilters) async {
try {
final completer = Completer<Set<CollectionFilter>?>();
final opCompleter = Completer<Set<CollectionFilter>?>();
_stream.receiveBroadcastStream(<String, dynamic>{
'op': 'pickCollectionFilters',
'initialFilters': initialFilters?.map((filter) => filter.toJson()).toList(),
}).listen(
(data) {
final result = (data as List?)?.cast<String>().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);
}

View file

@ -120,7 +120,7 @@ class PlatformMediaFetchService implements MediaFetchService {
BytesReceivedCallback? onBytesReceived,
}) async {
try {
final completer = Completer<Uint8List>.sync();
final opCompleter = Completer<Uint8List>();
final sink = OutputBuffer();
var bytesReceived = 0;
_byteStream.receiveBroadcastStream(<String, dynamic>{
@ -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);

View file

@ -265,20 +265,20 @@ class PlatformStorageService implements StorageService {
@override
Future<bool> requestDirectoryAccess(String path) async {
try {
final completer = Completer<bool>();
final opCompleter = Completer<bool>();
_stream.receiveBroadcastStream(<String, dynamic>{
'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<bool> requestMediaFileAccess(List<String> uris, List<String> mimeTypes) async {
try {
final completer = Completer<bool>();
final opCompleter = Completer<bool>();
_stream.receiveBroadcastStream(<String, dynamic>{
'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<bool?> createFile(String name, String mimeType, Uint8List bytes) async {
try {
final completer = Completer<bool?>();
final opCompleter = Completer<bool?>();
_stream.receiveBroadcastStream(<String, dynamic>{
'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<Uint8List> openFile([String? mimeType]) async {
try {
final completer = Completer<Uint8List>.sync();
final opCompleter = Completer<Uint8List>();
final sink = OutputBuffer();
_stream.receiveBroadcastStream(<String, dynamic>{
'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);
}

View file

@ -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<String> videoCapturesPaths;
Set<StorageVolume> storageVolumes = {};
_State _initialized = _State.uninitialized;
Future<void>? _loader;
AndroidFileUtils._private();
Future<void> init() async {
if (_initialized == _State.uninitialized) {
_initialized = _State.initializing;
await _doInit();
_initialized = _State.initialized;
}
_loader ??= _doInit();
await _loader;
}
Future<void> _doInit() async {

View file

@ -138,7 +138,7 @@ mixin FeedbackMixin {
VoidCallback? onCancel,
Future<void> Function(Set<T> 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;
}
}

View file

@ -309,18 +309,18 @@ class _HomePageState extends State<HomePage> {
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,

View file

@ -13,29 +13,28 @@ typedef HistogramLevels = Map<HistogramChannel, List<double>>;
mixin HistogramMixin {
HistogramLevels _levels = {};
Completer? _completer;
Future<void>? _loader;
static const int bins = 256;
Future<HistogramLevels> 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<ByteData, HistogramLevels>(_computeRgbLevels, data),
OverlayHistogramStyle.luminance => await compute<ByteData, HistogramLevels>(_computeLuminanceLevels, data),
_ => <HistogramChannel, List<double>>{},
};
_completer?.complete();
} else {
await _completer?.future;
_completer = null;
}
_loader ??= _getLevels(info);
await _loader;
_loader = null;
}
return _levels;
}
Future<void> _getLevels(ImageInfo info) async {
final data = (await info.image.toByteData(format: ImageByteFormat.rawStraightRgba))!;
_levels = switch (settings.overlayHistogramStyle) {
OverlayHistogramStyle.rgb => await compute<ByteData, HistogramLevels>(_computeRgbLevels, data),
OverlayHistogramStyle.luminance => await compute<ByteData, HistogramLevels>(_computeLuminanceLevels, data),
_ => <HistogramChannel, List<double>>{},
};
}
static HistogramLevels _computeRgbLevels(ByteData data) {
final redLevels = List.filled(bins, 0);
final greenLevels = List.filled(bins, 0);