rescan with service
This commit is contained in:
parent
85a0c504d6
commit
4d4da21090
10 changed files with 89 additions and 44 deletions
|
@ -157,12 +157,17 @@ class AnalysisService : MethodChannel.MethodCallHandler, Service() {
|
||||||
override fun handleMessage(msg: Message) {
|
override fun handleMessage(msg: Message) {
|
||||||
val context = this@AnalysisService
|
val context = this@AnalysisService
|
||||||
val data = msg.data
|
val data = msg.data
|
||||||
Log.d(LOG_TAG, "handleMessage data=$data")
|
|
||||||
when (data.getString(KEY_COMMAND)) {
|
when (data.getString(KEY_COMMAND)) {
|
||||||
COMMAND_START -> {
|
COMMAND_START -> {
|
||||||
runBlocking {
|
runBlocking {
|
||||||
context.runOnUiThread {
|
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 KEY_COMMAND = "command"
|
||||||
const val COMMAND_START = "start"
|
const val COMMAND_START = "start"
|
||||||
const val COMMAND_STOP = "stop"
|
const val COMMAND_STOP = "stop"
|
||||||
|
const val KEY_CONTENT_IDS = "content_ids"
|
||||||
|
const val KEY_FORCE = "force"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -44,10 +44,21 @@ class AnalysisHandler(private val activity: Activity, private val onAnalysisComp
|
||||||
result.success(true)
|
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)) {
|
if (!activity.isMyServiceRunning(AnalysisService::class.java)) {
|
||||||
val intent = Intent(activity, AnalysisService::class.java)
|
val intent = Intent(activity, AnalysisService::class.java)
|
||||||
intent.putExtra(AnalysisService.KEY_COMMAND, AnalysisService.COMMAND_START)
|
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) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
|
||||||
activity.startForegroundService(intent)
|
activity.startForegroundService(intent)
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -2,10 +2,12 @@ import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
class AnalysisController {
|
class AnalysisController {
|
||||||
final bool canStartService, force;
|
final bool canStartService, force;
|
||||||
|
final List<int>? contentIds;
|
||||||
final ValueNotifier<bool> stopSignal;
|
final ValueNotifier<bool> stopSignal;
|
||||||
|
|
||||||
AnalysisController({
|
AnalysisController({
|
||||||
this.canStartService = true,
|
this.canStartService = true,
|
||||||
|
this.contentIds,
|
||||||
this.force = false,
|
this.force = false,
|
||||||
ValueNotifier<bool>? stopSignal,
|
ValueNotifier<bool>? stopSignal,
|
||||||
}) : stopSignal = stopSignal ?? ValueNotifier(false);
|
}) : stopSignal = stopSignal ?? ValueNotifier(false);
|
||||||
|
|
|
@ -37,8 +37,6 @@ mixin SourceBase {
|
||||||
}
|
}
|
||||||
|
|
||||||
abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagMixin {
|
abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagMixin {
|
||||||
static const _analysisServiceOpCountThreshold = 400;
|
|
||||||
|
|
||||||
final EventBus _eventBus = EventBus();
|
final EventBus _eventBus = EventBus();
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
@ -269,30 +267,39 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
||||||
eventBus.fire(EntryRefreshedEvent({entry}));
|
eventBus.fire(EntryRefreshedEvent({entry}));
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> analyze(AnalysisController? analysisController, Set<AvesEntry> candidateEntries) async {
|
Future<void> analyze(AnalysisController? analysisController, {Set<AvesEntry>? entries}) async {
|
||||||
final todoEntries = visibleEntries;
|
final todoEntries = entries ?? visibleEntries;
|
||||||
final _analysisController = analysisController ?? AnalysisController();
|
final _analysisController = analysisController ?? AnalysisController();
|
||||||
|
final force = _analysisController.force;
|
||||||
if (!_analysisController.isStopping) {
|
if (!_analysisController.isStopping) {
|
||||||
late bool startAnalysisService;
|
var startAnalysisService = false;
|
||||||
if (_analysisController.canStartService && settings.canUseAnalysisService) {
|
if (_analysisController.canStartService && settings.canUseAnalysisService) {
|
||||||
final force = _analysisController.force;
|
// cataloguing
|
||||||
var opCount = 0;
|
if (!startAnalysisService) {
|
||||||
opCount += (force ? todoEntries : todoEntries.where(TagMixin.catalogEntriesTest)).length;
|
final opCount = (force ? todoEntries : todoEntries.where(TagMixin.catalogEntriesTest)).length;
|
||||||
opCount += (force ? todoEntries.where((entry) => entry.hasGps) : todoEntries.where(LocationMixin.locateCountriesTest)).length;
|
if (opCount > TagMixin.commitCountThreshold) {
|
||||||
if (await availability.canLocatePlaces) {
|
startAnalysisService = true;
|
||||||
opCount += (force ? todoEntries.where((entry) => entry.hasGps) : todoEntries.where(LocationMixin.locatePlacesTest)).length;
|
}
|
||||||
|
}
|
||||||
|
// 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) {
|
if (startAnalysisService) {
|
||||||
await AnalysisService.startService();
|
await AnalysisService.startService(
|
||||||
|
force: force,
|
||||||
|
contentIds: entries?.map((entry) => entry.contentId).whereNotNull().toList(),
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
await catalogEntries(_analysisController, candidateEntries);
|
await catalogEntries(_analysisController, todoEntries);
|
||||||
updateDerivedFilters(candidateEntries);
|
updateDerivedFilters(todoEntries);
|
||||||
await locateEntries(_analysisController, candidateEntries);
|
await locateEntries(_analysisController, todoEntries);
|
||||||
updateDerivedFilters(candidateEntries);
|
updateDerivedFilters(todoEntries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
stateNotifier.value = SourceState.ready;
|
stateNotifier.value = SourceState.ready;
|
||||||
|
@ -347,7 +354,7 @@ abstract class CollectionSource with SourceBase, AlbumMixin, LocationMixin, TagM
|
||||||
|
|
||||||
if (visible) {
|
if (visible) {
|
||||||
final candidateEntries = visibleEntries.where((entry) => filters.any((f) => f.test(entry))).toSet();
|
final candidateEntries = visibleEntries.where((entry) => filters.any((f) => f.test(entry))).toSet();
|
||||||
analyze(null, candidateEntries);
|
analyze(null, entries: candidateEntries);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -14,8 +14,8 @@ import 'package:flutter/foundation.dart';
|
||||||
import 'package:tuple/tuple.dart';
|
import 'package:tuple/tuple.dart';
|
||||||
|
|
||||||
mixin LocationMixin on SourceBase {
|
mixin LocationMixin on SourceBase {
|
||||||
static const _commitCountThreshold = 80;
|
static const commitCountThreshold = 200;
|
||||||
static const _stopCheckCountThreshold = 20;
|
static const _stopCheckCountThreshold = 50;
|
||||||
|
|
||||||
List<String> sortedCountries = List.unmodifiable([]);
|
List<String> sortedCountries = List.unmodifiable([]);
|
||||||
List<String> sortedPlaces = List.unmodifiable([]);
|
List<String> sortedPlaces = List.unmodifiable([]);
|
||||||
|
@ -118,7 +118,7 @@ mixin LocationMixin on SourceBase {
|
||||||
}
|
}
|
||||||
if (entry.hasFineAddress) {
|
if (entry.hasFineAddress) {
|
||||||
newAddresses.add(entry.addressDetails!);
|
newAddresses.add(entry.addressDetails!);
|
||||||
if (newAddresses.length >= _commitCountThreshold) {
|
if (newAddresses.length >= commitCountThreshold) {
|
||||||
await metadataDb.saveAddresses(Set.unmodifiable(newAddresses));
|
await metadataDb.saveAddresses(Set.unmodifiable(newAddresses));
|
||||||
onAddressMetadataChanged();
|
onAddressMetadataChanged();
|
||||||
newAddresses.clear();
|
newAddresses.clear();
|
||||||
|
|
|
@ -111,7 +111,12 @@ class MediaStoreSource extends CollectionSource {
|
||||||
updateDirectories();
|
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');
|
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);
|
await metadataDb.saveEntries(newEntries);
|
||||||
cleanEmptyAlbums(existingDirectories);
|
cleanEmptyAlbums(existingDirectories);
|
||||||
|
|
||||||
await analyze(analysisController, newEntries);
|
await analyze(analysisController, entries: newEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
return tempUris;
|
return tempUris;
|
||||||
|
|
|
@ -9,7 +9,7 @@ import 'package:collection/collection.dart';
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
mixin TagMixin on SourceBase {
|
mixin TagMixin on SourceBase {
|
||||||
static const _commitCountThreshold = 400;
|
static const commitCountThreshold = 400;
|
||||||
static const _stopCheckCountThreshold = 100;
|
static const _stopCheckCountThreshold = 100;
|
||||||
|
|
||||||
List<String> sortedTags = List.unmodifiable([]);
|
List<String> sortedTags = List.unmodifiable([]);
|
||||||
|
@ -41,7 +41,7 @@ mixin TagMixin on SourceBase {
|
||||||
await entry.catalog(background: true, persist: true, force: force);
|
await entry.catalog(background: true, persist: true, force: force);
|
||||||
if (entry.isCatalogued) {
|
if (entry.isCatalogued) {
|
||||||
newMetadata.add(entry.catalogMetadata!);
|
newMetadata.add(entry.catalogMetadata!);
|
||||||
if (newMetadata.length >= _commitCountThreshold) {
|
if (newMetadata.length >= commitCountThreshold) {
|
||||||
await metadataDb.saveMetadata(Set.unmodifiable(newMetadata));
|
await metadataDb.saveMetadata(Set.unmodifiable(newMetadata));
|
||||||
onCatalogMetadataChanged();
|
onCatalogMetadataChanged();
|
||||||
newMetadata.clear();
|
newMetadata.clear();
|
||||||
|
|
|
@ -25,9 +25,12 @@ class AnalysisService {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
static Future<void> startService() async {
|
static Future<void> startService({required bool force, List<int>? contentIds}) async {
|
||||||
try {
|
try {
|
||||||
await platform.invokeMethod('startService');
|
await platform.invokeMethod('startService', <String, dynamic>{
|
||||||
|
'contentIds': contentIds,
|
||||||
|
'force': force,
|
||||||
|
});
|
||||||
} on PlatformException catch (e, stack) {
|
} on PlatformException catch (e, stack) {
|
||||||
await reportService.recordError(e, stack);
|
await reportService.recordError(e, stack);
|
||||||
}
|
}
|
||||||
|
@ -48,7 +51,7 @@ Future<void> _init() async {
|
||||||
_channel.setMethodCallHandler((call) {
|
_channel.setMethodCallHandler((call) {
|
||||||
switch (call.method) {
|
switch (call.method) {
|
||||||
case 'start':
|
case 'start':
|
||||||
analyzer.start();
|
analyzer.start(call.arguments);
|
||||||
return Future.value(true);
|
return Future.value(true);
|
||||||
case 'stop':
|
case 'stop':
|
||||||
analyzer.stop();
|
analyzer.stop();
|
||||||
|
@ -69,7 +72,7 @@ enum AnalyzerState { running, stopping, stopped }
|
||||||
class Analyzer {
|
class Analyzer {
|
||||||
late AppLocalizations _l10n;
|
late AppLocalizations _l10n;
|
||||||
final ValueNotifier<AnalyzerState> _serviceStateNotifier = ValueNotifier<AnalyzerState>(AnalyzerState.stopped);
|
final ValueNotifier<AnalyzerState> _serviceStateNotifier = ValueNotifier<AnalyzerState>(AnalyzerState.stopped);
|
||||||
final AnalysisController _controller = AnalysisController(canStartService: false, stopSignal: ValueNotifier(false));
|
AnalysisController? _controller;
|
||||||
Timer? _notificationUpdateTimer;
|
Timer? _notificationUpdateTimer;
|
||||||
final _source = MediaStoreSource();
|
final _source = MediaStoreSource();
|
||||||
|
|
||||||
|
@ -94,13 +97,23 @@ class Analyzer {
|
||||||
_stopUpdateTimer();
|
_stopUpdateTimer();
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<void> start() async {
|
Future<void> start(dynamic args) async {
|
||||||
debugPrint('$runtimeType start');
|
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);
|
_l10n = await AppLocalizations.delegate.load(settings.appliedLocale);
|
||||||
|
_serviceStateNotifier.value = AnalyzerState.running;
|
||||||
_controller.stopSignal.value = false;
|
|
||||||
await _source.init();
|
await _source.init();
|
||||||
unawaited(_source.refresh(analysisController: _controller));
|
unawaited(_source.refresh(analysisController: _controller));
|
||||||
|
|
||||||
|
@ -126,7 +139,7 @@ class Analyzer {
|
||||||
_serviceStateNotifier.value = AnalyzerState.stopped;
|
_serviceStateNotifier.value = AnalyzerState.stopped;
|
||||||
break;
|
break;
|
||||||
case AnalyzerState.stopped:
|
case AnalyzerState.stopped:
|
||||||
_controller.stopSignal.value = true;
|
_controller?.stopSignal.value = true;
|
||||||
_stopUpdateTimer();
|
_stopUpdateTimer();
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
|
@ -76,8 +76,8 @@ class EntrySetActionDelegate with FeedbackMixin, PermissionAwareMixin, SizeAware
|
||||||
final selection = context.read<Selection<AvesEntry>>();
|
final selection = context.read<Selection<AvesEntry>>();
|
||||||
final selectedItems = _getExpandedSelectedItems(selection);
|
final selectedItems = _getExpandedSelectedItems(selection);
|
||||||
|
|
||||||
final controller = AnalysisController(canStartService: false, force: true);
|
final controller = AnalysisController(canStartService: true, force: true);
|
||||||
source.analyze(controller, selectedItems);
|
source.analyze(controller, entries: selectedItems);
|
||||||
|
|
||||||
selection.browse();
|
selection.browse();
|
||||||
}
|
}
|
||||||
|
|
|
@ -104,9 +104,9 @@ class _AppDebugPageState extends State<AppDebugPage> {
|
||||||
},
|
},
|
||||||
child: const Text('Source full refresh'),
|
child: const Text('Source full refresh'),
|
||||||
),
|
),
|
||||||
const ElevatedButton(
|
ElevatedButton(
|
||||||
onPressed: AnalysisService.startService,
|
onPressed: () => AnalysisService.startService(force: false),
|
||||||
child: Text('Start analysis service'),
|
child: const Text('Start analysis service'),
|
||||||
),
|
),
|
||||||
const Divider(),
|
const Divider(),
|
||||||
Padding(
|
Padding(
|
||||||
|
|
Loading…
Reference in a new issue