Avoid concurrency issues when reordering categories

Maybe fixes #8372
This commit is contained in:
arkon
2022-11-22 23:12:23 -05:00
parent 5c37347cec
commit e2179a6669
4 changed files with 53 additions and 37 deletions

View File

@@ -5,46 +5,66 @@ import eu.kanade.domain.category.model.CategoryUpdate
import eu.kanade.domain.category.repository.CategoryRepository
import eu.kanade.tachiyomi.util.lang.withNonCancellableContext
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import logcat.LogPriority
import java.util.Collections
class ReorderCategory(
private val categoryRepository: CategoryRepository,
) {
suspend fun await(categoryId: Long, newPosition: Int) = withNonCancellableContext {
val categories = categoryRepository.getAll().filterNot(Category::isSystemCategory)
private val mutex = Mutex()
val currentIndex = categories.indexOfFirst { it.id == categoryId }
if (currentIndex == newPosition) {
return@withNonCancellableContext Result.Unchanged
}
suspend fun moveUp(category: Category): Result =
await(category, MoveTo.UP)
val reorderedCategories = categories.toMutableList()
val reorderedCategory = reorderedCategories.removeAt(currentIndex)
reorderedCategories.add(newPosition, reorderedCategory)
suspend fun moveDown(category: Category): Result =
await(category, MoveTo.DOWN)
val updates = reorderedCategories.mapIndexed { index, category ->
CategoryUpdate(
id = category.id,
order = index.toLong(),
)
}
private suspend fun await(category: Category, moveTo: MoveTo) = withNonCancellableContext {
mutex.withLock {
val categories = categoryRepository.getAll()
.filterNot(Category::isSystemCategory)
.toMutableList()
try {
categoryRepository.updatePartial(updates)
Result.Success
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
Result.InternalError(e)
val newPosition = when (moveTo) {
MoveTo.UP -> category.order - 1
MoveTo.DOWN -> category.order + 1
}.toInt()
val currentIndex = categories.indexOfFirst { it.id == category.id }
if (currentIndex == newPosition) {
return@withNonCancellableContext Result.Unchanged
}
Collections.swap(categories, currentIndex, newPosition)
val updates = categories.mapIndexed { index, category ->
CategoryUpdate(
id = category.id,
order = index.toLong(),
)
}
try {
categoryRepository.updatePartial(updates)
Result.Success
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
Result.InternalError(e)
}
}
}
suspend fun await(category: Category, newPosition: Long): Result =
await(category.id, newPosition.toInt())
sealed class Result {
object Success : Result()
object Unchanged : Result()
data class InternalError(val error: Throwable) : Result()
}
private enum class MoveTo {
UP,
DOWN,
}
}