loader/completer review
This commit is contained in:
parent
f0e048e340
commit
b5fea82fce
12 changed files with 79 additions and 86 deletions
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
|
|
|
@ -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 {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -13,27 +13,26 @@ 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();
|
||||
_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>>{},
|
||||
};
|
||||
_completer?.complete();
|
||||
} else {
|
||||
await _completer?.future;
|
||||
_completer = null;
|
||||
}
|
||||
}
|
||||
return _levels;
|
||||
}
|
||||
|
||||
static HistogramLevels _computeRgbLevels(ByteData data) {
|
||||
|
|
Loading…
Reference in a new issue