analysis: do not chain workers, use prefs for data read/write instead

This commit is contained in:
Thibault Deckers 2024-07-16 20:23:46 +02:00
parent fbd498bee8
commit a38c5b72ee
4 changed files with 23 additions and 40 deletions

View file

@ -70,7 +70,7 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
private fun onStart() { private fun onStart() {
Log.i(LOG_TAG, "Start analysis worker $id") Log.i(LOG_TAG, "Start analysis worker $id")
runBlocking { runBlocking {
FlutterUtils.initFlutterEngine(applicationContext, SHARED_PREFERENCES_KEY, CALLBACK_HANDLE_KEY) { FlutterUtils.initFlutterEngine(applicationContext, SHARED_PREFERENCES_KEY, PREF_CALLBACK_HANDLE_KEY) {
flutterEngine = it flutterEngine = it
} }
} }
@ -78,14 +78,15 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
try { try {
initChannels(applicationContext) initChannels(applicationContext)
val preferences = applicationContext.getSharedPreferences(SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
val entryIdStrings = preferences.getStringSet(PREF_ENTRY_IDS_KEY, null)
runBlocking { runBlocking {
FlutterUtils.runOnUiThread { FlutterUtils.runOnUiThread {
backgroundChannel?.invokeMethod( backgroundChannel?.invokeMethod(
"start", hashMapOf( "start", hashMapOf(
"entryIds" to inputData.getIntArray(KEY_ENTRY_IDS)?.toList(), "entryIds" to entryIdStrings?.map { Integer.parseUnsignedInt(it) }?.toList(),
"force" to inputData.getBoolean(KEY_FORCE, false), "force" to inputData.getBoolean(KEY_FORCE, false),
"progressTotal" to inputData.getInt(KEY_PROGRESS_TOTAL, 0),
"progressOffset" to inputData.getInt(KEY_PROGRESS_OFFSET, 0),
) )
) )
} }
@ -194,14 +195,12 @@ class AnalysisWorker(context: Context, parameters: WorkerParameters) : Coroutine
private val LOG_TAG = LogUtils.createTag<AnalysisWorker>() private val LOG_TAG = LogUtils.createTag<AnalysisWorker>()
private const val BACKGROUND_CHANNEL = "deckers.thibault/aves/analysis_service_background" private const val BACKGROUND_CHANNEL = "deckers.thibault/aves/analysis_service_background"
const val SHARED_PREFERENCES_KEY = "analysis_service" const val SHARED_PREFERENCES_KEY = "analysis_service"
const val CALLBACK_HANDLE_KEY = "callback_handle" const val PREF_CALLBACK_HANDLE_KEY = "callback_handle"
const val PREF_ENTRY_IDS_KEY = "entry_ids"
const val NOTIFICATION_CHANNEL = "analysis" const val NOTIFICATION_CHANNEL = "analysis"
const val NOTIFICATION_ID = 1 const val NOTIFICATION_ID = 1
const val KEY_ENTRY_IDS = "entry_ids"
const val KEY_FORCE = "force" const val KEY_FORCE = "force"
const val KEY_PROGRESS_TOTAL = "progress_total"
const val KEY_PROGRESS_OFFSET = "progress_offset"
} }
} }

View file

@ -2,7 +2,6 @@ package deckers.thibault.aves.channel.calls
import android.content.Context import android.content.Context
import androidx.work.ExistingWorkPolicy import androidx.work.ExistingWorkPolicy
import androidx.work.OneTimeWorkRequest
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkInfo import androidx.work.WorkInfo
import androidx.work.WorkManager import androidx.work.WorkManager
@ -39,7 +38,7 @@ class AnalysisHandler(private val activity: FlutterActivity, private val onAnaly
val preferences = activity.getSharedPreferences(AnalysisWorker.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE) val preferences = activity.getSharedPreferences(AnalysisWorker.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
with(preferences.edit()) { with(preferences.edit()) {
putLong(AnalysisWorker.CALLBACK_HANDLE_KEY, callbackHandle) putLong(AnalysisWorker.PREF_CALLBACK_HANDLE_KEY, callbackHandle)
apply() apply()
} }
result.success(true) result.success(true)
@ -54,33 +53,24 @@ class AnalysisHandler(private val activity: FlutterActivity, private val onAnaly
// can be null or empty // can be null or empty
val allEntryIds = call.argument<List<Int>>("entryIds") val allEntryIds = call.argument<List<Int>>("entryIds")
val progressTotal = allEntryIds?.size ?: 0
var progressOffset = 0
// work `Data` cannot occupy more than 10240 bytes when serialized // work `Data` cannot occupy more than 10240 bytes when serialized
// so we split it when we have a long list of entry IDs // so we save the possibly long list of entry IDs to shared preferences
val chunked = allEntryIds?.chunked(WORK_DATA_CHUNK_SIZE) ?: listOf(null) val preferences = activity.getSharedPreferences(AnalysisWorker.SHARED_PREFERENCES_KEY, Context.MODE_PRIVATE)
with(preferences.edit()) {
fun buildRequest(entryIds: List<Int>?, progressOffset: Int): OneTimeWorkRequest { putStringSet(AnalysisWorker.PREF_ENTRY_IDS_KEY, allEntryIds?.map { it.toString() }?.toSet())
val workData = workDataOf( apply()
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<AnalysisWorker>().apply { setInputData(workData) }.build()
} }
var work = WorkManager.getInstance(activity).beginUniqueWork( val workData = workDataOf(
AnalysisWorker.KEY_FORCE to force,
)
WorkManager.getInstance(activity).beginUniqueWork(
ANALYSIS_WORK_NAME, ANALYSIS_WORK_NAME,
ExistingWorkPolicy.KEEP, ExistingWorkPolicy.KEEP,
buildRequest(chunked.first(), progressOffset), OneTimeWorkRequestBuilder<AnalysisWorker>().apply { setInputData(workData) }.build(),
) ).enqueue()
chunked.drop(1).forEach { entryIds ->
progressOffset += WORK_DATA_CHUNK_SIZE
work = work.then(buildRequest(entryIds, progressOffset))
}
work.enqueue()
attachToActivity() attachToActivity()
result.success(null) result.success(null)
@ -106,6 +96,5 @@ class AnalysisHandler(private val activity: FlutterActivity, private val onAnaly
companion object { companion object {
const val CHANNEL = "deckers.thibault/aves/analysis" const val CHANNEL = "deckers.thibault/aves/analysis"
private const val ANALYSIS_WORK_NAME = "analysis_work" private const val ANALYSIS_WORK_NAME = "analysis_work"
private const val WORK_DATA_CHUNK_SIZE = 1000
} }
} }

View file

@ -127,21 +127,16 @@ class Analyzer with WidgetsBindingObserver {
Future<void> start(dynamic args) async { Future<void> start(dynamic args) async {
List<int>? entryIds; List<int>? entryIds;
var force = false; var force = false;
var progressTotal = 0, progressOffset = 0;
if (args is Map) { if (args is Map) {
entryIds = (args['entryIds'] as List?)?.cast<int>(); entryIds = (args['entryIds'] as List?)?.cast<int>();
force = args['force'] ?? false; force = args['force'] ?? false;
progressTotal = args['progressTotal'];
progressOffset = args['progressOffset'];
} }
await reportService.log('Analyzer start for ${entryIds?.length ?? 'all'} entries, at $progressOffset/$progressTotal'); await reportService.log('Analyzer start for ${entryIds?.length ?? 'all'} entries');
_controller?.dispose(); _controller?.dispose();
_controller = AnalysisController( _controller = AnalysisController(
canStartService: false, canStartService: false,
entryIds: entryIds, entryIds: entryIds,
force: force, force: force,
progressTotal: progressTotal,
progressOffset: progressOffset,
); );
settings.systemLocalesFallback = await deviceService.getLocales(); settings.systemLocalesFallback = await deviceService.getLocales();

View file

@ -106,8 +106,8 @@ class AndroidFileUtils {
if (isScreenshotsPath(dirPath)) return AlbumType.screenshots; if (isScreenshotsPath(dirPath)) return AlbumType.screenshots;
if (isVideoCapturesPath(dirPath)) return AlbumType.videoCaptures; if (isVideoCapturesPath(dirPath)) return AlbumType.videoCaptures;
final dir = pContext.split(dirPath).last; final dir = pContext.split(dirPath).lastOrNull;
if (dirPath.startsWith(primaryStorage) && appInventory.isPotentialAppDir(dir)) return AlbumType.app; if (dir != null && dirPath.startsWith(primaryStorage) && appInventory.isPotentialAppDir(dir)) return AlbumType.app;
return AlbumType.regular; return AlbumType.regular;
} }