refactor: slim IndexingProgress to a producer-supplied [0,1] progress

perFileProgress was never populated and its progress branch was
unreachable; currentFile, currentIndex, currentFileProgress and label
were write-only (no consumer read them); progressPercent had no
callers; the -1f aggregate sentinel is gone along with the fallback
branches that only fired when the tracker was absent.
This commit is contained in:
2026-09-01 13:36:23 +08:00
parent bd7f597986
commit 9fcd7914dc
2 changed files with 16 additions and 64 deletions
@@ -1,42 +1,13 @@
package com.example.research.core.domain.model
import kotlin.math.roundToInt
/**
* Snapshot of an ongoing indexing operation.
*
* [progress] is the authoritative [0f, 1f] completion value and is O(1) to
* compute: the repository's internal `ProgressTracker` maintains a running
* sum across files incrementally and stores it in [aggregateSum], avoiding
* an O(n) walk over [perFileProgress] on every read.
* [perFileProgress] is kept for diagnostics; callers should prefer
* [progress] / [progressPercent].
* [progress] is the authoritative [0f, 1f] completion value: the repository
* aggregates a file-size-weighted sum across files incrementally while
* indexing, so reading it is O(1).
*/
data class IndexingProgress(
val currentFile: String = "",
val currentIndex: Int = 0,
val totalFiles: Int = 0,
val isIndexing: Boolean = false,
val currentFileProgress: Float = 0f,
val label: String = "",
val perFileProgress: Map<String, Float> = emptyMap(),
/** Pre-aggregated sum in [0f, totalFiles] supplied by the producer; -1f = unknown. */
val aggregateSum: Float = -1f,
) {
val progress: Float
get() = if (totalFiles > 0) {
when {
aggregateSum >= 0f -> (aggregateSum / totalFiles).coerceIn(0f, 1f)
perFileProgress.isNotEmpty() -> {
val totalProgress = perFileProgress.values.sum()
(totalProgress / totalFiles).coerceIn(0f, 1f)
}
else -> {
val completedFiles = (currentIndex - 1).coerceAtLeast(0)
((completedFiles + currentFileProgress) / totalFiles).coerceIn(0f, 1f)
}
}
} else 0f
val progressPercent: Int
get() = (progress * 100).roundToInt().coerceIn(0, 100)
}
val progress: Float = 0f,
)
@@ -70,13 +70,9 @@ private class ProgressTracker(files: List<LocalDictionaryRepository.DiscoveredFi
aggregateWeightedMicros.addAndGet(newMicros - prevMicros)
}
/**
* Current weighted sum mapped back to [0f, totalFiles] so the existing
* IndexingProgress model can keep deriving progress as aggregate/total.
*/
fun aggregateSum(): Float {
fun progress(): Float {
val weightedProgress = aggregateWeightedMicros.get().toDouble() / (totalWeight.toDouble() * 1_000_000.0)
return (weightedProgress.coerceIn(0.0, 1.0) * totalFiles).toFloat()
return weightedProgress.coerceIn(0.0, 1.0).toFloat()
}
fun reset() {
@@ -194,10 +190,7 @@ class LocalDictionaryRepository(
mutableIndexingProgress.emit(
IndexingProgress(
isIndexing = true,
currentFile = context.getString(R.string.indexing_label),
totalFiles = filesToScan.size,
currentIndex = filesToScan.size - filesToIndex.size,
aggregateSum = progressTracker?.aggregateSum() ?: -1f,
progress = progressTracker?.progress() ?: 0f,
)
)
@@ -205,44 +198,32 @@ class LocalDictionaryRepository(
val lastProgressEmitMs = AtomicLong(0L)
supervisorScope {
filesToIndex.mapIndexed { index, file ->
filesToIndex.map { file ->
async {
indexingSemaphore.withPermit {
coroutineContext.ensureActive()
yield()
val res = indexDictionary(file) { _, op, prog ->
val res = indexDictionary(file) { _, _, prog ->
if (prog >= 0f) {
progressTracker?.updateFileProgress(file.name, prog)
}
val tracker = progressTracker
val aggregate = tracker?.aggregateSum() ?: -1f
val newPercent = if (filesToScan.isNotEmpty() && aggregate >= 0f) {
((aggregate / filesToScan.size) * 100f).toInt().coerceIn(0, 100)
} else -1
val progress = progressTracker?.progress() ?: 0f
val newPercent = (progress * 100f).toInt().coerceIn(0, 100)
val currentIdx = filesToScan.size - filesToIndex.size + index + 1
val previous = mutableIndexingProgress.value
val now = SystemClock.elapsedRealtime()
val percentChanged = newPercent >= 0 && newPercent != lastEmittedPercent.get()
val fileChanged = previous.currentFile != file.name
val percentChanged = newPercent != lastEmittedPercent.get()
val completed = newPercent >= 100 || prog >= 1f
val intervalElapsed =
now - lastProgressEmitMs.get() >= MIN_PROGRESS_EMIT_INTERVAL_MS
val shouldEmit = fileChanged || completed || (percentChanged && intervalElapsed)
val shouldEmit = completed || (percentChanged && intervalElapsed)
if (shouldEmit) {
if (newPercent >= 0) lastEmittedPercent.set(newPercent)
lastEmittedPercent.set(newPercent)
lastProgressEmitMs.set(now)
mutableIndexingProgress.update { p ->
p.copy(
currentFile = file.name,
currentIndex = currentIdx,
currentFileProgress = prog,
label = op.ifEmpty { p.label },
aggregateSum = aggregate,
)
p.copy(progress = progress)
}
}
}