rescan with service

This commit is contained in:
Thibault Deckers 2021-10-18 11:46:42 +09:00
parent 85a0c504d6
commit 4d4da21090
10 changed files with 89 additions and 44 deletions

View file

@ -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"
}
}

View file

@ -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<Boolean>("force")
if (force == null) {
result.error("startAnalysis-args", "failed because of missing arguments", null)
return
}
// can be null or empty
val contentIds = call.argument<List<Int>>("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 {

View file

@ -2,10 +2,12 @@ import 'package:flutter/foundation.dart';
class AnalysisController {
final bool canStartService, force;
final List<int>? contentIds;
final ValueNotifier<bool> stopSignal;
AnalysisController({
this.canStartService = true,
this.contentIds,
this.force = false,
ValueNotifier<bool>? stopSignal,
}) : stopSignal = stopSignal ?? ValueNotifier(false);

View file

@ -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<void> analyze(AnalysisController? analysisController, Set<AvesEntry> candidateEntries) async {
final todoEntries = visibleEntries;
Future<void> analyze(AnalysisController? analysisController, {Set<AvesEntry>? entries}) async {
final todoEntries = entries ?? visibleEntries;
final _analysisController = analysisController ?? AnalysisController();
if (!_analysisController.isStopping) {
late bool startAnalysisService;
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;
if (!_analysisController.isStopping) {
var startAnalysisService = false;
if (_analysisController.canStartService && settings.canUseAnalysisService) {
// 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);
}
}
}

View file

@ -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<String> sortedCountries = List.unmodifiable([]);
List<String> 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();

View file

@ -111,7 +111,12 @@ class MediaStoreSource extends CollectionSource {
updateDirectories();
}
await analyze(analysisController, visibleEntries);
Set<AvesEntry>? 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;

View file

@ -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<String> 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();

View file

@ -25,9 +25,12 @@ class AnalysisService {
}
}
static Future<void> startService() async {
static Future<void> startService({required bool force, List<int>? contentIds}) async {
try {
await platform.invokeMethod('startService');
await platform.invokeMethod('startService', <String, dynamic>{
'contentIds': contentIds,
'force': force,
});
} on PlatformException catch (e, stack) {
await reportService.recordError(e, stack);
}
@ -48,7 +51,7 @@ Future<void> _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<AnalyzerState> _serviceStateNotifier = ValueNotifier<AnalyzerState>(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<void> start() async {
Future<void> start(dynamic args) async {
debugPrint('$runtimeType start');
_serviceStateNotifier.value = AnalyzerState.running;
List<int>? contentIds;
var force = false;
if (args is Map) {
contentIds = (args['contentIds'] as List?)?.cast<int>();
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;
}

View file

@ -76,8 +76,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
final selection = context.read<Selection<AvesEntry>>();
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();
}

View file

@ -104,9 +104,9 @@ class _AppDebugPageState extends State<AppDebugPage> {
},
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(