fix: defer import errors until the copy loop finishes
Errors and skips are collected while the copy loop runs and reported once it ends. The final import state is exhaustive - success, already added, invalid name, or nothing supported found - so every import run reaches a terminal state and the pipeline notification always resolves instead of lingering as an indeterminate progress bar.
This commit is contained in:
@@ -93,6 +93,8 @@ class NotificationHelper(private val context: Context) {
|
|||||||
notificationManager.cancel(NOTIFICATION_ID)
|
notificationManager.cancel(NOTIFICATION_ID)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun hasActiveProgressNotification(): Boolean = lastNotificationKey != 0
|
||||||
|
|
||||||
fun showUnifiedProgressNotification(
|
fun showUnifiedProgressNotification(
|
||||||
title: String,
|
title: String,
|
||||||
contentText: String,
|
contentText: String,
|
||||||
|
|||||||
+4
@@ -97,6 +97,10 @@ class DictionaryForegroundService : Service() {
|
|||||||
contentText = "${snapshot.percent}%",
|
contentText = "${snapshot.percent}%",
|
||||||
progressPercent = snapshot.percent,
|
progressPercent = snapshot.percent,
|
||||||
)
|
)
|
||||||
|
} else if (snapshot == null && !isFinalizing &&
|
||||||
|
notificationHelper.hasActiveProgressNotification()
|
||||||
|
) {
|
||||||
|
notificationHelper.cancelNotification()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,6 +36,8 @@ class DictionaryImportManager(
|
|||||||
val dictionariesDir = File(context.getExternalFilesDir(null), "dictionaries")
|
val dictionariesDir = File(context.getExternalFilesDir(null), "dictionaries")
|
||||||
val totalFiles = uris.size.coerceAtLeast(1)
|
val totalFiles = uris.size.coerceAtLeast(1)
|
||||||
importedFiles.clear()
|
importedFiles.clear()
|
||||||
|
val skippedNames = mutableListOf<String>()
|
||||||
|
val deferredErrors = mutableListOf<String>()
|
||||||
|
|
||||||
if (!dictionariesDir.exists()) {
|
if (!dictionariesDir.exists()) {
|
||||||
dictionariesDir.mkdirs()
|
dictionariesDir.mkdirs()
|
||||||
@@ -46,9 +48,7 @@ class DictionaryImportManager(
|
|||||||
currentCoroutineContext().ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
val fileName = getFileName(uri) ?: continue
|
val fileName = getFileName(uri) ?: continue
|
||||||
if (SafeFileName.validate(fileName) == null) {
|
if (SafeFileName.validate(fileName) == null) {
|
||||||
mutableImportState.value = ImportState.Error(
|
deferredErrors += context.getString(R.string.import_invalid_file_name, fileName)
|
||||||
context.getString(R.string.import_invalid_file_name, fileName)
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
val lowerFileName = fileName.lowercase()
|
val lowerFileName = fileName.lowercase()
|
||||||
@@ -65,9 +65,7 @@ class DictionaryImportManager(
|
|||||||
val destFile = File(dictionariesDir, fileName)
|
val destFile = File(dictionariesDir, fileName)
|
||||||
|
|
||||||
if (destFile.exists()) {
|
if (destFile.exists()) {
|
||||||
mutableImportState.value = ImportState.Error(
|
skippedNames += fileName
|
||||||
context.getString(R.string.import_file_exists, fileName)
|
|
||||||
)
|
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -115,7 +113,17 @@ class DictionaryImportManager(
|
|||||||
|
|
||||||
currentCoroutineContext().ensureActive()
|
currentCoroutineContext().ensureActive()
|
||||||
|
|
||||||
mutableImportState.value = ImportState.Success
|
mutableImportState.value = resolveImportOutcome(
|
||||||
|
importedCount = importedFiles.size,
|
||||||
|
skippedNames = skippedNames,
|
||||||
|
deferredErrors = deferredErrors,
|
||||||
|
).toImportState(
|
||||||
|
existsMessage = { names ->
|
||||||
|
context.getString(R.string.import_file_exists, names.joinToString(separator = ", "))
|
||||||
|
},
|
||||||
|
invalidMessage = { message -> message },
|
||||||
|
nothingImportedMessage = { context.getString(R.string.import_nothing_imported) },
|
||||||
|
)
|
||||||
|
|
||||||
} catch (e: CancellationException) {
|
} catch (e: CancellationException) {
|
||||||
cleanupImportedFiles()
|
cleanupImportedFiles()
|
||||||
|
|||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package com.example.research.feature.import
|
||||||
|
|
||||||
|
import com.example.research.ui.settings.ImportState
|
||||||
|
|
||||||
|
internal sealed interface ImportOutcome {
|
||||||
|
data object Imported : ImportOutcome
|
||||||
|
data class AlreadyExists(val names: List<String>) : ImportOutcome
|
||||||
|
data class InvalidFile(val message: String) : ImportOutcome
|
||||||
|
data object NothingImported : ImportOutcome
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun resolveImportOutcome(
|
||||||
|
importedCount: Int,
|
||||||
|
skippedNames: List<String>,
|
||||||
|
deferredErrors: List<String>,
|
||||||
|
): ImportOutcome = when {
|
||||||
|
importedCount > 0 -> ImportOutcome.Imported
|
||||||
|
skippedNames.isNotEmpty() -> ImportOutcome.AlreadyExists(skippedNames)
|
||||||
|
deferredErrors.isNotEmpty() -> ImportOutcome.InvalidFile(deferredErrors.first())
|
||||||
|
else -> ImportOutcome.NothingImported
|
||||||
|
}
|
||||||
|
|
||||||
|
internal fun ImportOutcome.toImportState(
|
||||||
|
existsMessage: (List<String>) -> String,
|
||||||
|
invalidMessage: (String) -> String,
|
||||||
|
nothingImportedMessage: () -> String,
|
||||||
|
): ImportState = when (this) {
|
||||||
|
ImportOutcome.Imported -> ImportState.Success
|
||||||
|
is ImportOutcome.AlreadyExists -> ImportState.Error(existsMessage(names))
|
||||||
|
is ImportOutcome.InvalidFile -> ImportState.Error(invalidMessage(message))
|
||||||
|
ImportOutcome.NothingImported -> ImportState.Error(nothingImportedMessage())
|
||||||
|
}
|
||||||
@@ -67,8 +67,9 @@
|
|||||||
<string name="dictionary_management_title">Словари</string>
|
<string name="dictionary_management_title">Словари</string>
|
||||||
<string name="import_dictionary_button">Выбрать файлы</string>
|
<string name="import_dictionary_button">Выбрать файлы</string>
|
||||||
<string name="import_error">Не удалось импортировать словарь: %1$s</string>
|
<string name="import_error">Не удалось импортировать словарь: %1$s</string>
|
||||||
<string name="import_file_exists">Файл уже существует: %1$s</string>
|
<string name="import_file_exists">Словарь уже добавлен: %1$s</string>
|
||||||
<string name="import_invalid_file_name">Недопустимое имя файла: %1$s</string>
|
<string name="import_invalid_file_name">Недопустимое имя файла: %1$s</string>
|
||||||
|
<string name="import_nothing_imported">В выбранном нет поддерживаемых файлов словарей</string>
|
||||||
<string name="dictionary_source_url_hint">URL</string>
|
<string name="dictionary_source_url_hint">URL</string>
|
||||||
<string name="dictionary_source_add_button">Добавить источник</string>
|
<string name="dictionary_source_add_button">Добавить источник</string>
|
||||||
<string name="dictionary_source_duplicate">Этот URL уже существует</string>
|
<string name="dictionary_source_duplicate">Этот URL уже существует</string>
|
||||||
|
|||||||
@@ -65,8 +65,9 @@
|
|||||||
<string name="dictionary_management_title">Dictionaries</string>
|
<string name="dictionary_management_title">Dictionaries</string>
|
||||||
<string name="import_dictionary_button">Select files</string>
|
<string name="import_dictionary_button">Select files</string>
|
||||||
<string name="import_error">Failed to import dictionary: %1$s</string>
|
<string name="import_error">Failed to import dictionary: %1$s</string>
|
||||||
<string name="import_file_exists">File already exists: %1$s</string>
|
<string name="import_file_exists">Dictionary already added: %1$s</string>
|
||||||
<string name="import_invalid_file_name">Invalid file name: %1$s</string>
|
<string name="import_invalid_file_name">Invalid file name: %1$s</string>
|
||||||
|
<string name="import_nothing_imported">No supported dictionary files were found in the selection</string>
|
||||||
<string name="dictionary_source_url_hint">URL</string>
|
<string name="dictionary_source_url_hint">URL</string>
|
||||||
<string name="dictionary_source_add_button">Add source</string>
|
<string name="dictionary_source_add_button">Add source</string>
|
||||||
<string name="dictionary_source_duplicate">This URL already exists</string>
|
<string name="dictionary_source_duplicate">This URL already exists</string>
|
||||||
|
|||||||
Reference in New Issue
Block a user