refactor(db): Migrate some more category queries to SQLDelight

This commit is contained in:
Ahmad Ansori Palembani 2024-11-29 11:28:38 +07:00
parent 9c40aadca2
commit ca41e02fe1
Signed by: null2264
GPG key ID: BA64F8B60AF3EFB6
12 changed files with 187 additions and 21 deletions

View file

@ -3,7 +3,6 @@ package eu.kanade.tachiyomi.data.backup.restore
import android.content.Context
import android.net.Uri
import eu.kanade.tachiyomi.data.backup.BackupNotifier
import eu.kanade.tachiyomi.data.backup.models.BackupSource
import eu.kanade.tachiyomi.data.backup.restore.restorers.CategoriesBackupRestorer
import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaBackupRestorer
import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceBackupRestorer

View file

@ -1,20 +1,20 @@
package eu.kanade.tachiyomi.data.backup.restore.restorers
import eu.kanade.tachiyomi.data.backup.models.BackupCategory
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import yokai.data.DatabaseHandler
import yokai.domain.category.interactor.GetCategories
class CategoriesBackupRestorer(
private val db: DatabaseHelper = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val handler: DatabaseHandler = Injekt.get(),
) {
suspend fun restoreCategories(backupCategories: List<BackupCategory>, onComplete: () -> Unit) {
// Get categories from file and from db
// Do it outside of transaction because StorIO might hang because we're using SQLDelight
val dbCategories = getCategories.await()
db.inTransaction {
handler.await(true) {
// Iterate over them
backupCategories.map { it.getCategoryImpl() }.forEach { category ->
// Used to know if the category is already in the db
@ -33,8 +33,13 @@ class CategoriesBackupRestorer(
if (!found) {
// Let the db assign the id
category.id = null
val result = db.insertCategory(category).executeAsBlocking()
category.id = result.insertedId()?.toInt()
categoriesQueries.insert(
name = category.name,
mangaOrder = category.mangaOrderToString(),
sort = category.order.toLong(),
flags = category.flags.toLong(),
)
category.id = categoriesQueries.selectLastInsertedRowId().executeAsOneOrNull()?.toInt()
}
}
}

View file

@ -53,6 +53,9 @@ interface Category : Serializable {
mangaSort = (LibrarySort.valueOf(sort) ?: LibrarySort.Title).categoryValue
}
fun mangaOrderToString(): String =
if (mangaSort != null) mangaSort.toString() else mangaOrder.joinToString("/")
companion object {
var lastCategoriesAddedTo = emptySet<Int>()

View file

@ -1,19 +1,19 @@
package eu.kanade.tachiyomi.ui.category
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.ui.library.LibrarySort
import eu.kanade.tachiyomi.util.system.executeOnIO
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import yokai.domain.category.interactor.DeleteCategories
import yokai.domain.category.interactor.GetCategories
import yokai.domain.category.interactor.InsertCategories
import yokai.domain.category.interactor.UpdateCategories
import yokai.domain.category.models.CategoryUpdate
import yokai.i18n.MR
import yokai.util.lang.getString
@ -22,9 +22,11 @@ import yokai.util.lang.getString
*/
class CategoryPresenter(
private val controller: CategoryController,
private val db: DatabaseHelper = Injekt.get(),
) {
private val deleteCategories: DeleteCategories by injectLazy()
private val getCategories: GetCategories by injectLazy()
private val insertCategories: InsertCategories by injectLazy()
private val updateCategories: UpdateCategories by injectLazy()
private var scope = CoroutineScope(Job() + Dispatchers.Default)
@ -79,8 +81,8 @@ class CategoryPresenter(
// Insert into database.
cat.mangaSort = LibrarySort.Title.categoryValue
db.insertCategory(cat).executeAsBlocking()
// FIXME: Don't do blocking
runBlocking { insertCategories.awaitOne(cat) }
val cats = runBlocking { getCategories.await() }
val newCat = cats.find { it.name == name } ?: return false
categories.add(1, newCat)
@ -94,10 +96,12 @@ class CategoryPresenter(
* @param category The category to delete.
*/
fun deleteCategory(category: Category?) {
val safeCategory = category ?: return
db.deleteCategory(safeCategory).executeAsBlocking()
categories.remove(safeCategory)
controller.setCategories(categories.map(::CategoryItem))
val safeCategory = category?.id ?: return
scope.launch {
deleteCategories.awaitOne(safeCategory.toLong())
categories.remove(category)
controller.setCategories(categories.map(::CategoryItem))
}
}
/**
@ -107,10 +111,19 @@ class CategoryPresenter(
*/
fun reorderCategories(categories: List<Category>) {
scope.launch {
categories.forEachIndexed { i, category ->
if (category.order != CREATE_CATEGORY_ORDER) category.order = i - 1
}
db.insertCategories(categories.filter { it.order != CREATE_CATEGORY_ORDER }).executeOnIO()
val updates: MutableList<CategoryUpdate> = mutableListOf()
categories
.filter { it.order != CREATE_CATEGORY_ORDER }
.forEachIndexed { i, category ->
category.order = i - 1
updates.add(
CategoryUpdate(
id = category.id!!.toLong(),
order = category.order.toLong(),
)
)
}
updateCategories.await(updates)
this@CategoryPresenter.categories = categories.sortedBy { it.order }.toMutableList()
withContext(Dispatchers.Main) {
controller.setCategories(this@CategoryPresenter.categories.map(::CategoryItem))
@ -135,7 +148,14 @@ class CategoryPresenter(
}
category.name = name
db.insertCategory(category).executeAsBlocking()
runBlocking {
updateCategories.awaitOne(
CategoryUpdate(
id = category.id!!.toLong(),
name = category.name,
)
)
}
categories.find { it.id == category.id }?.name = name
controller.setCategories(categories.map(::CategoryItem))
return true

View file

@ -9,7 +9,10 @@ import yokai.data.library.custom.CustomMangaRepositoryImpl
import yokai.data.manga.MangaRepositoryImpl
import yokai.data.track.TrackRepositoryImpl
import yokai.domain.category.CategoryRepository
import yokai.domain.category.interactor.DeleteCategories
import yokai.domain.category.interactor.GetCategories
import yokai.domain.category.interactor.InsertCategories
import yokai.domain.category.interactor.UpdateCategories
import yokai.domain.chapter.ChapterRepository
import yokai.domain.chapter.interactor.DeleteChapter
import yokai.domain.chapter.interactor.GetAvailableScanlators
@ -45,7 +48,10 @@ fun domainModule() = module {
factory { TrustExtension(get(), get()) }
single<CategoryRepository> { CategoryRepositoryImpl(get()) }
factory { DeleteCategories(get()) }
factory { GetCategories(get()) }
factory { InsertCategories(get()) }
factory { UpdateCategories(get()) }
single<ExtensionRepoRepository> { ExtensionRepoRepositoryImpl(get()) }
factory { CreateExtensionRepo(get()) }

View file

@ -1,9 +1,13 @@
package yokai.data.category
import co.touchlab.kermit.Logger
import eu.kanade.tachiyomi.data.database.models.Category
import eu.kanade.tachiyomi.data.database.tables.CategoryTable.COL_MANGA_ORDER
import kotlinx.coroutines.flow.Flow
import yokai.data.DatabaseHandler
import yokai.data.updateStrategyAdapter
import yokai.domain.category.CategoryRepository
import yokai.domain.category.models.CategoryUpdate
class CategoryRepositoryImpl(private val handler: DatabaseHandler) : CategoryRepository {
override suspend fun getAll(): List<Category> =
@ -14,4 +18,64 @@ class CategoryRepositoryImpl(private val handler: DatabaseHandler) : CategoryRep
override fun getAllAsFlow(): Flow<List<Category>> =
handler.subscribeToList { categoriesQueries.findAll(Category::mapper) }
override suspend fun insert(category: Category): Long? =
handler.awaitOneOrNullExecutable {
categoriesQueries.insert(
name = category.name,
mangaOrder = category.mangaOrderToString(),
sort = category.order.toLong(),
flags = category.flags.toLong(),
)
categoriesQueries.selectLastInsertedRowId()
}
override suspend fun insertBulk(categories: List<Category>) =
handler.await(true) {
categories.forEach { category ->
categoriesQueries.insert(
name = category.name,
mangaOrder = category.mangaOrderToString(),
sort = category.order.toLong(),
flags = category.flags.toLong(),
)
}
}
override suspend fun update(update: CategoryUpdate): Boolean {
return try {
partialUpdate(update)
true
} catch (e: Exception) {
Logger.e { "Failed to update category with id '${update.id}'" }
false
}
}
override suspend fun updateAll(updates: List<CategoryUpdate>): Boolean {
return try {
partialUpdate(*updates.toTypedArray())
true
} catch (e: Exception) {
Logger.e(e) { "Failed to bulk update categories" }
false
}
}
private suspend fun partialUpdate(vararg updates: CategoryUpdate) {
handler.await(inTransaction = true) {
updates.forEach { update ->
categoriesQueries.update(
id = update.id,
name = update.name,
mangaOrder = update.mangaOrder,
sort = update.order,
flags = update.flags,
)
}
}
}
override suspend fun delete(id: Long) =
handler.await { categoriesQueries.delete(id) }
}

View file

@ -2,9 +2,15 @@ package yokai.domain.category
import eu.kanade.tachiyomi.data.database.models.Category
import kotlinx.coroutines.flow.Flow
import yokai.domain.category.models.CategoryUpdate
interface CategoryRepository {
suspend fun getAll(): List<Category>
suspend fun getAllByMangaId(mangaId: Long): List<Category>
fun getAllAsFlow(): Flow<List<Category>>
suspend fun insert(category: Category): Long?
suspend fun insertBulk(categories: List<Category>)
suspend fun update(update: CategoryUpdate): Boolean
suspend fun updateAll(updates: List<CategoryUpdate>): Boolean
suspend fun delete(id: Long)
}

View file

@ -0,0 +1,12 @@
package yokai.domain.category.interactor
import eu.kanade.tachiyomi.data.database.models.Category
import yokai.domain.category.CategoryRepository
import yokai.domain.category.models.CategoryUpdate
class DeleteCategories(
private val categoryRepository: CategoryRepository,
) {
// suspend fun await(updates: List<Int>) =
suspend fun awaitOne(id: Long) = categoryRepository.delete(id)
}

View file

@ -0,0 +1,11 @@
package yokai.domain.category.interactor
import eu.kanade.tachiyomi.data.database.models.Category
import yokai.domain.category.CategoryRepository
class InsertCategories(
private val categoryRepository: CategoryRepository,
) {
suspend fun await(categories: List<Category>) = categoryRepository.insertBulk(categories)
suspend fun awaitOne(category: Category) = categoryRepository.insert(category)
}

View file

@ -0,0 +1,12 @@
package yokai.domain.category.interactor
import eu.kanade.tachiyomi.data.database.models.Category
import yokai.domain.category.CategoryRepository
import yokai.domain.category.models.CategoryUpdate
class UpdateCategories(
private val categoryRepository: CategoryRepository,
) {
suspend fun await(updates: List<CategoryUpdate>) = categoryRepository.updateAll(updates)
suspend fun awaitOne(update: CategoryUpdate) = categoryRepository.update(update)
}

View file

@ -15,3 +15,22 @@ findAllByMangaId:
SELECT categories.* FROM categories
JOIN mangas_categories ON categories._id = mangas_categories.category_id
WHERE mangas_categories.manga_id = :mangaId;
insert:
INSERT INTO categories (name, sort, flags, manga_order)
VALUES (:name, :sort, :flags, :mangaOrder);
selectLastInsertedRowId:
SELECT last_insert_rowid();
update:
UPDATE categories SET
name = coalesce(:name, name),
sort = coalesce(:sort, sort),
flags = coalesce(:flags, flags),
manga_order = coalesce(:mangaOrder, manga_order)
WHERE _id = :id;
delete:
DELETE FROM categories
WHERE _id = :id;

View file

@ -0,0 +1,9 @@
package yokai.domain.category.models
data class CategoryUpdate(
val id: Long,
val name: String? = null,
val mangaOrder: String? = null,
val order: Long? = null,
val flags: Long? = null,
)