diff --git a/app/src/main/java/com/example/research/ui/settings/SettingsViewModel.kt b/app/src/main/java/com/example/research/ui/settings/SettingsViewModel.kt index b80d3dd..eccf8f0 100644 --- a/app/src/main/java/com/example/research/ui/settings/SettingsViewModel.kt +++ b/app/src/main/java/com/example/research/ui/settings/SettingsViewModel.kt @@ -37,6 +37,16 @@ import kotlinx.coroutines.launch import kotlinx.coroutines.withContext import java.util.concurrent.ConcurrentHashMap +private const val STATUS_UP_TO_DATE = "UpToDate" +private const val STATUS_NEEDS_UPDATE = "NeedsUpdate" +private const val STATUS_EMPTY = "Empty" + +private data class PersistedDictionaryStatus( + val name: String, + val date: String, + val sourceIds: Set, +) + private data class DictionaryStateInputs( val theme: AppTheme, val dictionarySources: List, @@ -115,14 +125,18 @@ class SettingsViewModel( private fun persistDictionaryStatus() { viewModelScope.launch { dictionaryStatus.collect { status -> - val persistedName = when (status) { - DictionaryStatus.UpToDate -> "UpToDate" - is DictionaryStatus.NeedsUpdate -> "NeedsUpdate" - DictionaryStatus.Empty -> "Empty" + val persisted = when (status) { + DictionaryStatus.UpToDate -> + "$STATUS_UP_TO_DATE|${DateUtils.getCurrentDateString()}" + is DictionaryStatus.NeedsUpdate -> + "$STATUS_NEEDS_UPDATE|${DateUtils.getCurrentDateString()}|" + + status.sourceIds.joinToString(",") + DictionaryStatus.Empty -> + "$STATUS_EMPTY|${DateUtils.getCurrentDateString()}" else -> null } - if (persistedName != null) { - preferencesManager.saveDictionaryStatus("$persistedName|${DateUtils.getCurrentDateString()}") + if (persisted != null) { + preferencesManager.saveDictionaryStatus(persisted) } } } @@ -283,7 +297,7 @@ class SettingsViewModel( scanCompleted = if (localDictionaryRepository.dictionaries.value.isEmpty()) { scanDirectoryInternal(preferencesManager.dictionaryPath) is OperationResult.Success } else { - dictionaryStatus.value = DictionaryStatus.UpToDate + restoreOrEvaluateDictionaryStatus() false } } catch (e: Exception) { @@ -551,6 +565,71 @@ class SettingsViewModel( ): List = DictionarySourceFileMatcher.installedSources(sources, dictionaries) + private suspend fun restoreOrEvaluateDictionaryStatus() { + val restored = restorePersistedDictionaryStatus() + if (restored != null) { + dictionaryStatus.value = restored + return + } + dictionaryStatus.value = DictionaryStatus.Checking + dictionaryStatus.value = withContext(Dispatchers.IO) { evaluateDictionaryStatus() } + } + + private suspend fun restorePersistedDictionaryStatus(): DictionaryStatus? { + val persisted = readPersistedDictionaryStatus() ?: return null + if (persisted.date != DateUtils.getCurrentDateString()) return null + return when (persisted.name) { + STATUS_UP_TO_DATE -> + if (withContext(Dispatchers.IO) { installedDictionariesAreCurrent() }) { + DictionaryStatus.UpToDate + } else { + null + } + STATUS_NEEDS_UPDATE -> { + val updatableIds = withContext(Dispatchers.IO) { sourcesMissingTodaysFile() } + val restorableIds = persisted.sourceIds intersect updatableIds + if (restorableIds.isNotEmpty()) DictionaryStatus.NeedsUpdate(restorableIds) else null + } + else -> null + } + } + + private suspend fun readPersistedDictionaryStatus(): PersistedDictionaryStatus? { + val raw = preferencesManager.dictionaryStatus() ?: return null + val parts = raw.split('|') + if (parts.isEmpty() || parts.first().isEmpty()) return null + return PersistedDictionaryStatus( + name = parts.first(), + date = parts.getOrElse(1) { "" }, + sourceIds = parts.getOrElse(2) { "" } + .split(',') + .map(String::trim) + .filter(String::isNotEmpty) + .toSet(), + ) + } + + private suspend fun sourcesMissingTodaysFile(): Set { + val sources = preferencesManager.dictionarySources.first() + val dictionaries = localDictionaryRepository.dictionaries.first() + val datedEnabledSources = installedEnabledSources(sources, dictionaries) + .filter { DictionarySource.hasDatePlaceholder(it.urlTemplate) } + if (datedEnabledSources.isEmpty()) return emptySet() + val fileNames = localDictionaryRepository.listDictionaryPayloadFileNames( + preferencesManager.dictionaryPath + ) ?: return emptySet() + val today = DateUtils.getCurrentDateString() + return datedEnabledSources + .filterNot { source -> fileNames.any { it.startsWith(fileBaseNameFor(source, today)) } } + .mapTo(mutableSetOf(), DictionarySource::id) + } + + private fun fileBaseNameFor(source: DictionarySource, date: String): String = + DictionarySource.extractFileName(source.urlTemplate, date) + .removeSuffix(".gz") + .removeSuffix(".dsl") + .removeSuffix(".dz") + private suspend fun installedDictionariesAreCurrent(): Boolean { val sources = preferencesManager.dictionarySources.first() val dictionaries = localDictionaryRepository.dictionaries.first() @@ -562,11 +641,7 @@ class SettingsViewModel( ) ?: return true val today = DateUtils.getCurrentDateString() return datedEnabledSources.all { source -> - val baseName = DictionarySource.extractFileName(source.urlTemplate, today) - .removeSuffix(".gz") - .removeSuffix(".dsl") - .removeSuffix(".dz") - fileNames.any { it.startsWith(baseName) } + fileNames.any { it.startsWith(fileBaseNameFor(source, today)) } } } @@ -622,20 +697,7 @@ class SettingsViewModel( dictionaryStatus.value = DictionaryStatus.UpToDate isDownloadInProgress = false } else { - val savedStatus = preferencesManager.dictionaryStatus() - val savedStatusName = savedStatus?.substringBefore('|') - val savedStatusDate = savedStatus?.substringAfter('|', "") - val restoredStatus = if (savedStatusName == "UpToDate") DictionaryStatus.UpToDate else null - if (restoredStatus != null && - savedStatusDate == DateUtils.getCurrentDateString() && - withContext(Dispatchers.IO) { installedDictionariesAreCurrent() } - ) { - dictionaryStatus.value = restoredStatus - } else { - dictionaryStatus.value = DictionaryStatus.Checking - val status = withContext(Dispatchers.IO) { evaluateDictionaryStatus() } - dictionaryStatus.value = status - } + restoreOrEvaluateDictionaryStatus() } if (result.data > 0) {