diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisService.kt b/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisService.kt index b96611fed..c35efa712 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisService.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisService.kt @@ -157,12 +157,17 @@ class AnalysisService : MethodChannel.MethodCallHandler, Service() { override fun handleMessage(msg: Message) { val context = this@AnalysisService val data = msg.data - Log.d(LOG_TAG, "handleMessage data=$data") when (data.getString(KEY_COMMAND)) { COMMAND_START -> { runBlocking { context.runOnUiThread { - backgroundChannel?.invokeMethod("start", null) + val contentIds = data.get(KEY_CONTENT_IDS)?.takeIf { it is IntArray }?.let { (it as IntArray).toList() } + backgroundChannel?.invokeMethod( + "start", hashMapOf( + "contentIds" to contentIds, + "force" to data.getBoolean(KEY_FORCE), + ) + ) } } } @@ -194,6 +199,8 @@ class AnalysisService : MethodChannel.MethodCallHandler, Service() { const val KEY_COMMAND = "command" const val COMMAND_START = "start" const val COMMAND_STOP = "stop" + const val KEY_CONTENT_IDS = "content_ids" + const val KEY_FORCE = "force" } } diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AnalysisHandler.kt b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AnalysisHandler.kt index a402b24ca..8f0a263b5 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AnalysisHandler.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/channel/calls/AnalysisHandler.kt @@ -44,10 +44,21 @@ class AnalysisHandler(private val activity: Activity, private val onAnalysisComp result.success(true) } - private fun startAnalysis(@Suppress("UNUSED_PARAMETER") call: MethodCall, result: MethodChannel.Result) { + private fun startAnalysis(call: MethodCall, result: MethodChannel.Result) { + val force = call.argument("force") + if (force == null) { + result.error("startAnalysis-args", "failed because of missing arguments", null) + return + } + + // can be null or empty + val contentIds = call.argument>("contentIds"); + if (!activity.isMyServiceRunning(AnalysisService::class.java)) { val intent = Intent(activity, AnalysisService::class.java) intent.putExtra(AnalysisService.KEY_COMMAND, AnalysisService.COMMAND_START) + intent.putExtra(AnalysisService.KEY_CONTENT_IDS, contentIds?.toIntArray()) + intent.putExtra(AnalysisService.KEY_FORCE, force) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { activity.startForegroundService(intent) } else { diff --git a/lib/model/source/analysis_controller.dart b/lib/model/source/analysis_controller.dart index f7a8e97d9..aeac9dd31 100644 --- a/lib/model/source/analysis_controller.dart +++ b/lib/model/source/analysis_controller.dart @@ -2,10 +2,12 @@ import 'package:flutter/foundation.dart'; class AnalysisController { final bool canStartService, force; + final List? contentIds; final ValueNotifier stopSignal; AnalysisController({ this.canStartService = true, + this.contentIds, this.force = false, ValueNotifier? stopSignal, }) : stopSignal = stopSignal ?? ValueNotifier(false); diff --git a/lib/model/source/collection_source.dart b/lib/model/source/collection_source.dart index 95f8cd9c7..3475dab32 100644 --- a/lib/model/source/collection_source.dart +++ b/lib/model/source/collection_source.dart @@ -37,8 +37,6 @@ mixin SourceBase { } abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagMixin { - static const _analysisServiceOpCountThreshold = 400; - final EventBus _eventBus = EventBus(); @override @@ -269,30 +267,39 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM eventBus.fire(EntryRefreshedEvent({entry})); } - Future analyze(AnalysisController? analysisController, Set candidateEntries) async { - final todoEntries = visibleEntries; + Future analyze(AnalysisController? analysisController, {Set? entries}) async { + final todoEntries = entries ?? visibleEntries; final _analysisController = analysisController ?? AnalysisController(); + final force = _analysisController.force; if (!_analysisController.isStopping) { - late bool startAnalysisService; + var startAnalysisService = false; if (_analysisController.canStartService && settings.canUseAnalysisService) { - final force = _analysisController.force; - var opCount = 0; - opCount += (force ? todoEntries : todoEntries.where(TagMixin.catalogEntriesTest)).length; - opCount += (force ? todoEntries.where((entry) => entry.hasGps) : todoEntries.where(LocationMixin.locateCountriesTest)).length; - if (await availability.canLocatePlaces) { - opCount += (force ? todoEntries.where((entry) => entry.hasGps) : todoEntries.where(LocationMixin.locatePlacesTest)).length; + // cataloguing + if (!startAnalysisService) { + final opCount = (force ? todoEntries : todoEntries.where(TagMixin.catalogEntriesTest)).length; + if (opCount > TagMixin.commitCountThreshold) { + startAnalysisService = true; + } + } + // ignore locating countries + // locating places + if (!startAnalysisService && await availability.canLocatePlaces) { + final opCount = (force ? todoEntries.where((entry) => entry.hasGps) : todoEntries.where(LocationMixin.locatePlacesTest)).length; + if (opCount > LocationMixin.commitCountThreshold) { + startAnalysisService = true; + } } - startAnalysisService = opCount > _analysisServiceOpCountThreshold; - } else { - startAnalysisService = false; } if (startAnalysisService) { - await AnalysisService.startService(); + await AnalysisService.startService( + force: force, + contentIds: entries?.map((entry) => entry.contentId).whereNotNull().toList(), + ); } else { - await catalogEntries(_analysisController, candidateEntries); - updateDerivedFilters(candidateEntries); - await locateEntries(_analysisController, candidateEntries); - updateDerivedFilters(candidateEntries); + await catalogEntries(_analysisController, todoEntries); + updateDerivedFilters(todoEntries); + await locateEntries(_analysisController, todoEntries); + updateDerivedFilters(todoEntries); } } stateNotifier.value = SourceState.ready; @@ -347,7 +354,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM if (visible) { final candidateEntries = visibleEntries.where((entry) => filters.any((f) => f.test(entry))).toSet(); - analyze(null, candidateEntries); + analyze(null, entries: candidateEntries); } } } diff --git a/lib/model/source/location.dart b/lib/model/source/location.dart index db1c096ad..764025ce8 100644 --- a/lib/model/source/location.dart +++ b/lib/model/source/location.dart @@ -14,8 +14,8 @@ import 'package:flutter/foundation.dart'; import 'package:tuple/tuple.dart'; mixin LocationMixin on SourceBase { - static const _commitCountThreshold = 80; - static const _stopCheckCountThreshold = 20; + static const commitCountThreshold = 200; + static const _stopCheckCountThreshold = 50; List sortedCountries = List.unmodifiable([]); List sortedPlaces = List.unmodifiable([]); @@ -118,7 +118,7 @@ mixin LocationMixin on SourceBase { } if (entry.hasFineAddress) { newAddresses.add(entry.addressDetails!); - if (newAddresses.length >= _commitCountThreshold) { + if (newAddresses.length >= commitCountThreshold) { await metadataDb.saveAddresses(Set.unmodifiable(newAddresses)); onAddressMetadataChanged(); newAddresses.clear(); diff --git a/lib/model/source/media_store_source.dart b/lib/model/source/media_store_source.dart index f76b6c326..124de29ad 100644 --- a/lib/model/source/media_store_source.dart +++ b/lib/model/source/media_store_source.dart @@ -111,7 +111,12 @@ class MediaStoreSource extends CollectionSource { updateDirectories(); } - await analyze(analysisController, visibleEntries); + Set? analysisEntries; + final analysisIds = analysisController?.contentIds; + if (analysisIds != null) { + analysisEntries = visibleEntries.where((entry) => analysisIds.contains(entry.contentId)).toSet(); + } + await analyze(analysisController, entries: analysisEntries); debugPrint('$runtimeType refresh ${stopwatch.elapsed} done for ${oldEntries.length} known, ${allNewEntries.length} new, ${obsoleteContentIds.length} obsolete'); }, @@ -179,7 +184,7 @@ class MediaStoreSource extends CollectionSource { await metadataDb.saveEntries(newEntries); cleanEmptyAlbums(existingDirectories); - await analyze(analysisController, newEntries); + await analyze(analysisController, entries: newEntries); } return tempUris; diff --git a/lib/model/source/tag.dart b/lib/model/source/tag.dart index e08913547..8f9922daa 100644 --- a/lib/model/source/tag.dart +++ b/lib/model/source/tag.dart @@ -9,7 +9,7 @@ import 'package:collection/collection.dart'; import 'package:flutter/foundation.dart'; mixin TagMixin on SourceBase { - static const _commitCountThreshold = 400; + static const commitCountThreshold = 400; static const _stopCheckCountThreshold = 100; List sortedTags = List.unmodifiable([]); @@ -41,7 +41,7 @@ mixin TagMixin on SourceBase { await entry.catalog(background: true, persist: true, force: force); if (entry.isCatalogued) { newMetadata.add(entry.catalogMetadata!); - if (newMetadata.length >= _commitCountThreshold) { + if (newMetadata.length >= commitCountThreshold) { await metadataDb.saveMetadata(Set.unmodifiable(newMetadata)); onCatalogMetadataChanged(); newMetadata.clear(); diff --git a/lib/services/analysis_service.dart b/lib/services/analysis_service.dart index dee908146..354068e6f 100644 --- a/lib/services/analysis_service.dart +++ b/lib/services/analysis_service.dart @@ -25,9 +25,12 @@ class AnalysisService { } } - static Future startService() async { + static Future startService({required bool force, List? contentIds}) async { try { - await platform.invokeMethod('startService'); + await platform.invokeMethod('startService', { + 'contentIds': contentIds, + 'force': force, + }); } on PlatformException catch (e, stack) { await reportService.recordError(e, stack); } @@ -48,7 +51,7 @@ Future _init() async { _channel.setMethodCallHandler((call) { switch (call.method) { case 'start': - analyzer.start(); + analyzer.start(call.arguments); return Future.value(true); case 'stop': analyzer.stop(); @@ -69,7 +72,7 @@ enum AnalyzerState { running, stopping, stopped } class Analyzer { late AppLocalizations _l10n; final ValueNotifier _serviceStateNotifier = ValueNotifier(AnalyzerState.stopped); - final AnalysisController _controller = AnalysisController(canStartService: false, stopSignal: ValueNotifier(false)); + AnalysisController? _controller; Timer? _notificationUpdateTimer; final _source = MediaStoreSource(); @@ -94,13 +97,23 @@ class Analyzer { _stopUpdateTimer(); } - Future start() async { + Future start(dynamic args) async { debugPrint('$runtimeType start'); - _serviceStateNotifier.value = AnalyzerState.running; + List? contentIds; + var force = false; + if (args is Map) { + contentIds = (args['contentIds'] as List?)?.cast(); + force = args['force'] ?? false; + } + _controller = AnalysisController( + canStartService: false, + contentIds: contentIds, + force: force, + stopSignal: ValueNotifier(false), + ); _l10n = await AppLocalizations.delegate.load(settings.appliedLocale); - - _controller.stopSignal.value = false; + _serviceStateNotifier.value = AnalyzerState.running; await _source.init(); unawaited(_source.refresh(analysisController: _controller)); @@ -126,7 +139,7 @@ class Analyzer { _serviceStateNotifier.value = AnalyzerState.stopped; break; case AnalyzerState.stopped: - _controller.stopSignal.value = true; + _controller?.stopSignal.value = true; _stopUpdateTimer(); break; } diff --git a/lib/widgets/collection/entry_set_action_delegate.dart b/lib/widgets/collection/entry_set_action_delegate.dart index 8c3974992..06ca44bab 100644 --- a/lib/widgets/collection/entry_set_action_delegate.dart +++ b/lib/widgets/collection/entry_set_action_delegate.dart @@ -76,8 +76,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware final selection = context.read>(); final selectedItems = _getExpandedSelectedItems(selection); - final controller = AnalysisController(canStartService: false, force: true); - source.analyze(controller, selectedItems); + final controller = AnalysisController(canStartService: true, force: true); + source.analyze(controller, entries: selectedItems); selection.browse(); } diff --git a/lib/widgets/debug/app_debug_page.dart b/lib/widgets/debug/app_debug_page.dart index f338cbb6e..fdf0be831 100644 --- a/lib/widgets/debug/app_debug_page.dart +++ b/lib/widgets/debug/app_debug_page.dart @@ -104,9 +104,9 @@ class _AppDebugPageState extends State { }, child: const Text('Source full refresh'), ), - const ElevatedButton( - onPressed: AnalysisService.startService, - child: Text('Start analysis service'), + ElevatedButton( + onPressed: () => AnalysisService.startService(force: false), + child: const Text('Start analysis service'), ), const Divider(), Padding(