From 05085fe57fe4c3ada497f93b8cd282a5009cdbbb Mon Sep 17 00:00:00 2001 From: Andreas Date: Sat, 2 Jul 2022 18:55:34 +0200 Subject: [PATCH] Use SQLDelight on Library screen (#7432) - Uses the new `asObservable` function to change the database calls to use SQLDelight, which should make the impact minimal when it comes to bugs. - Use interactors where they already exist - The todos are for the Compose rewrite - Removed unused StorIO methods/queries - Tested loading library, move manga to new category, unfavorite multiple manga, move multiple manga from one category to another, change filter, sort and display settings (with and without per category settings), (un)mark chapters, start/delete downloads Thank Syer for asObservable Co-authored-by: jobobby04 <17078382+jobobby04@users.noreply.github.com> Co-authored-by: jobobby04 <17078382+jobobby04@users.noreply.github.com> --- .../eu/kanade/core/util/RxJavaExtensions.kt | 36 ++++ .../data/category/CategoryRepositoryImpl.kt | 18 +- .../kanade/data/manga/MangaRepositoryImpl.kt | 60 ++++-- .../kanade/data/track/TrackRepositoryImpl.kt | 8 +- .../java/eu/kanade/domain/DomainModule.kt | 4 +- .../category/interactor/GetCategories.kt | 6 +- ...aToCategories.kt => SetMangaCategories.kt} | 4 +- .../category/repository/CategoryRepository.kt | 6 +- .../domain/manga/interactor/UpdateManga.kt | 4 + .../manga/repository/MangaRepository.kt | 4 +- .../domain/track/interactor/GetTracks.kt | 8 +- .../track/repository/TrackRepository.kt | 4 +- .../tachiyomi/data/database/DatabaseHelper.kt | 3 +- .../data/database/queries/CategoryQueries.kt | 2 - .../data/database/queries/MangaQueries.kt | 32 --- .../data/database/queries/RawQueries.kt | 34 --- .../data/database/queries/TrackQueries.kt | 18 -- .../migration/search/SearchPresenter.kt | 6 +- .../tachiyomi/ui/library/LibraryController.kt | 42 ++-- .../tachiyomi/ui/library/LibraryPresenter.kt | 195 ++++++++++++------ .../ui/library/LibrarySettingsSheet.kt | 41 +++- .../tachiyomi/ui/manga/MangaPresenter.kt | 6 +- app/src/main/sqldelight/data/manga_sync.sq | 4 + app/src/main/sqldelight/data/mangas.sq | 55 +++++ 24 files changed, 373 insertions(+), 227 deletions(-) rename app/src/main/java/eu/kanade/domain/category/interactor/{MoveMangaToCategories.kt => SetMangaCategories.kt} (79%) delete mode 100644 app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt diff --git a/app/src/main/java/eu/kanade/core/util/RxJavaExtensions.kt b/app/src/main/java/eu/kanade/core/util/RxJavaExtensions.kt index 4d1ef452d..b54fa63ab 100644 --- a/app/src/main/java/eu/kanade/core/util/RxJavaExtensions.kt +++ b/app/src/main/java/eu/kanade/core/util/RxJavaExtensions.kt @@ -1,10 +1,17 @@ package eu.kanade.core.util +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.CoroutineStart +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.channels.awaitClose import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.callbackFlow +import kotlinx.coroutines.launch +import rx.Emitter import rx.Observable import rx.Observer +import kotlin.coroutines.CoroutineContext fun Observable.asFlow(): Flow = callbackFlow { val observer = object : Observer { @@ -23,3 +30,32 @@ fun Observable.asFlow(): Flow = callbackFlow { val subscription = subscribe(observer) awaitClose { subscription.unsubscribe() } } + +fun Flow.asObservable( + context: CoroutineContext = Dispatchers.Unconfined, + backpressureMode: Emitter.BackpressureMode = Emitter.BackpressureMode.NONE, +): Observable { + return Observable.create( + { emitter -> + /* + * ATOMIC is used here to provide stable behaviour of subscribe+dispose pair even if + * asObservable is already invoked from unconfined + */ + val job = GlobalScope.launch(context = context, start = CoroutineStart.ATOMIC) { + try { + collect { emitter.onNext(it) } + emitter.onCompleted() + } catch (e: Throwable) { + // Ignore `CancellationException` as error, since it indicates "normal cancellation" + if (e !is CancellationException) { + emitter.onError(e) + } else { + emitter.onCompleted() + } + } + } + emitter.setCancellation { job.cancel() } + }, + backpressureMode, + ) +} diff --git a/app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt index 44b78cd6d..ef90ad085 100644 --- a/app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt +++ b/app/src/main/java/eu/kanade/data/category/CategoryRepositoryImpl.kt @@ -15,6 +15,18 @@ class CategoryRepositoryImpl( return handler.subscribeToList { categoriesQueries.getCategories(categoryMapper) } } + override suspend fun getCategoriesByMangaId(mangaId: Long): List { + return handler.awaitList { + categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper) + } + } + + override fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow> { + return handler.subscribeToList { + categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper) + } + } + @Throws(DuplicateNameException::class) override suspend fun insert(name: String, order: Long) { if (checkDuplicateName(name)) throw DuplicateNameException(name) @@ -48,12 +60,6 @@ class CategoryRepositoryImpl( } } - override suspend fun getCategoriesForManga(mangaId: Long): List { - return handler.awaitList { - categoriesQueries.getCategoriesByMangaId(mangaId, categoryMapper) - } - } - override suspend fun checkDuplicateName(name: String): Boolean { return handler .awaitList { categoriesQueries.getCategories() } diff --git a/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt index b90a46f87..9058eac34 100644 --- a/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt +++ b/app/src/main/java/eu/kanade/data/manga/MangaRepositoryImpl.kt @@ -46,7 +46,7 @@ class MangaRepositoryImpl( } } - override suspend fun moveMangaToCategories(mangaId: Long, categoryIds: List) { + override suspend fun setMangaCategories(mangaId: Long, categoryIds: List) { handler.await(inTransaction = true) { mangas_categoriesQueries.deleteMangaCategoryByMangaId(mangaId) categoryIds.map { categoryId -> @@ -57,31 +57,47 @@ class MangaRepositoryImpl( override suspend fun update(update: MangaUpdate): Boolean { return try { - handler.await { - mangasQueries.update( - source = update.source, - url = update.url, - artist = update.artist, - author = update.author, - description = update.description, - genre = update.genre?.let(listOfStringsAdapter::encode), - title = update.title, - status = update.status, - thumbnailUrl = update.thumbnailUrl, - favorite = update.favorite?.toLong(), - lastUpdate = update.lastUpdate, - initialized = update.initialized?.toLong(), - viewer = update.viewerFlags, - chapterFlags = update.chapterFlags, - coverLastModified = update.coverLastModified, - dateAdded = update.dateAdded, - mangaId = update.id, - ) - } + partialUpdate(update) true } catch (e: Exception) { logcat(LogPriority.ERROR, e) false } } + + override suspend fun updateAll(values: List): Boolean { + return try { + partialUpdate(*values.toTypedArray()) + true + } catch (e: Exception) { + logcat(LogPriority.ERROR, e) + false + } + } + + private suspend fun partialUpdate(vararg values: MangaUpdate) { + handler.await(inTransaction = true) { + values.forEach { value -> + mangasQueries.update( + source = value.source, + url = value.url, + artist = value.artist, + author = value.author, + description = value.description, + genre = value.genre?.let(listOfStringsAdapter::encode), + title = value.title, + status = value.status, + thumbnailUrl = value.thumbnailUrl, + favorite = value.favorite?.toLong(), + lastUpdate = value.lastUpdate, + initialized = value.initialized?.toLong(), + viewer = value.viewerFlags, + chapterFlags = value.chapterFlags, + coverLastModified = value.coverLastModified, + dateAdded = value.dateAdded, + mangaId = value.id, + ) + } + } + } } diff --git a/app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt b/app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt index ac8942eb7..30481edaf 100644 --- a/app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt +++ b/app/src/main/java/eu/kanade/data/track/TrackRepositoryImpl.kt @@ -15,7 +15,13 @@ class TrackRepositoryImpl( } } - override suspend fun subscribeTracksByMangaId(mangaId: Long): Flow> { + override fun getTracksAsFlow(): Flow> { + return handler.subscribeToList { + manga_syncQueries.getTracks(trackMapper) + } + } + + override fun getTracksByMangaIdAsFlow(mangaId: Long): Flow> { return handler.subscribeToList { manga_syncQueries.getTracksByMangaId(mangaId, trackMapper) } diff --git a/app/src/main/java/eu/kanade/domain/DomainModule.kt b/app/src/main/java/eu/kanade/domain/DomainModule.kt index bcd296777..622060862 100644 --- a/app/src/main/java/eu/kanade/domain/DomainModule.kt +++ b/app/src/main/java/eu/kanade/domain/DomainModule.kt @@ -9,7 +9,7 @@ import eu.kanade.data.track.TrackRepositoryImpl import eu.kanade.domain.category.interactor.DeleteCategory import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.InsertCategory -import eu.kanade.domain.category.interactor.MoveMangaToCategories +import eu.kanade.domain.category.interactor.SetMangaCategories import eu.kanade.domain.category.interactor.UpdateCategory import eu.kanade.domain.category.repository.CategoryRepository import eu.kanade.domain.chapter.interactor.GetChapter @@ -77,7 +77,7 @@ class DomainModule : InjektModule { addFactory { ResetViewerFlags(get()) } addFactory { SetMangaChapterFlags(get()) } addFactory { UpdateManga(get()) } - addFactory { MoveMangaToCategories(get()) } + addFactory { SetMangaCategories(get()) } addSingletonFactory { TrackRepositoryImpl(get()) } addFactory { DeleteTrack(get()) } diff --git a/app/src/main/java/eu/kanade/domain/category/interactor/GetCategories.kt b/app/src/main/java/eu/kanade/domain/category/interactor/GetCategories.kt index ab1f5f240..5cedc70c3 100644 --- a/app/src/main/java/eu/kanade/domain/category/interactor/GetCategories.kt +++ b/app/src/main/java/eu/kanade/domain/category/interactor/GetCategories.kt @@ -12,7 +12,11 @@ class GetCategories( return categoryRepository.getAll() } + fun subscribe(mangaId: Long): Flow> { + return categoryRepository.getCategoriesByMangaIdAsFlow(mangaId) + } + suspend fun await(mangaId: Long): List { - return categoryRepository.getCategoriesForManga(mangaId) + return categoryRepository.getCategoriesByMangaId(mangaId) } } diff --git a/app/src/main/java/eu/kanade/domain/category/interactor/MoveMangaToCategories.kt b/app/src/main/java/eu/kanade/domain/category/interactor/SetMangaCategories.kt similarity index 79% rename from app/src/main/java/eu/kanade/domain/category/interactor/MoveMangaToCategories.kt rename to app/src/main/java/eu/kanade/domain/category/interactor/SetMangaCategories.kt index 567ec133b..1ddb55c3b 100644 --- a/app/src/main/java/eu/kanade/domain/category/interactor/MoveMangaToCategories.kt +++ b/app/src/main/java/eu/kanade/domain/category/interactor/SetMangaCategories.kt @@ -4,13 +4,13 @@ import eu.kanade.domain.manga.repository.MangaRepository import eu.kanade.tachiyomi.util.system.logcat import logcat.LogPriority -class MoveMangaToCategories( +class SetMangaCategories( private val mangaRepository: MangaRepository, ) { suspend fun await(mangaId: Long, categoryIds: List) { try { - mangaRepository.moveMangaToCategories(mangaId, categoryIds) + mangaRepository.setMangaCategories(mangaId, categoryIds) } catch (e: Exception) { logcat(LogPriority.ERROR, e) } diff --git a/app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt b/app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt index 231cd81f8..19418f438 100644 --- a/app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt +++ b/app/src/main/java/eu/kanade/domain/category/repository/CategoryRepository.kt @@ -8,6 +8,10 @@ interface CategoryRepository { fun getAll(): Flow> + suspend fun getCategoriesByMangaId(mangaId: Long): List + + fun getCategoriesByMangaIdAsFlow(mangaId: Long): Flow> + @Throws(DuplicateNameException::class) suspend fun insert(name: String, order: Long) @@ -16,8 +20,6 @@ interface CategoryRepository { suspend fun delete(categoryId: Long) - suspend fun getCategoriesForManga(mangaId: Long): List - suspend fun checkDuplicateName(name: String): Boolean } diff --git a/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt b/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt index 1071997ac..329359253 100644 --- a/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt +++ b/app/src/main/java/eu/kanade/domain/manga/interactor/UpdateManga.kt @@ -20,6 +20,10 @@ class UpdateManga( return mangaRepository.update(mangaUpdate) } + suspend fun awaitAll(values: List): Boolean { + return mangaRepository.updateAll(values) + } + suspend fun awaitUpdateFromSource( localManga: Manga, remoteManga: MangaInfo, diff --git a/app/src/main/java/eu/kanade/domain/manga/repository/MangaRepository.kt b/app/src/main/java/eu/kanade/domain/manga/repository/MangaRepository.kt index 60c07ba84..d5d282832 100644 --- a/app/src/main/java/eu/kanade/domain/manga/repository/MangaRepository.kt +++ b/app/src/main/java/eu/kanade/domain/manga/repository/MangaRepository.kt @@ -18,7 +18,9 @@ interface MangaRepository { suspend fun resetViewerFlags(): Boolean - suspend fun moveMangaToCategories(mangaId: Long, categoryIds: List) + suspend fun setMangaCategories(mangaId: Long, categoryIds: List) suspend fun update(update: MangaUpdate): Boolean + + suspend fun updateAll(values: List): Boolean } diff --git a/app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt b/app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt index a48a40421..519d8a843 100644 --- a/app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt +++ b/app/src/main/java/eu/kanade/domain/track/interactor/GetTracks.kt @@ -19,7 +19,11 @@ class GetTracks( } } - suspend fun subscribe(mangaId: Long): Flow> { - return trackRepository.subscribeTracksByMangaId(mangaId) + fun subscribe(): Flow> { + return trackRepository.getTracksAsFlow() + } + + fun subscribe(mangaId: Long): Flow> { + return trackRepository.getTracksByMangaIdAsFlow(mangaId) } } diff --git a/app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt b/app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt index 38207a2f2..65e4937f7 100644 --- a/app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt +++ b/app/src/main/java/eu/kanade/domain/track/repository/TrackRepository.kt @@ -7,7 +7,9 @@ interface TrackRepository { suspend fun getTracksByMangaId(mangaId: Long): List - suspend fun subscribeTracksByMangaId(mangaId: Long): Flow> + fun getTracksAsFlow(): Flow> + + fun getTracksByMangaIdAsFlow(mangaId: Long): Flow> suspend fun delete(mangaId: Long, syncId: Long) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt index e87fa54f5..3e2db4bbb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/DatabaseHelper.kt @@ -18,7 +18,6 @@ import eu.kanade.tachiyomi.data.database.queries.CategoryQueries import eu.kanade.tachiyomi.data.database.queries.ChapterQueries import eu.kanade.tachiyomi.data.database.queries.MangaCategoryQueries import eu.kanade.tachiyomi.data.database.queries.MangaQueries -import eu.kanade.tachiyomi.data.database.queries.TrackQueries /** * This class provides operations to manage the database through its interfaces. @@ -26,7 +25,7 @@ import eu.kanade.tachiyomi.data.database.queries.TrackQueries class DatabaseHelper( openHelper: SupportSQLiteOpenHelper, ) : - MangaQueries, ChapterQueries, TrackQueries, CategoryQueries, MangaCategoryQueries { + MangaQueries, ChapterQueries, CategoryQueries, MangaCategoryQueries { override val db = DefaultStorIOSQLite.builder() .sqliteOpenHelper(openHelper) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt index 78755ed83..48b1e5beb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/CategoryQueries.kt @@ -28,6 +28,4 @@ interface CategoryQueries : DbProvider { .build(), ) .prepare() - - fun insertCategory(category: Category) = db.put().`object`(category).prepare() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt index 35ab51f44..edaddecb9 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt @@ -60,8 +60,6 @@ interface MangaQueries : DbProvider { fun insertManga(manga: Manga) = db.put().`object`(manga).prepare() - fun insertMangas(mangas: List) = db.put().objects(mangas).prepare() - fun updateChapterFlags(manga: Manga) = db.put() .`object`(manga) .withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags)) @@ -76,34 +74,4 @@ interface MangaQueries : DbProvider { .`object`(manga) .withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags)) .prepare() - - fun getLastReadManga() = db.get() - .listOfObjects(Manga::class.java) - .withQuery( - RawQuery.builder() - .query(getLastReadMangaQuery()) - .observesTables(MangaTable.TABLE) - .build(), - ) - .prepare() - - fun getLatestChapterManga() = db.get() - .listOfObjects(Manga::class.java) - .withQuery( - RawQuery.builder() - .query(getLatestChapterMangaQuery()) - .observesTables(MangaTable.TABLE) - .build(), - ) - .prepare() - - fun getChapterFetchDateManga() = db.get() - .listOfObjects(Manga::class.java) - .withQuery( - RawQuery.builder() - .query(getChapterFetchDateMangaQuery()) - .observesTables(MangaTable.TABLE) - .build(), - ) - .prepare() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt index 4ae959241..6eebf2f33 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/RawQueries.kt @@ -2,7 +2,6 @@ package eu.kanade.tachiyomi.data.database.queries import eu.kanade.tachiyomi.data.database.tables.CategoryTable as Category import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter -import eu.kanade.tachiyomi.data.database.tables.HistoryTable as History import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga @@ -38,39 +37,6 @@ val libraryQuery = ON MC.${MangaCategory.COL_MANGA_ID} = M.${Manga.COL_ID} """ -fun getLastReadMangaQuery() = - """ - SELECT ${Manga.TABLE}.*, MAX(${History.TABLE}.${History.COL_LAST_READ}) AS max - FROM ${Manga.TABLE} - JOIN ${Chapter.TABLE} - ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} - JOIN ${History.TABLE} - ON ${Chapter.TABLE}.${Chapter.COL_ID} = ${History.TABLE}.${History.COL_CHAPTER_ID} - WHERE ${Manga.TABLE}.${Manga.COL_FAVORITE} = 1 - GROUP BY ${Manga.TABLE}.${Manga.COL_ID} - ORDER BY max DESC -""" - -fun getLatestChapterMangaQuery() = - """ - SELECT ${Manga.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_UPLOAD}) AS max - FROM ${Manga.TABLE} - JOIN ${Chapter.TABLE} - ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} - GROUP BY ${Manga.TABLE}.${Manga.COL_ID} - ORDER by max DESC -""" - -fun getChapterFetchDateMangaQuery() = - """ - SELECT ${Manga.TABLE}.*, MAX(${Chapter.TABLE}.${Chapter.COL_DATE_FETCH}) AS max - FROM ${Manga.TABLE} - JOIN ${Chapter.TABLE} - ON ${Manga.TABLE}.${Manga.COL_ID} = ${Chapter.TABLE}.${Chapter.COL_MANGA_ID} - GROUP BY ${Manga.TABLE}.${Manga.COL_ID} - ORDER by max DESC -""" - /** * Query to get the categories for a manga. */ diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt b/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt deleted file mode 100644 index a0a917ab5..000000000 --- a/app/src/main/java/eu/kanade/tachiyomi/data/database/queries/TrackQueries.kt +++ /dev/null @@ -1,18 +0,0 @@ -package eu.kanade.tachiyomi.data.database.queries - -import com.pushtorefresh.storio.sqlite.queries.Query -import eu.kanade.tachiyomi.data.database.DbProvider -import eu.kanade.tachiyomi.data.database.models.Track -import eu.kanade.tachiyomi.data.database.tables.TrackTable - -interface TrackQueries : DbProvider { - - fun getTracks() = db.get() - .listOfObjects(Track::class.java) - .withQuery( - Query.builder() - .table(TrackTable.TABLE) - .build(), - ) - .prepare() -} diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt index 560281829..323b0b4eb 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/browse/migration/search/SearchPresenter.kt @@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.browse.migration.search import android.os.Bundle import com.jakewharton.rxrelay.BehaviorRelay import eu.kanade.domain.category.interactor.GetCategories -import eu.kanade.domain.category.interactor.MoveMangaToCategories +import eu.kanade.domain.category.interactor.SetMangaCategories import eu.kanade.domain.chapter.interactor.GetChapterByMangaId import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.UpdateChapter @@ -48,7 +48,7 @@ class SearchPresenter( private val getCategories: GetCategories = Injekt.get(), private val getTracks: GetTracks = Injekt.get(), private val insertTrack: InsertTrack = Injekt.get(), - private val moveMangaToCategories: MoveMangaToCategories = Injekt.get(), + private val setMangaCategories: SetMangaCategories = Injekt.get(), ) : GlobalSearchPresenter(initialQuery) { private val replacingMangaRelay = BehaviorRelay.create>() @@ -164,7 +164,7 @@ class SearchPresenter( // Update categories if (migrateCategories) { val categoryIds = getCategories.await(prevDomainManga.id).map { it.id } - moveMangaToCategories.await(domainManga.id, categoryIds) + setMangaCategories.await(domainManga.id, categoryIds) } // Update track diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt index 4318989fe..806a4084d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryController.kt @@ -29,6 +29,8 @@ import eu.kanade.tachiyomi.ui.base.controller.pushController import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController import eu.kanade.tachiyomi.ui.main.MainActivity import eu.kanade.tachiyomi.ui.manga.MangaController +import eu.kanade.tachiyomi.util.lang.launchIO +import eu.kanade.tachiyomi.util.lang.launchUI import eu.kanade.tachiyomi.util.preference.asImmediateFlow import eu.kanade.tachiyomi.util.system.getResourceColor import eu.kanade.tachiyomi.util.system.openInBrowser @@ -36,6 +38,7 @@ import eu.kanade.tachiyomi.util.system.toast import eu.kanade.tachiyomi.widget.ActionModeWithToolbar import eu.kanade.tachiyomi.widget.EmptyView import eu.kanade.tachiyomi.widget.materialdialogs.QuadStateTextView +import kotlinx.coroutines.cancel import kotlinx.coroutines.flow.drop import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.onEach @@ -226,6 +229,7 @@ class LibraryController( destroyActionModeIfNeeded() adapter?.onDestroy() adapter = null + settingsSheet?.sheetScope?.cancel() settingsSheet = null tabsVisibilitySubscription?.unsubscribe() tabsVisibilitySubscription = null @@ -541,25 +545,29 @@ class LibraryController( * Move the selected manga to a list of categories. */ private fun showMangaCategoriesDialog() { - // Create a copy of selected manga - val mangas = selectedMangas.toList() + viewScope.launchIO { + // Create a copy of selected manga + val mangas = selectedMangas.toList() - // Hide the default category because it has a different behavior than the ones from db. - val categories = presenter.categories.filter { it.id != 0 } + // Hide the default category because it has a different behavior than the ones from db. + val categories = presenter.categories.filter { it.id != 0 } - // Get indexes of the common categories to preselect. - val common = presenter.getCommonCategories(mangas) - // Get indexes of the mix categories to preselect. - val mix = presenter.getMixCategories(mangas) - val preselected = categories.map { - when (it) { - in common -> QuadStateTextView.State.CHECKED.ordinal - in mix -> QuadStateTextView.State.INDETERMINATE.ordinal - else -> QuadStateTextView.State.UNCHECKED.ordinal + // Get indexes of the common categories to preselect. + val common = presenter.getCommonCategories(mangas) + // Get indexes of the mix categories to preselect. + val mix = presenter.getMixCategories(mangas) + val preselected = categories.map { + when (it) { + in common -> QuadStateTextView.State.CHECKED.ordinal + in mix -> QuadStateTextView.State.INDETERMINATE.ordinal + else -> QuadStateTextView.State.UNCHECKED.ordinal + } + }.toTypedArray() + launchUI { + ChangeMangaCategoriesDialog(this@LibraryController, mangas, categories, preselected) + .showDialog(router) } - }.toTypedArray() - ChangeMangaCategoriesDialog(this, mangas, categories, preselected) - .showDialog(router) + } } private fun downloadUnreadChapters() { @@ -579,7 +587,7 @@ class LibraryController( } override fun updateCategoriesForMangas(mangas: List, addCategories: List, removeCategories: List) { - presenter.updateMangasToCategories(mangas, addCategories, removeCategories) + presenter.setMangaCategories(mangas, addCategories, removeCategories) destroyActionModeIfNeeded() } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt index 63d7bfc5b..a5297d98b 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibraryPresenter.kt @@ -2,12 +2,23 @@ package eu.kanade.tachiyomi.ui.library import android.os.Bundle import com.jakewharton.rxrelay.BehaviorRelay +import eu.kanade.core.util.asObservable +import eu.kanade.data.DatabaseHandler +import eu.kanade.domain.category.interactor.GetCategories +import eu.kanade.domain.category.interactor.SetMangaCategories +import eu.kanade.domain.category.model.toDbCategory +import eu.kanade.domain.chapter.interactor.GetChapterByMangaId +import eu.kanade.domain.chapter.interactor.UpdateChapter +import eu.kanade.domain.chapter.model.ChapterUpdate +import eu.kanade.domain.chapter.model.toDbChapter +import eu.kanade.domain.manga.interactor.UpdateManga +import eu.kanade.domain.manga.model.MangaUpdate +import eu.kanade.domain.track.interactor.GetTracks import eu.kanade.tachiyomi.data.cache.CoverCache -import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.database.models.Chapter +import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.Manga -import eu.kanade.tachiyomi.data.database.models.MangaCategory import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.track.TrackManager @@ -23,6 +34,8 @@ import eu.kanade.tachiyomi.util.lang.isNullOrUnsubscribed import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State +import kotlinx.coroutines.flow.map +import kotlinx.coroutines.runBlocking import rx.Observable import rx.Subscription import rx.android.schedulers.AndroidSchedulers @@ -47,7 +60,13 @@ private typealias LibraryMap = Map> * Presenter of [LibraryController]. */ class LibraryPresenter( - private val db: DatabaseHelper = Injekt.get(), + private val handler: DatabaseHandler = Injekt.get(), + private val getTracks: GetTracks = Injekt.get(), + private val getCategories: GetCategories = Injekt.get(), + private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), + private val updateChapter: UpdateChapter = Injekt.get(), + private val updateManga: UpdateManga = Injekt.get(), + private val setMangaCategories: SetMangaCategories = Injekt.get(), private val preferences: PreferencesHelper = Injekt.get(), private val coverCache: CoverCache = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(), @@ -92,6 +111,7 @@ class LibraryPresenter( * Subscribes to library if needed. */ fun subscribeLibrary() { + // TODO: Move this to a coroutine world if (librarySubscription.isNullOrUnsubscribed()) { librarySubscription = getLibraryObservable() .combineLatest(badgeTriggerRelay.observeOn(Schedulers.io())) { lib, _ -> @@ -115,7 +135,7 @@ class LibraryPresenter( * * @param map the map to filter. */ - private fun applyFilters(map: LibraryMap, trackMap: Map>): LibraryMap { + private fun applyFilters(map: LibraryMap, trackMap: Map>): LibraryMap { val downloadedOnly = preferences.downloadedOnly().get() val filterDownloaded = preferences.filterDownloaded().get() val filterUnread = preferences.filterUnread().get() @@ -252,18 +272,30 @@ class LibraryPresenter( private fun applySort(categories: List, map: LibraryMap): LibraryMap { val lastReadManga by lazy { var counter = 0 - // Result comes as newest to oldest so it's reversed - db.getLastReadManga().executeAsBlocking().reversed().associate { it.id!! to counter++ } + // TODO: Make [applySort] a suspended function + runBlocking { + handler.awaitList { + mangasQueries.getLastRead() + }.associate { it._id to counter++ } + } } val latestChapterManga by lazy { var counter = 0 - // Result comes as newest to oldest so it's reversed - db.getLatestChapterManga().executeAsBlocking().reversed().associate { it.id!! to counter++ } + // TODO: Make [applySort] a suspended function + runBlocking { + handler.awaitList { + mangasQueries.getLatestByChapterUploadDate() + }.associate { it._id to counter++ } + } } val chapterFetchDateManga by lazy { var counter = 0 - // Result comes as newest to oldest so it's reversed - db.getChapterFetchDateManga().executeAsBlocking().reversed().associate { it.id!! to counter++ } + // TODO: Make [applySort] a suspended function + runBlocking { + handler.awaitList { + mangasQueries.getLatestByChapterFetchDate() + }.associate { it._id to counter++ } + } } val sortingModes = categories.associate { category -> @@ -366,7 +398,7 @@ class LibraryPresenter( * @return an observable of the categories. */ private fun getCategoriesObservable(): Observable> { - return db.getCategories().asRxObservable() + return getCategories.subscribe().map { it.map { it.toDbCategory() } }.asObservable() } /** @@ -378,7 +410,36 @@ class LibraryPresenter( private fun getLibraryMangasObservable(): Observable { val defaultLibraryDisplayMode = preferences.libraryDisplayMode() val shouldSetFromCategory = preferences.categorizedDisplaySettings() - return db.getLibraryMangas().asRxObservable() + + // TODO: Move this to domain/data layer + return handler + .subscribeToList { + mangasQueries.getLibrary { _id: Long, source: Long, url: String, artist: String?, author: String?, description: String?, genre: List?, title: String, status: Long, thumbnail_url: String?, favorite: Boolean, last_update: Long?, next_update: Long?, initialized: Boolean, viewer: Long, chapter_flags: Long, cover_last_modified: Long, date_added: Long, unread_count: Long, read_count: Long, category: Long -> + LibraryManga().apply { + this.id = _id + this.source = source + this.url = url + this.artist = artist + this.author = author + this.description = description + this.genre = genre?.joinToString() + this.title = title + this.status = status.toInt() + this.thumbnail_url = thumbnail_url + this.favorite = favorite + this.last_update = last_update ?: 0 + this.initialized = initialized + this.viewer_flags = viewer.toInt() + this.chapter_flags = chapter_flags.toInt() + this.cover_last_modified = cover_last_modified + this.date_added = date_added + this.unreadCount = unread_count.toInt() + this.readCount = read_count.toInt() + this.category = category.toInt() + } + } + } + .asObservable() .map { list -> list.map { libraryManga -> // Display mode based on user preference: take it from global library setting or category @@ -396,7 +457,7 @@ class LibraryPresenter( * * @return an observable of tracked manga. */ - private fun getFilterObservable(): Observable>> { + private fun getFilterObservable(): Observable>> { return getTracksObservable().combineLatest(filterTriggerRelay.observeOn(Schedulers.io())) { tracks, _ -> tracks } } @@ -405,16 +466,20 @@ class LibraryPresenter( * * @return an observable of tracked manga. */ - private fun getTracksObservable(): Observable>> { - return db.getTracks().asRxObservable().map { tracks -> - tracks.groupBy { it.manga_id } - .mapValues { tracksForMangaId -> - // Check if any of the trackers is logged in for the current manga id - tracksForMangaId.value.associate { - Pair(it.sync_id, trackManager.getService(it.sync_id.toLong())?.isLogged ?: false) + private fun getTracksObservable(): Observable>> { + // TODO: Move this to domain/data layer + return getTracks.subscribe() + .asObservable().map { tracks -> + tracks + .groupBy { it.mangaId } + .mapValues { tracksForMangaId -> + // Check if any of the trackers is logged in for the current manga id + tracksForMangaId.value.associate { + Pair(it.syncId, trackManager.getService(it.syncId)?.isLogged ?: false) + } } - } - }.observeOn(Schedulers.io()) + } + .observeOn(Schedulers.io()) } /** @@ -451,11 +516,11 @@ class LibraryPresenter( * * @param mangas the list of manga. */ - fun getCommonCategories(mangas: List): Collection { + suspend fun getCommonCategories(mangas: List): Collection { if (mangas.isEmpty()) return emptyList() return mangas.toSet() - .map { db.getCategoriesForManga(it).executeAsBlocking() } - .reduce { set1: Iterable, set2 -> set1.intersect(set2).toMutableList() } + .map { getCategories.await(it.id!!).map { it.toDbCategory() } } + .reduce { set1, set2 -> set1.intersect(set2).toMutableList() } } /** @@ -463,9 +528,9 @@ class LibraryPresenter( * * @param mangas the list of manga. */ - fun getMixCategories(mangas: List): Collection { + suspend fun getMixCategories(mangas: List): Collection { if (mangas.isEmpty()) return emptyList() - val mangaCategories = mangas.toSet().map { db.getCategoriesForManga(it).executeAsBlocking() } + val mangaCategories = mangas.toSet().map { getCategories.await(it.id!!).map { it.toDbCategory() } } val common = mangaCategories.reduce { set1, set2 -> set1.intersect(set2).toMutableList() } return mangaCategories.flatten().distinct().subtract(common).toMutableList() } @@ -478,8 +543,9 @@ class LibraryPresenter( fun downloadUnreadChapters(mangas: List) { mangas.forEach { manga -> launchIO { - val chapters = db.getChapters(manga).executeAsBlocking() + val chapters = getChapterByMangaId.await(manga.id!!) .filter { !it.read } + .map { it.toDbChapter() } downloadManager.downloadChapters(manga, chapters) } @@ -494,17 +560,20 @@ class LibraryPresenter( fun markReadStatus(mangas: List, read: Boolean) { mangas.forEach { manga -> launchIO { - val chapters = db.getChapters(manga).executeAsBlocking() - chapters.forEach { - it.read = read - if (!read) { - it.last_page_read = 0 + val chapters = getChapterByMangaId.await(manga.id!!) + + val toUpdate = chapters + .map { chapter -> + ChapterUpdate( + read = read, + lastPageRead = if (read) 0 else null, + id = chapter.id, + ) } - } - db.updateChaptersProgress(chapters).executeAsBlocking() + updateChapter.awaitAll(toUpdate) if (read && preferences.removeAfterMarkedAsRead()) { - deleteChapters(manga, chapters) + deleteChapters(manga, chapters.map { it.toDbChapter() }) } } } @@ -519,20 +588,23 @@ class LibraryPresenter( /** * Remove the selected manga. * - * @param mangas the list of manga to delete. + * @param mangaList the list of manga to delete. * @param deleteFromLibrary whether to delete manga from library. * @param deleteChapters whether to delete downloaded chapters. */ - fun removeMangas(mangas: List, deleteFromLibrary: Boolean, deleteChapters: Boolean) { + fun removeMangas(mangaList: List, deleteFromLibrary: Boolean, deleteChapters: Boolean) { launchIO { - val mangaToDelete = mangas.distinctBy { it.id } + val mangaToDelete = mangaList.distinctBy { it.id } if (deleteFromLibrary) { - mangaToDelete.forEach { - it.favorite = false + val toDelete = mangaToDelete.map { it.removeCovers(coverCache) + MangaUpdate( + favorite = false, + id = it.id!!, + ) } - db.insertMangas(mangaToDelete).executeAsBlocking() + updateManga.awaitAll(toDelete) } if (deleteChapters) { @@ -547,35 +619,22 @@ class LibraryPresenter( } /** - * Move the given list of manga to categories. + * Bulk update categories of manga using old and new common categories. * - * @param categories the selected categories. - * @param mangas the list of manga to move. - */ - fun moveMangasToCategories(categories: List, mangas: List) { - val mc = mutableListOf() - - for (manga in mangas) { - categories.mapTo(mc) { MangaCategory.create(manga, it) } - } - - db.setMangaCategories(mc, mangas) - } - - /** - * Bulk update categories of mangas using old and new common categories. - * - * @param mangas the list of manga to move. + * @param mangaList the list of manga to move. * @param addCategories the categories to add for all mangas. * @param removeCategories the categories to remove in all mangas. */ - fun updateMangasToCategories(mangas: List, addCategories: List, removeCategories: List) { - val mangaCategories = mangas.map { manga -> - val categories = db.getCategoriesForManga(manga).executeAsBlocking() - .subtract(removeCategories).plus(addCategories).distinct() - categories.map { MangaCategory.create(manga, it) } - }.flatten() - - db.setMangaCategories(mangaCategories, mangas) + fun setMangaCategories(mangaList: List, addCategories: List, removeCategories: List) { + presenterScope.launchIO { + mangaList.map { manga -> + val categoryIds = getCategories.await(manga.id!!) + .map { it.toDbCategory() } + .subtract(removeCategories) + .plus(addCategories) + .mapNotNull { it.id?.toLong() } + setMangaCategories.await(manga.id!!, categoryIds) + } + } } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt index 1795d31c2..700735d07 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/library/LibrarySettingsSheet.kt @@ -4,8 +4,9 @@ import android.content.Context import android.util.AttributeSet import android.view.View import com.bluelinelabs.conductor.Router +import eu.kanade.domain.category.interactor.UpdateCategory +import eu.kanade.domain.category.model.CategoryUpdate import eu.kanade.tachiyomi.R -import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.track.TrackManager @@ -13,9 +14,13 @@ import eu.kanade.tachiyomi.data.track.TrackService import eu.kanade.tachiyomi.ui.library.setting.DisplayModeSetting import eu.kanade.tachiyomi.ui.library.setting.SortDirectionSetting import eu.kanade.tachiyomi.ui.library.setting.SortModeSetting +import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.widget.ExtendedNavigationView import eu.kanade.tachiyomi.widget.ExtendedNavigationView.Item.TriStateGroup.State import eu.kanade.tachiyomi.widget.sheet.TabbedBottomSheetDialog +import kotlinx.coroutines.CoroutineScope +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.Job import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.api.get import uy.kohesive.injekt.injectLazy @@ -23,13 +28,15 @@ import uy.kohesive.injekt.injectLazy class LibrarySettingsSheet( router: Router, private val trackManager: TrackManager = Injekt.get(), + private val updateCategory: UpdateCategory = Injekt.get(), onGroupClickListener: (ExtendedNavigationView.Group) -> Unit, ) : TabbedBottomSheetDialog(router.activity!!) { val filters: Filter private val sort: Sort private val display: Display - private val db: DatabaseHelper by injectLazy() + + val sheetScope = CoroutineScope(Job() + Dispatchers.IO) init { filters = Filter(router.activity!!) @@ -250,8 +257,14 @@ class LibrarySettingsSheet( if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) { currentCategory?.sortDirection = flag.flag - - db.insertCategory(currentCategory!!).executeAsBlocking() + sheetScope.launchIO { + updateCategory.await( + CategoryUpdate( + id = currentCategory!!.id?.toLong()!!, + flags = currentCategory!!.flags.toLong(), + ), + ) + } } else { preferences.librarySortingAscending().set(flag) } @@ -272,8 +285,14 @@ class LibrarySettingsSheet( if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) { currentCategory?.sortMode = flag.flag - - db.insertCategory(currentCategory!!).executeAsBlocking() + sheetScope.launchIO { + updateCategory.await( + CategoryUpdate( + id = currentCategory!!.id?.toLong()!!, + flags = currentCategory!!.flags.toLong(), + ), + ) + } } else { preferences.librarySortingMode().set(flag) } @@ -361,8 +380,14 @@ class LibrarySettingsSheet( if (preferences.categorizedDisplaySettings().get() && currentCategory != null && currentCategory?.id != 0) { currentCategory?.displayMode = flag.flag - - db.insertCategory(currentCategory!!).executeAsBlocking() + sheetScope.launchIO { + updateCategory.await( + CategoryUpdate( + id = currentCategory!!.id?.toLong()!!, + flags = currentCategory!!.flags.toLong(), + ), + ) + } } else { preferences.libraryDisplayMode().set(flag) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt index 12e77fff9..92569e22f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaPresenter.kt @@ -3,7 +3,7 @@ package eu.kanade.tachiyomi.ui.manga import android.os.Bundle import androidx.compose.runtime.Immutable import eu.kanade.domain.category.interactor.GetCategories -import eu.kanade.domain.category.interactor.MoveMangaToCategories +import eu.kanade.domain.category.interactor.SetMangaCategories import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.chapter.interactor.UpdateChapter @@ -90,7 +90,7 @@ class MangaPresenter( private val getCategories: GetCategories = Injekt.get(), private val deleteTrack: DeleteTrack = Injekt.get(), private val getTracks: GetTracks = Injekt.get(), - private val moveMangaToCategories: MoveMangaToCategories = Injekt.get(), + private val setMangaCategories: SetMangaCategories = Injekt.get(), private val insertTrack: InsertTrack = Injekt.get(), private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(), ) : BasePresenter() { @@ -358,7 +358,7 @@ class MangaPresenter( val mangaId = manga.id ?: return val categoryIds = categories.mapNotNull { it.id?.toLong() } presenterScope.launchIO { - moveMangaToCategories.await(mangaId, categoryIds) + setMangaCategories.await(mangaId, categoryIds) } } diff --git a/app/src/main/sqldelight/data/manga_sync.sq b/app/src/main/sqldelight/data/manga_sync.sq index c3276a7b8..93b5b6bb8 100644 --- a/app/src/main/sqldelight/data/manga_sync.sq +++ b/app/src/main/sqldelight/data/manga_sync.sq @@ -21,6 +21,10 @@ delete: DELETE FROM manga_sync WHERE manga_id = :mangaId AND sync_id = :syncId; +getTracks: +SELECT * +FROM manga_sync; + getTracksByMangaId: SELECT * FROM manga_sync diff --git a/app/src/main/sqldelight/data/mangas.sq b/app/src/main/sqldelight/data/mangas.sq index ad4246f4f..753285299 100644 --- a/app/src/main/sqldelight/data/mangas.sq +++ b/app/src/main/sqldelight/data/mangas.sq @@ -86,6 +86,61 @@ AND C.date_upload > :after AND C.date_fetch > M.date_added ORDER BY C.date_upload DESC; +getLibrary: +SELECT M.*, COALESCE(MC.category_id, 0) AS category +FROM ( + SELECT mangas.*, COALESCE(C.unreadCount, 0) AS unread_count, COALESCE(R.readCount, 0) AS read_count + FROM mangas + LEFT JOIN ( + SELECT chapters.manga_id, COUNT(*) AS unreadCount + FROM chapters + WHERE chapters.read = 0 + GROUP BY chapters.manga_id + ) AS C + ON mangas._id = C.manga_id + LEFT JOIN ( + SELECT chapters.manga_id, COUNT(*) AS readCount + FROM chapters + WHERE chapters.read = 1 + GROUP BY chapters.manga_id + ) AS R + WHERE mangas.favorite = 1 + GROUP BY mangas._id + ORDER BY mangas.title +) AS M +LEFT JOIN ( + SELECT * + FROM mangas_categories +) AS MC +ON M._id = MC.manga_id; + +getLastRead: +SELECT M.*, MAX(H.last_read) AS max +FROM mangas M +JOIN chapters C +ON M._id = C.manga_id +JOIN history H +ON C._id = H.chapter_id +WHERE M.favorite = 1 +GROUP BY M._id +ORDER BY max ASC; + +getLatestByChapterUploadDate: +SELECT M.*, MAX(C.date_upload) AS max +FROM mangas M +JOIN chapters C +ON M._id = C.manga_id +GROUP BY M._id +ORDER BY max ASC; + +getLatestByChapterFetchDate: +SELECT M.*, MAX(C.date_fetch) AS max +FROM mangas M +JOIN chapters C +ON M._id = C.manga_id +GROUP BY M._id +ORDER BY max ASC; + deleteMangasNotInLibraryBySourceIds: DELETE FROM mangas WHERE favorite = 0 AND source IN :sourceIds;