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.content.Context
import android.net.Uri import android.net.Uri
import eu.kanade.tachiyomi.data.backup.BackupNotifier 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.CategoriesBackupRestorer
import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaBackupRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.MangaBackupRestorer
import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceBackupRestorer import eu.kanade.tachiyomi.data.backup.restore.restorers.PreferenceBackupRestorer

View file

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

View file

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

View file

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

View file

@ -1,9 +1,13 @@
package yokai.data.category package yokai.data.category
import co.touchlab.kermit.Logger
import eu.kanade.tachiyomi.data.database.models.Category 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 kotlinx.coroutines.flow.Flow
import yokai.data.DatabaseHandler import yokai.data.DatabaseHandler
import yokai.data.updateStrategyAdapter
import yokai.domain.category.CategoryRepository import yokai.domain.category.CategoryRepository
import yokai.domain.category.models.CategoryUpdate
class CategoryRepositoryImpl(private val handler: DatabaseHandler) : CategoryRepository { class CategoryRepositoryImpl(private val handler: DatabaseHandler) : CategoryRepository {
override suspend fun getAll(): List<Category> = override suspend fun getAll(): List<Category> =
@ -14,4 +18,64 @@ class CategoryRepositoryImpl(private val handler: DatabaseHandler) : CategoryRep
override fun getAllAsFlow(): Flow<List<Category>> = override fun getAllAsFlow(): Flow<List<Category>> =
handler.subscribeToList { categoriesQueries.findAll(Category::mapper) } 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 eu.kanade.tachiyomi.data.database.models.Category
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import yokai.domain.category.models.CategoryUpdate
interface CategoryRepository { interface CategoryRepository {
suspend fun getAll(): List<Category> suspend fun getAll(): List<Category>
suspend fun getAllByMangaId(mangaId: Long): List<Category> suspend fun getAllByMangaId(mangaId: Long): List<Category>
fun getAllAsFlow(): Flow<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 SELECT categories.* FROM categories
JOIN mangas_categories ON categories._id = mangas_categories.category_id JOIN mangas_categories ON categories._id = mangas_categories.category_id
WHERE mangas_categories.manga_id = :mangaId; 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,
)