diff --git a/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisWorker.kt b/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisWorker.kt index ec8ffb8ef..16e47dd16 100644 --- a/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisWorker.kt +++ b/android/app/src/main/kotlin/deckers/thibault/aves/AnalysisWorker.kt @@ -63,6 +63,8 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine "start", hashMapOf( "entryIds" to inputData.getIntArray(KEY_ENTRY_IDS)?.toList(), "force" to inputData.getBoolean(KEY_FORCE, false), + "progressTotal" to inputData.getInt(KEY_PROGRESS_TOTAL, 0), + "progressOffset" to inputData.getInt(KEY_PROGRESS_OFFSET, 0), ) ) } @@ -169,5 +171,7 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine const val KEY_ENTRY_IDS = "entry_ids" const val KEY_FORCE = "force" + const val KEY_PROGRESS_TOTAL = "progress_total" + const val KEY_PROGRESS_OFFSET = "progress_offset" } } 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 ade45f3c5..c640d31c7 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 @@ -3,6 +3,7 @@ package deckers.thibault.aves.channel.calls import android.content.Context import androidx.core.app.ComponentActivity import androidx.work.ExistingWorkPolicy +import androidx.work.OneTimeWorkRequest import androidx.work.OneTimeWorkRequestBuilder import androidx.work.WorkInfo import androidx.work.WorkManager @@ -51,20 +52,35 @@ class AnalysisHandler(private val activity: ComponentActivity, private val onAna } // can be null or empty - val entryIds = call.argument>("entryIds") + val allEntryIds = call.argument>("entryIds") + val progressTotal = allEntryIds?.size ?: 0 + var progressOffset = 0 - WorkManager.getInstance(activity).enqueueUniqueWork( + // work `Data` cannot occupy more than 10240 bytes when serialized + // so we split it when we have a long list of entry IDs + val chunked = allEntryIds?.chunked(WORK_DATA_CHUNK_SIZE) ?: listOf(null) + + fun buildRequest(entryIds: List?, progressOffset: Int): OneTimeWorkRequest { + val workData = workDataOf( + AnalysisWorker.KEY_ENTRY_IDS to entryIds?.toIntArray(), + AnalysisWorker.KEY_FORCE to force, + AnalysisWorker.KEY_PROGRESS_TOTAL to progressTotal, + AnalysisWorker.KEY_PROGRESS_OFFSET to progressOffset, + ) + return OneTimeWorkRequestBuilder().apply { setInputData(workData) }.build() + } + + var work = WorkManager.getInstance(activity).beginUniqueWork( ANALYSIS_WORK_NAME, ExistingWorkPolicy.KEEP, - OneTimeWorkRequestBuilder().apply { - setInputData( - workDataOf( - AnalysisWorker.KEY_ENTRY_IDS to entryIds?.toIntArray(), - AnalysisWorker.KEY_FORCE to force, - ) - ) - }.build() + buildRequest(chunked.first(), progressOffset), ) + chunked.drop(1).forEach { entryIds -> + progressOffset += WORK_DATA_CHUNK_SIZE + work = work.then(buildRequest(entryIds, progressOffset)) + } + work.enqueue() + attachToActivity() result.success(null) } @@ -89,5 +105,6 @@ class AnalysisHandler(private val activity: ComponentActivity, private val onAna companion object { const val CHANNEL = "deckers.thibault/aves/analysis" private const val ANALYSIS_WORK_NAME = "analysis_work" + private const val WORK_DATA_CHUNK_SIZE = 1000 } } diff --git a/lib/model/source/analysis_controller.dart b/lib/model/source/analysis_controller.dart index c4e1e9e41..f6996674c 100644 --- a/lib/model/source/analysis_controller.dart +++ b/lib/model/source/analysis_controller.dart @@ -2,6 +2,7 @@ import 'package:flutter/foundation.dart'; class AnalysisController { final bool canStartService, force; + final int progressTotal, progressOffset; final List? entryIds; final ValueNotifier stopSignal; @@ -9,6 +10,8 @@ class AnalysisController { this.canStartService = true, this.entryIds, this.force = false, + this.progressTotal = 0, + this.progressOffset = 0, ValueNotifier? stopSignal, }) : stopSignal = stopSignal ?? ValueNotifier(false); diff --git a/lib/model/source/tag.dart b/lib/model/source/tag.dart index bd8248b07..537ee0c53 100644 --- a/lib/model/source/tag.dart +++ b/lib/model/source/tag.dart @@ -34,8 +34,11 @@ mixin TagMixin on SourceBase { if (todo.isEmpty) return; state = SourceState.cataloguing; - var progressDone = 0; - final progressTotal = todo.length; + var progressDone = controller.progressOffset; + var progressTotal = controller.progressTotal; + if (progressTotal == 0) { + progressTotal = todo.length; + } setProgress(done: progressDone, total: progressTotal); var stopCheckCount = 0; diff --git a/lib/services/analysis_service.dart b/lib/services/analysis_service.dart index 9c656db62..071561f35 100644 --- a/lib/services/analysis_service.dart +++ b/lib/services/analysis_service.dart @@ -108,15 +108,20 @@ class Analyzer { Future start(dynamic args) async { List? entryIds; var force = false; + var progressTotal = 0, progressOffset = 0; if (args is Map) { entryIds = (args['entryIds'] as List?)?.cast(); force = args['force'] ?? false; + progressTotal = args['progressTotal']; + progressOffset = args['progressOffset']; } - debugPrint('$runtimeType start for ${entryIds?.length ?? 'all'} entries'); + debugPrint('$runtimeType start for ${entryIds?.length ?? 'all'} entries, at $progressOffset/$progressTotal'); _controller = AnalysisController( canStartService: false, entryIds: entryIds, force: force, + progressTotal: progressTotal, + progressOffset: progressOffset, stopSignal: ValueNotifier(false), );