Use SQLDelight for all Manga related queries (#7447)

This commit is contained in:
Andreas 2022-07-03 16:17:41 +02:00 committed by GitHub
parent 6d6237e370
commit 17951cfd68
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
42 changed files with 343 additions and 753 deletions

View file

@ -165,6 +165,7 @@ dependencies {
implementation(androidx.paging.runtime)
implementation(androidx.paging.compose)
implementation(libs.bundles.sqlite)
implementation(androidx.sqlite)
implementation(libs.sqldelight.android.driver)
implementation(libs.sqldelight.coroutines)
@ -218,11 +219,6 @@ dependencies {
implementation(libs.unifile)
implementation(libs.junrar)
// Database
implementation(libs.bundles.sqlite)
implementation("com.github.inorichi.storio:storio-common:8be19de@aar")
implementation("com.github.inorichi.storio:storio-sqlite:8be19de@aar")
// Preferences
implementation(libs.preferencektx)
implementation(libs.flowpreferences)

View file

@ -2,6 +2,7 @@ package eu.kanade.data.manga
import eu.kanade.domain.chapter.model.Chapter
import eu.kanade.domain.manga.model.Manga
import eu.kanade.tachiyomi.data.database.models.LibraryManga
val mangaMapper: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long) -> Manga =
{ id, source, url, artist, author, description, genre, title, status, thumbnailUrl, favorite, lastUpdate, _, initialized, viewer, chapterFlags, coverLastModified, dateAdded ->
@ -61,3 +62,29 @@ val mangaChapterMapper: (Long, Long, String, String?, String?, String?, List<Str
scanlator = scanlator,
)
}
val libraryManga: (Long, Long, String, String?, String?, String?, List<String>?, String, Long, String?, Boolean, Long?, Long?, Boolean, Long, Long, Long, Long, Long, Long, Long) -> LibraryManga =
{ _id, source, url, artist, author, description, genre, title, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, unread_count, read_count, category ->
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()
}
}

View file

@ -6,6 +6,7 @@ import eu.kanade.data.toLong
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.Flow
import logcat.LogPriority
@ -18,18 +19,26 @@ class MangaRepositoryImpl(
return handler.awaitOne { mangasQueries.getMangaById(id, mangaMapper) }
}
override suspend fun subscribeMangaById(id: Long): Flow<Manga> {
return handler.subscribeToOne { mangasQueries.getMangaById(id, mangaMapper) }
}
override suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga> {
return handler.subscribeToOne { mangasQueries.getMangaById(id, mangaMapper) }
}
override suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga? {
return handler.awaitOneOrNull { mangasQueries.getMangaByUrlAndSource(url, sourceId, mangaMapper) }
}
override suspend fun getFavorites(): List<Manga> {
return handler.awaitList { mangasQueries.getFavorites(mangaMapper) }
}
override suspend fun getLibraryManga(): List<LibraryManga> {
return handler.awaitList { mangasQueries.getLibrary(libraryManga) }
}
override fun getLibraryMangaAsFlow(): Flow<List<LibraryManga>> {
return handler.subscribeToList { mangasQueries.getLibrary(libraryManga) }
}
override fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>> {
return handler.subscribeToList { mangasQueries.getFavoriteBySourceId(sourceId, mangaMapper) }
}
@ -59,6 +68,31 @@ class MangaRepositoryImpl(
}
}
override suspend fun insert(manga: Manga): Long? {
return handler.awaitOneOrNull {
mangasQueries.insert(
source = manga.source,
url = manga.url,
artist = manga.artist,
author = manga.author,
description = manga.description,
genre = manga.genre,
title = manga.title,
status = manga.status,
thumbnail_url = manga.thumbnailUrl,
favorite = manga.favorite,
last_update = manga.lastUpdate,
next_update = null,
initialized = manga.initialized,
viewer = manga.viewerFlags,
chapter_flags = manga.chapterFlags,
cover_last_modified = manga.coverLastModified,
date_added = manga.dateAdded,
)
mangasQueries.selectLastInsertedRowId()
}
}
override suspend fun update(update: MangaUpdate): Boolean {
return try {
partialUpdate(update)

View file

@ -32,10 +32,13 @@ import eu.kanade.domain.history.interactor.UpsertHistory
import eu.kanade.domain.history.repository.HistoryRepository
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetLibraryManga
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.interactor.ResetViewerFlags
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.domain.source.interactor.GetEnabledSources
@ -71,11 +74,14 @@ class DomainModule : InjektModule {
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetDuplicateLibraryManga(get()) }
addFactory { GetFavorites(get()) }
addFactory { GetLibraryManga(get()) }
addFactory { GetMangaWithChapters(get(), get()) }
addFactory { GetMangaById(get()) }
addFactory { GetManga(get()) }
addFactory { GetNextChapter(get()) }
addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) }
addFactory { SetMangaViewerFlags(get()) }
addFactory { InsertManga(get()) }
addFactory { UpdateManga(get()) }
addFactory { SetMangaCategories(get()) }

View file

@ -0,0 +1,18 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import kotlinx.coroutines.flow.Flow
class GetLibraryManga(
private val mangaRepository: MangaRepository,
) {
suspend fun await(): List<LibraryManga> {
return mangaRepository.getLibraryManga()
}
fun subscribe(): Flow<List<LibraryManga>> {
return mangaRepository.getLibraryMangaAsFlow()
}
}

View file

@ -6,7 +6,7 @@ import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.Flow
import logcat.LogPriority
class GetMangaById(
class GetManga(
private val mangaRepository: MangaRepository,
) {
@ -20,6 +20,10 @@ class GetMangaById(
}
suspend fun subscribe(id: Long): Flow<Manga> {
return mangaRepository.subscribeMangaById(id)
return mangaRepository.getMangaByIdAsFlow(id)
}
suspend fun await(url: String, sourceId: Long): Manga? {
return mangaRepository.getMangaByUrlAndSourceId(url, sourceId)
}
}

View file

@ -14,7 +14,7 @@ class GetMangaWithChapters(
suspend fun subscribe(id: Long): Flow<Pair<Manga, List<Chapter>>> {
return combine(
mangaRepository.subscribeMangaById(id),
mangaRepository.getMangaByIdAsFlow(id),
chapterRepository.getChapterByMangaIdAsFlow(id),
) { manga, chapters ->
Pair(manga, chapters)

View file

@ -0,0 +1,13 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.repository.MangaRepository
class InsertManga(
private val mangaRepository: MangaRepository,
) {
suspend fun await(manga: Manga): Long? {
return mangaRepository.insert(manga)
}
}

View file

@ -0,0 +1,33 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.ui.reader.setting.OrientationType
import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
class SetMangaViewerFlags(
private val mangaRepository: MangaRepository,
) {
suspend fun awaitSetMangaReadingMode(id: Long, flag: Long) {
mangaRepository.update(
MangaUpdate(
id = id,
viewerFlags = flag.setFlag(flag, ReadingModeType.MASK.toLong()),
),
)
}
suspend fun awaitSetOrientationType(id: Long, flag: Long) {
mangaRepository.update(
MangaUpdate(
id = id,
viewerFlags = flag.setFlag(flag, OrientationType.MASK.toLong()),
),
)
}
private fun Long.setFlag(flag: Long, mask: Long): Long {
return this and mask.inv() or (flag and mask)
}
}

View file

@ -175,6 +175,28 @@ fun Manga.toMangaInfo(): MangaInfo = MangaInfo(
title = title,
)
fun Manga.toMangaUpdate(): MangaUpdate {
return MangaUpdate(
id = id,
source = source,
favorite = favorite,
lastUpdate = lastUpdate,
dateAdded = dateAdded,
viewerFlags = viewerFlags,
chapterFlags = chapterFlags,
coverLastModified = coverLastModified,
url = url,
title = title,
artist = artist,
author = author,
description = description,
genre = genre,
status = status,
thumbnailUrl = thumbnailUrl,
initialized = initialized,
)
}
fun Manga.isLocal(): Boolean = source == LocalSource.ID
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {

View file

@ -2,18 +2,23 @@ package eu.kanade.domain.manga.repository
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import kotlinx.coroutines.flow.Flow
interface MangaRepository {
suspend fun getMangaById(id: Long): Manga
suspend fun subscribeMangaById(id: Long): Flow<Manga>
suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga>
suspend fun getMangaByUrlAndSourceId(url: String, sourceId: Long): Manga?
suspend fun getFavorites(): List<Manga>
suspend fun getLibraryManga(): List<LibraryManga>
fun getLibraryMangaAsFlow(): Flow<List<LibraryManga>>
fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>>
suspend fun getDuplicateLibraryManga(title: String, sourceId: Long): Manga?
@ -22,6 +27,8 @@ interface MangaRepository {
suspend fun setMangaCategories(mangaId: Long, categoryIds: List<Long>)
suspend fun insert(manga: Manga): Long?
suspend fun update(update: MangaUpdate): Boolean
suspend fun updateAll(values: List<MangaUpdate>): Boolean

View file

@ -15,7 +15,6 @@ import eu.kanade.data.dateAdapter
import eu.kanade.data.listOfStringsAdapter
import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.DbOpenCallback
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -76,8 +75,6 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { PreferencesHelper(app) }
addSingletonFactory { DatabaseHelper(get()) }
addSingletonFactory { ChapterCache(app) }
addSingletonFactory { CoverCache(app) }
@ -106,8 +103,6 @@ class AppModule(val app: Application) : InjektModule {
get<Database>()
get<DatabaseHelper>()
get<DownloadManager>()
}
}

View file

@ -1,27 +0,0 @@
package eu.kanade.tachiyomi.data.database
import androidx.sqlite.db.SupportSQLiteOpenHelper
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
import eu.kanade.tachiyomi.data.database.mappers.ChapterTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.MangaCategoryTypeMapping
import eu.kanade.tachiyomi.data.database.mappers.MangaTypeMapping
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.queries.MangaQueries
/**
* This class provides operations to manage the database through its interfaces.
*/
class DatabaseHelper(
openHelper: SupportSQLiteOpenHelper,
) :
MangaQueries {
override val db = DefaultStorIOSQLite.builder()
.sqliteOpenHelper(openHelper)
.addTypeMapping(Manga::class.java, MangaTypeMapping())
.addTypeMapping(Chapter::class.java, ChapterTypeMapping())
.addTypeMapping(MangaCategory::class.java, MangaCategoryTypeMapping())
.build()
}

View file

@ -1,24 +0,0 @@
package eu.kanade.tachiyomi.data.database
import com.pushtorefresh.storio.sqlite.StorIOSQLite
inline fun StorIOSQLite.inTransaction(block: () -> Unit) {
lowLevel().beginTransaction()
try {
block()
lowLevel().setTransactionSuccessful()
} finally {
lowLevel().endTransaction()
}
}
inline fun <T> StorIOSQLite.inTransactionReturn(block: () -> T): T {
lowLevel().beginTransaction()
try {
val result = block()
lowLevel().setTransactionSuccessful()
return result
} finally {
lowLevel().endTransaction()
}
}

View file

@ -1,7 +0,0 @@
package eu.kanade.tachiyomi.data.database
import com.pushtorefresh.storio.sqlite.impl.DefaultStorIOSQLite
interface DbProvider {
val db: DefaultStorIOSQLite
}

View file

@ -1,88 +0,0 @@
package eu.kanade.tachiyomi.data.database.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.ChapterImpl
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_BOOKMARK
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_CHAPTER_NUMBER
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_DATE_FETCH
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_DATE_UPLOAD
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_LAST_PAGE_READ
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_MANGA_ID
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_NAME
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_READ
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_SCANLATOR
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_SOURCE_ORDER
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.COL_URL
import eu.kanade.tachiyomi.data.database.tables.ChapterTable.TABLE
class ChapterTypeMapping : SQLiteTypeMapping<Chapter>(
ChapterPutResolver(),
ChapterGetResolver(),
ChapterDeleteResolver(),
)
class ChapterPutResolver : DefaultPutResolver<Chapter>() {
override fun mapToInsertQuery(obj: Chapter) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: Chapter) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: Chapter) =
contentValuesOf(
COL_ID to obj.id,
COL_MANGA_ID to obj.manga_id,
COL_URL to obj.url,
COL_NAME to obj.name,
COL_READ to obj.read,
COL_SCANLATOR to obj.scanlator,
COL_BOOKMARK to obj.bookmark,
COL_DATE_FETCH to obj.date_fetch,
COL_DATE_UPLOAD to obj.date_upload,
COL_LAST_PAGE_READ to obj.last_page_read,
COL_CHAPTER_NUMBER to obj.chapter_number,
COL_SOURCE_ORDER to obj.source_order,
)
}
class ChapterGetResolver : DefaultGetResolver<Chapter>() {
override fun mapFromCursor(cursor: Cursor): Chapter = ChapterImpl().apply {
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
url = cursor.getString(cursor.getColumnIndexOrThrow(COL_URL))
name = cursor.getString(cursor.getColumnIndexOrThrow(COL_NAME))
scanlator = cursor.getString(cursor.getColumnIndexOrThrow(COL_SCANLATOR))
read = cursor.getInt(cursor.getColumnIndexOrThrow(COL_READ)) == 1
bookmark = cursor.getInt(cursor.getColumnIndexOrThrow(COL_BOOKMARK)) == 1
date_fetch = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_FETCH))
date_upload = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_UPLOAD))
last_page_read = cursor.getInt(cursor.getColumnIndexOrThrow(COL_LAST_PAGE_READ))
chapter_number = cursor.getFloat(cursor.getColumnIndexOrThrow(COL_CHAPTER_NUMBER))
source_order = cursor.getInt(cursor.getColumnIndexOrThrow(COL_SOURCE_ORDER))
}
}
class ChapterDeleteResolver : DefaultDeleteResolver<Chapter>() {
override fun mapToDeleteQuery(obj: Chapter) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View file

@ -1,60 +0,0 @@
package eu.kanade.tachiyomi.data.database.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_CATEGORY_ID
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.COL_MANGA_ID
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable.TABLE
class MangaCategoryTypeMapping : SQLiteTypeMapping<MangaCategory>(
MangaCategoryPutResolver(),
MangaCategoryGetResolver(),
MangaCategoryDeleteResolver(),
)
class MangaCategoryPutResolver : DefaultPutResolver<MangaCategory>() {
override fun mapToInsertQuery(obj: MangaCategory) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: MangaCategory) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: MangaCategory) =
contentValuesOf(
COL_ID to obj.id,
COL_MANGA_ID to obj.manga_id,
COL_CATEGORY_ID to obj.category_id,
)
}
class MangaCategoryGetResolver : DefaultGetResolver<MangaCategory>() {
override fun mapFromCursor(cursor: Cursor): MangaCategory = MangaCategory().apply {
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
manga_id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_MANGA_ID))
category_id = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CATEGORY_ID))
}
}
class MangaCategoryDeleteResolver : DefaultDeleteResolver<MangaCategory>() {
override fun mapToDeleteQuery(obj: MangaCategory) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View file

@ -1,109 +0,0 @@
package eu.kanade.tachiyomi.data.database.mappers
import android.database.Cursor
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.SQLiteTypeMapping
import com.pushtorefresh.storio.sqlite.operations.delete.DefaultDeleteResolver
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import com.pushtorefresh.storio.sqlite.operations.put.DefaultPutResolver
import com.pushtorefresh.storio.sqlite.queries.DeleteQuery
import com.pushtorefresh.storio.sqlite.queries.InsertQuery
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.MangaImpl
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ARTIST
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_AUTHOR
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_CHAPTER_FLAGS
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_COVER_LAST_MODIFIED
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DATE_ADDED
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_DESCRIPTION
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_FAVORITE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_GENRE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_ID
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_INITIALIZED
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_LAST_UPDATE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_SOURCE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_STATUS
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_THUMBNAIL_URL
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_TITLE
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_URL
import eu.kanade.tachiyomi.data.database.tables.MangaTable.COL_VIEWER
import eu.kanade.tachiyomi.data.database.tables.MangaTable.TABLE
class MangaTypeMapping : SQLiteTypeMapping<Manga>(
MangaPutResolver(),
MangaGetResolver(),
MangaDeleteResolver(),
)
class MangaPutResolver : DefaultPutResolver<Manga>() {
override fun mapToInsertQuery(obj: Manga) = InsertQuery.builder()
.table(TABLE)
.build()
override fun mapToUpdateQuery(obj: Manga) = UpdateQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
override fun mapToContentValues(obj: Manga) =
contentValuesOf(
COL_ID to obj.id,
COL_SOURCE to obj.source,
COL_URL to obj.url,
COL_ARTIST to obj.artist,
COL_AUTHOR to obj.author,
COL_DESCRIPTION to obj.description,
COL_GENRE to obj.genre,
COL_TITLE to obj.title,
COL_STATUS to obj.status,
COL_THUMBNAIL_URL to obj.thumbnail_url,
COL_FAVORITE to obj.favorite,
COL_LAST_UPDATE to obj.last_update,
COL_INITIALIZED to obj.initialized,
COL_VIEWER to obj.viewer_flags,
COL_CHAPTER_FLAGS to obj.chapter_flags,
COL_COVER_LAST_MODIFIED to obj.cover_last_modified,
COL_DATE_ADDED to obj.date_added,
)
}
interface BaseMangaGetResolver {
fun mapBaseFromCursor(manga: Manga, cursor: Cursor) = manga.apply {
id = cursor.getLong(cursor.getColumnIndexOrThrow(COL_ID))
source = cursor.getLong(cursor.getColumnIndexOrThrow(COL_SOURCE))
url = cursor.getString(cursor.getColumnIndexOrThrow(COL_URL))
artist = cursor.getString(cursor.getColumnIndexOrThrow(COL_ARTIST))
author = cursor.getString(cursor.getColumnIndexOrThrow(COL_AUTHOR))
description = cursor.getString(cursor.getColumnIndexOrThrow(COL_DESCRIPTION))
genre = cursor.getString(cursor.getColumnIndexOrThrow(COL_GENRE))
title = cursor.getString(cursor.getColumnIndexOrThrow(COL_TITLE))
status = cursor.getInt(cursor.getColumnIndexOrThrow(COL_STATUS))
thumbnail_url = cursor.getString(cursor.getColumnIndexOrThrow(COL_THUMBNAIL_URL))
favorite = cursor.getInt(cursor.getColumnIndexOrThrow(COL_FAVORITE)) == 1
last_update = cursor.getLong(cursor.getColumnIndexOrThrow(COL_LAST_UPDATE))
initialized = cursor.getInt(cursor.getColumnIndexOrThrow(COL_INITIALIZED)) == 1
viewer_flags = cursor.getInt(cursor.getColumnIndexOrThrow(COL_VIEWER))
chapter_flags = cursor.getInt(cursor.getColumnIndexOrThrow(COL_CHAPTER_FLAGS))
cover_last_modified = cursor.getLong(cursor.getColumnIndexOrThrow(COL_COVER_LAST_MODIFIED))
date_added = cursor.getLong(cursor.getColumnIndexOrThrow(COL_DATE_ADDED))
}
}
open class MangaGetResolver : DefaultGetResolver<Manga>(), BaseMangaGetResolver {
override fun mapFromCursor(cursor: Cursor): Manga {
return mapBaseFromCursor(MangaImpl(), cursor)
}
}
class MangaDeleteResolver : DefaultDeleteResolver<Manga>() {
override fun mapToDeleteQuery(obj: Manga) = DeleteQuery.builder()
.table(TABLE)
.where("$COL_ID = ?")
.whereArgs(obj.id)
.build()
}

View file

@ -1,77 +0,0 @@
package eu.kanade.tachiyomi.data.database.queries
import com.pushtorefresh.storio.sqlite.queries.Query
import com.pushtorefresh.storio.sqlite.queries.RawQuery
import eu.kanade.tachiyomi.data.database.DbProvider
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
import eu.kanade.tachiyomi.data.database.tables.MangaTable
interface MangaQueries : DbProvider {
fun getLibraryMangas() = db.get()
.listOfObjects(LibraryManga::class.java)
.withQuery(
RawQuery.builder()
.query(libraryQuery)
.observesTables(MangaTable.TABLE, ChapterTable.TABLE, MangaCategoryTable.TABLE, CategoryTable.TABLE)
.build(),
)
.withGetResolver(LibraryMangaGetResolver.INSTANCE)
.prepare()
fun getFavoriteMangas() = db.get()
.listOfObjects(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_FAVORITE} = ?")
.whereArgs(1)
.build(),
)
.prepare()
fun getManga(url: String, sourceId: Long) = db.get()
.`object`(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_URL} = ? AND ${MangaTable.COL_SOURCE} = ?")
.whereArgs(url, sourceId)
.build(),
)
.prepare()
fun getManga(id: Long) = db.get()
.`object`(Manga::class.java)
.withQuery(
Query.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(id)
.build(),
)
.prepare()
fun insertManga(manga: Manga) = db.put().`object`(manga).prepare()
fun updateChapterFlags(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags))
.prepare()
fun updateChapterFlags(manga: List<Manga>) = db.put()
.objects(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_CHAPTER_FLAGS, Manga::chapter_flags))
.prepare()
fun updateViewerFlags(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags))
.prepare()
}

View file

@ -1,37 +0,0 @@
package eu.kanade.tachiyomi.data.database.queries
import eu.kanade.tachiyomi.data.database.tables.ChapterTable as Chapter
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable as MangaCategory
import eu.kanade.tachiyomi.data.database.tables.MangaTable as Manga
/**
* Query to get the manga from the library, with their categories, read and unread count.
*/
val libraryQuery =
"""
SELECT M.*, COALESCE(MC.${MangaCategory.COL_CATEGORY_ID}, 0) AS ${Manga.COL_CATEGORY}
FROM (
SELECT ${Manga.TABLE}.*, COALESCE(C.unreadCount, 0) AS ${Manga.COMPUTED_COL_UNREAD_COUNT}, COALESCE(R.readCount, 0) AS ${Manga.COMPUTED_COL_READ_COUNT}
FROM ${Manga.TABLE}
LEFT JOIN (
SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS unreadCount
FROM ${Chapter.TABLE}
WHERE ${Chapter.COL_READ} = 0
GROUP BY ${Chapter.COL_MANGA_ID}
) AS C
ON ${Manga.COL_ID} = C.${Chapter.COL_MANGA_ID}
LEFT JOIN (
SELECT ${Chapter.COL_MANGA_ID}, COUNT(*) AS readCount
FROM ${Chapter.TABLE}
WHERE ${Chapter.COL_READ} = 1
GROUP BY ${Chapter.COL_MANGA_ID}
) AS R
ON ${Manga.COL_ID} = R.${Chapter.COL_MANGA_ID}
WHERE ${Manga.COL_FAVORITE} = 1
GROUP BY ${Manga.COL_ID}
ORDER BY ${Manga.COL_TITLE}
) AS M
LEFT JOIN (
SELECT * FROM ${MangaCategory.TABLE}) AS MC
ON MC.${MangaCategory.COL_MANGA_ID} = M.${Manga.COL_ID}
"""

View file

@ -1,34 +0,0 @@
package eu.kanade.tachiyomi.data.database.resolvers
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
class ChapterProgressPutResolver : PutResolver<Chapter>() {
override fun performPut(db: StorIOSQLite, chapter: Chapter) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(chapter)
val contentValues = mapToContentValues(chapter)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(chapter: Chapter) = UpdateQuery.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_ID} = ?")
.whereArgs(chapter.id)
.build()
fun mapToContentValues(chapter: Chapter) =
contentValuesOf(
ChapterTable.COL_READ to chapter.read,
ChapterTable.COL_BOOKMARK to chapter.bookmark,
ChapterTable.COL_LAST_PAGE_READ to chapter.last_page_read,
)
}

View file

@ -1,25 +0,0 @@
package eu.kanade.tachiyomi.data.database.resolvers
import android.database.Cursor
import com.pushtorefresh.storio.sqlite.operations.get.DefaultGetResolver
import eu.kanade.tachiyomi.data.database.mappers.BaseMangaGetResolver
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
class LibraryMangaGetResolver : DefaultGetResolver<LibraryManga>(), BaseMangaGetResolver {
companion object {
val INSTANCE = LibraryMangaGetResolver()
}
override fun mapFromCursor(cursor: Cursor): LibraryManga {
val manga = LibraryManga()
mapBaseFromCursor(manga, cursor)
manga.unreadCount = cursor.getInt(cursor.getColumnIndexOrThrow(MangaTable.COMPUTED_COL_UNREAD_COUNT))
manga.category = cursor.getInt(cursor.getColumnIndexOrThrow(MangaTable.COL_CATEGORY))
manga.readCount = cursor.getInt(cursor.getColumnIndexOrThrow(MangaTable.COMPUTED_COL_READ_COUNT))
return manga
}
}

View file

@ -1,33 +0,0 @@
package eu.kanade.tachiyomi.data.database.resolvers
import androidx.core.content.contentValuesOf
import com.pushtorefresh.storio.sqlite.StorIOSQLite
import com.pushtorefresh.storio.sqlite.operations.put.PutResolver
import com.pushtorefresh.storio.sqlite.operations.put.PutResult
import com.pushtorefresh.storio.sqlite.queries.UpdateQuery
import eu.kanade.tachiyomi.data.database.inTransactionReturn
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.tables.MangaTable
import kotlin.reflect.KProperty1
class MangaFlagsPutResolver(private val colName: String, private val fieldGetter: KProperty1<Manga, Int>) : PutResolver<Manga>() {
override fun performPut(db: StorIOSQLite, manga: Manga) = db.inTransactionReturn {
val updateQuery = mapToUpdateQuery(manga)
val contentValues = mapToContentValues(manga)
val numberOfRowsUpdated = db.lowLevel().update(updateQuery, contentValues)
PutResult.newUpdateResult(numberOfRowsUpdated, updateQuery.table())
}
fun mapToUpdateQuery(manga: Manga) = UpdateQuery.builder()
.table(MangaTable.TABLE)
.where("${MangaTable.COL_ID} = ?")
.whereArgs(manga.id)
.build()
fun mapToContentValues(manga: Manga) =
contentValuesOf(
colName to fieldGetter.get(manga),
)
}

View file

@ -1,6 +0,0 @@
package eu.kanade.tachiyomi.data.database.tables
object CategoryTable {
const val TABLE = "categories"
}

View file

@ -1,30 +0,0 @@
package eu.kanade.tachiyomi.data.database.tables
object ChapterTable {
const val TABLE = "chapters"
const val COL_ID = "_id"
const val COL_MANGA_ID = "manga_id"
const val COL_URL = "url"
const val COL_NAME = "name"
const val COL_READ = "read"
const val COL_SCANLATOR = "scanlator"
const val COL_BOOKMARK = "bookmark"
const val COL_DATE_FETCH = "date_fetch"
const val COL_DATE_UPLOAD = "date_upload"
const val COL_LAST_PAGE_READ = "last_page_read"
const val COL_CHAPTER_NUMBER = "chapter_number"
const val COL_SOURCE_ORDER = "source_order"
}

View file

@ -1,12 +0,0 @@
package eu.kanade.tachiyomi.data.database.tables
object MangaCategoryTable {
const val TABLE = "mangas_categories"
const val COL_ID = "_id"
const val COL_MANGA_ID = "manga_id"
const val COL_CATEGORY_ID = "category_id"
}

View file

@ -1,50 +0,0 @@
package eu.kanade.tachiyomi.data.database.tables
object MangaTable {
const val TABLE = "mangas"
const val COL_ID = "_id"
const val COL_SOURCE = "source"
const val COL_URL = "url"
const val COL_ARTIST = "artist"
const val COL_AUTHOR = "author"
const val COL_DESCRIPTION = "description"
const val COL_GENRE = "genre"
const val COL_TITLE = "title"
const val COL_STATUS = "status"
const val COL_THUMBNAIL_URL = "thumbnail_url"
const val COL_FAVORITE = "favorite"
const val COL_LAST_UPDATE = "last_update"
// Not actually used anymore
const val COL_NEXT_UPDATE = "next_update"
const val COL_DATE_ADDED = "date_added"
const val COL_INITIALIZED = "initialized"
const val COL_VIEWER = "viewer"
const val COL_CHAPTER_FLAGS = "chapter_flags"
const val COL_CATEGORY = "category"
const val COL_COVER_LAST_MODIFIED = "cover_last_modified"
// Not an actual value but computed when created
const val COMPUTED_COL_UNREAD_COUNT = "unread_count"
const val COMPUTED_COL_READ_COUNT = "read_count"
}

View file

@ -5,7 +5,6 @@ import com.hippo.unifile.UniFile
import com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download
@ -32,7 +31,6 @@ import uy.kohesive.injekt.injectLazy
*/
class DownloadManager(
private val context: Context,
private val db: DatabaseHelper = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
) {

View file

@ -4,7 +4,7 @@ import android.content.Context
import androidx.core.content.edit
import eu.kanade.domain.chapter.interactor.GetChapter
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download
@ -34,7 +34,7 @@ class DownloadStore(
private val json: Json by injectLazy()
private val getMangaById: GetMangaById by injectLazy()
private val getManga: GetManga by injectLazy()
private val getChapter: GetChapter by injectLazy()
/**
@ -96,7 +96,7 @@ class DownloadStore(
val cachedManga = mutableMapOf<Long, Manga?>()
for ((mangaId, chapterId) in objs) {
val manga = cachedManga.getOrPut(mangaId) {
runBlocking { getMangaById.await(mangaId)?.toDbManga() }
runBlocking { getManga.await(mangaId)?.toDbManga() }
} ?: continue
val source = sourceManager.get(manga.source) as? HttpSource ?: continue
val chapter = runBlocking { getChapter.await(chapterId) }?.toDbChapter() ?: continue

View file

@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.data.download.model
import eu.kanade.domain.chapter.interactor.GetChapter
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga
@ -69,11 +69,11 @@ data class Download(
suspend fun fromChapterId(
chapterId: Long,
getChapter: GetChapter = Injekt.get(),
getMangaById: GetMangaById = Injekt.get(),
getManga: GetManga = Injekt.get(),
sourceManager: SourceManager = Injekt.get(),
): Download? {
val chapter = getChapter.await(chapterId) ?: return null
val manga = getMangaById.await(chapter.mangaId) ?: return null
val manga = getManga.await(chapter.mangaId) ?: return null
val source = sourceManager.get(manga.source) as? HttpSource ?: return null
return Download(source, manga.toDbManga(), chapter.toDbChapter())

View file

@ -13,16 +13,17 @@ import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetLibraryManga
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toMangaInfo
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga
@ -61,6 +62,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit
@ -84,13 +86,13 @@ import eu.kanade.domain.manga.model.Manga as DomainManga
* destroyed.
*/
class LibraryUpdateService(
val db: DatabaseHelper = Injekt.get(),
val sourceManager: SourceManager = Injekt.get(),
val preferences: PreferencesHelper = Injekt.get(),
val downloadManager: DownloadManager = Injekt.get(),
val trackManager: TrackManager = Injekt.get(),
val coverCache: CoverCache = Injekt.get(),
private val getMangaById: GetMangaById = Injekt.get(),
private val getLibraryManga: GetLibraryManga = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
@ -255,7 +257,7 @@ class LibraryUpdateService(
* @param categoryId the ID of the category to update, or -1 if no category specified.
*/
fun addMangaToQueue(categoryId: Long) {
val libraryManga = db.getLibraryMangas().executeAsBlocking()
val libraryManga = runBlocking { getLibraryManga.await() }
val listToUpdate = if (categoryId != -1L) {
libraryManga.filter { it.category.toLong() == categoryId }
@ -323,7 +325,7 @@ class LibraryUpdateService(
}
// Don't continue to update if manga not in library
manga.id?.let { getMangaById.await(it) } ?: return@forEach
manga.id?.let { getManga.await(it) } ?: return@forEach
withUpdateNotification(
currentlyUpdatingManga,
@ -434,7 +436,7 @@ class LibraryUpdateService(
.map { it.toSChapter() }
// Get manga from database to account for if it was removed during the update
val dbManga = getMangaById.await(manga.id)
val dbManga = getManga.await(manga.id)
?: return Pair(emptyList(), emptyList())
// [dbmanga] was used so that manga data doesn't get overwritten
@ -471,7 +473,14 @@ class LibraryUpdateService(
mangaWithNotif.prepUpdateCover(coverCache, sManga, true)
sManga.thumbnail_url?.let {
mangaWithNotif.thumbnail_url = it
db.insertManga(mangaWithNotif).executeAsBlocking()
try {
updateManga.await(
mangaWithNotif.toDomainManga()!!
.toMangaUpdate(),
)
} catch (e: Exception) {
logcat(LogPriority.ERROR) { "Manga don't exist anymore" }
}
}
} catch (e: Throwable) {
// Ignore errors and continue

View file

@ -11,7 +11,7 @@ import eu.kanade.domain.chapter.interactor.GetChapter
import eu.kanade.domain.chapter.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.toChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupRestoreService
@ -46,7 +46,7 @@ import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
*/
class NotificationReceiver : BroadcastReceiver() {
private val getMangaById: GetMangaById by injectLazy()
private val getManga: GetManga by injectLazy()
private val getChapter: GetChapter by injectLazy()
private val updateChapter: UpdateChapter by injectLazy()
private val downloadManager: DownloadManager by injectLazy()
@ -178,7 +178,7 @@ class NotificationReceiver : BroadcastReceiver() {
* @param chapterId id of chapter
*/
private fun openChapter(context: Context, mangaId: Long, chapterId: Long) {
val manga = runBlocking { getMangaById.await(mangaId) }
val manga = runBlocking { getManga.await(mangaId) }
val chapter = runBlocking { getChapter.await(chapterId) }
if (manga != null && chapter != null) {
val intent = ReaderActivity.newIntent(context, manga.id, chapter.id).apply {
@ -248,7 +248,7 @@ class NotificationReceiver : BroadcastReceiver() {
.map {
val chapter = it.copy(read = true)
if (preferences.removeAfterMarkedAsRead()) {
val manga = getMangaById.await(mangaId)
val manga = getManga.await(mangaId)
if (manga != null) {
val source = sourceManager.get(manga.source)
if (source != null) {
@ -270,7 +270,7 @@ class NotificationReceiver : BroadcastReceiver() {
*/
private fun downloadChapters(chapterUrls: Array<String>, mangaId: Long) {
launchIO {
val manga = getMangaById.await(mangaId)?.toDbManga()
val manga = getManga.await(mangaId)?.toDbManga()
val chapters = chapterUrls.mapNotNull { getChapter.await(it, mangaId)?.toDbChapter() }
if (manga != null && chapters.isNotEmpty()) {
downloadManager.downloadChapters(manga, chapters)

View file

@ -9,7 +9,7 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack
@ -26,7 +26,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result {
val getMangaById = Injekt.get<GetMangaById>()
val getManga = Injekt.get<GetManga>()
val getTracks = Injekt.get<GetTracks>()
val insertTrack = Injekt.get<InsertTrack>()
@ -35,7 +35,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
withContext(Dispatchers.IO) {
val tracks = delayedTrackingStore.getItems().mapNotNull {
val manga = getMangaById.await(it.mangaId) ?: return@withContext
val manga = getManga.await(it.mangaId) ?: return@withContext
getTracks.await(manga.id)
.find { track -> track.id == it.trackId }
?.copy(lastChapterRead = it.lastChapterRead.toDouble())

View file

@ -6,8 +6,9 @@ import androidx.core.view.isVisible
import com.bluelinelabs.conductor.Controller
import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -18,6 +19,7 @@ import eu.kanade.tachiyomi.ui.browse.migration.MigrationFlags
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
import eu.kanade.tachiyomi.ui.manga.MangaController
import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
@ -27,9 +29,11 @@ class SearchController(
) : GlobalSearchController(manga?.title) {
constructor(mangaId: Long) : this(
Injekt.get<DatabaseHelper>()
.getManga(mangaId)
.executeAsBlocking(),
runBlocking {
Injekt.get<GetManga>()
.await(mangaId)
?.toDbManga()
},
)
private var newManga: Manga? = null

View file

@ -7,11 +7,14 @@ import eu.kanade.domain.category.interactor.SetMangaCategories
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
@ -67,13 +70,15 @@ open class BrowseSourcePresenter(
private val sourceId: Long,
searchQuery: String? = null,
private val sourceManager: SourceManager = Injekt.get(),
private val db: DatabaseHelper = Injekt.get(),
private val prefs: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val setMangaCategories: SetMangaCategories = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(),
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
) : BasePresenter<BrowseSourceController>() {
@ -208,19 +213,22 @@ open class BrowseSourcePresenter(
* @return a manga from the database.
*/
private fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking()
var localManga = runBlocking { getManga.await(sManga.url, sourceId) }
if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga)
val result = db.insertManga(newManga).executeAsBlocking()
newManga.id = result.insertedId()
localManga = newManga
newManga.id = -1
val result = runBlocking {
val id = insertManga.await(newManga.toDomainManga()!!)
getManga.await(id!!)
}
localManga = result
} else if (!localManga.favorite) {
// if the manga isn't a favorite, set its display title from source
// if it later becomes a favorite, updated title will go to db
localManga.title = sManga.title
localManga = localManga.copy(title = sManga.title)
}
return localManga
return localManga?.toDbManga()!!
}
/**
@ -255,7 +263,11 @@ open class BrowseSourcePresenter(
val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga())
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
updateManga.await(
manga
.toDomainManga()
?.toMangaUpdate()!!,
)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
@ -282,7 +294,13 @@ open class BrowseSourcePresenter(
autoAddTrack(manga)
}
db.insertManga(manga).executeAsBlocking()
runBlocking {
updateManga.await(
manga
.toDomainManga()
?.toMangaUpdate()!!,
)
}
}
private fun autoAddTrack(manga: Manga) {

View file

@ -1,8 +1,13 @@
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
import android.os.Bundle
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.ExtensionManager
@ -16,6 +21,7 @@ import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.runBlocking
import logcat.LogPriority
import rx.Observable
import rx.Subscription
@ -38,8 +44,10 @@ open class GlobalSearchPresenter(
private val initialQuery: String? = "",
private val initialExtensionFilter: String? = null,
val sourceManager: SourceManager = Injekt.get(),
val db: DatabaseHelper = Injekt.get(),
val preferences: PreferencesHelper = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val insertManga: InsertManga = Injekt.get(),
private val updateManga: UpdateManga = Injekt.get(),
) : BasePresenter<GlobalSearchController>() {
/**
@ -248,7 +256,7 @@ open class GlobalSearchPresenter(
val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga())
manga.initialized = true
db.insertManga(manga).executeAsBlocking()
runBlocking { updateManga.await(manga.toDomainManga()!!.toMangaUpdate()) }
return manga
}
@ -260,18 +268,21 @@ open class GlobalSearchPresenter(
* @return a manga from the database.
*/
protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga {
var localManga = db.getManga(sManga.url, sourceId).executeAsBlocking()
var localManga = runBlocking { getManga.await(sManga.url, sourceId) }
if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga)
val result = db.insertManga(newManga).executeAsBlocking()
newManga.id = result.insertedId()
localManga = newManga
newManga.id = -1
val result = runBlocking {
val id = insertManga.await(newManga.toDomainManga()!!)
getManga.await(id!!)
}
localManga = result
} else if (!localManga.favorite) {
// if the manga isn't a favorite, set its display title from source
// if it later becomes a favorite, updated title will go to db
localManga.title = sManga.title
localManga = localManga.copy(title = sManga.title)
}
return localManga
return localManga!!.toDbManga()
}
}

View file

@ -11,13 +11,13 @@ 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.GetLibraryManga
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
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.models.Chapter
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager
@ -60,6 +60,7 @@ typealias LibraryMap = Map<Long, List<LibraryItem>>
*/
class LibraryPresenter(
private val handler: DatabaseHandler = Injekt.get(),
private val getLibraryManga: GetLibraryManga = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
@ -410,35 +411,7 @@ class LibraryPresenter(
val defaultLibraryDisplayMode = preferences.libraryDisplayMode()
val shouldSetFromCategory = preferences.categorizedDisplaySettings()
// 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<String>?, 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()
return getLibraryManga.subscribe().asObservable()
.map { list ->
list.map { libraryManga ->
// Display mode based on user preference: take it from global library setting or category

View file

@ -149,7 +149,7 @@ class MangaPresenter(
presenterScope.launchIO {
if (!getMangaAndChapters.awaitManga(mangaId).favorite) {
ChapterSettingsHelper.applySettingDefaults(mangaId, setMangaChapterFlags)
ChapterSettingsHelper.applySettingDefaults(mangaId)
}
getMangaAndChapters.subscribe(mangaId)

View file

@ -20,7 +20,7 @@ import androidx.core.os.bundleOf
import coil.imageLoader
import coil.request.ImageRequest
import coil.size.Size
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.hasCustomCover
@ -161,7 +161,7 @@ class MangaFullCoverDialog : FullComposeController<MangaFullCoverDialog.MangaFul
inner class MangaFullCoverPresenter(
private val mangaId: Long,
private val getMangaById: GetMangaById = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
) : Presenter<MangaFullCoverDialog>() {
private var presenterScope: CoroutineScope = MainScope()
@ -176,7 +176,7 @@ class MangaFullCoverDialog : FullComposeController<MangaFullCoverDialog.MangaFul
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
presenterScope.launchIO {
getMangaById.subscribe(mangaId)
getManga.subscribe(mangaId)
.collect { _mangaFlow.value = it }
}
}

View file

@ -11,13 +11,13 @@ import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.history.interactor.UpsertHistory
import eu.kanade.domain.history.model.HistoryUpdate
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.GetManga
import eu.kanade.domain.manga.interactor.SetMangaViewerFlags
import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.download.DownloadManager
@ -68,17 +68,17 @@ import java.util.concurrent.TimeUnit
* Presenter used by the activity to perform background operations.
*/
class ReaderPresenter(
private val db: DatabaseHelper = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get(),
private val delayedTrackingStore: DelayedTrackingStore = Injekt.get(),
private val getMangaById: GetMangaById = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(),
private val upsertHistory: UpsertHistory = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(),
private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
) : BasePresenter<ReaderActivity>() {
/**
@ -242,7 +242,7 @@ class ReaderPresenter(
launchIO {
try {
val manga = getMangaById.await(mangaId)
val manga = getManga.await(mangaId)
withUIContext {
manga?.let { init(it.toDbManga(), initialChapterId) }
}
@ -570,7 +570,9 @@ class ReaderPresenter(
fun setMangaReadingMode(readingModeType: Int) {
val manga = manga ?: return
manga.readingModeType = readingModeType
db.updateViewerFlags(manga).executeAsBlocking()
runBlocking {
setMangaViewerFlags.awaitSetMangaReadingMode(manga.id!!.toLong(), readingModeType.toLong())
}
Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ ->
@ -605,7 +607,9 @@ class ReaderPresenter(
fun setMangaOrientationType(rotationType: Int) {
val manga = manga ?: return
manga.orientationType = rotationType
db.updateViewerFlags(manga).executeAsBlocking()
runBlocking {
setMangaViewerFlags.awaitSetOrientationType(manga.id!!.toLong(), rotationType.toLong())
}
logcat(LogPriority.INFO) { "Manga orientation is ${manga.orientationType}" }

View file

@ -1,7 +1,7 @@
package eu.kanade.tachiyomi.util.chapter
import eu.kanade.domain.manga.interactor.GetFavorites
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.util.lang.launchIO
@ -10,7 +10,8 @@ import uy.kohesive.injekt.injectLazy
object ChapterSettingsHelper {
private val prefs: PreferencesHelper by injectLazy()
private val db: DatabaseHelper by injectLazy()
private val getFavorites: GetFavorites by injectLazy()
private val setMangaChapterFlags: SetMangaChapterFlags by injectLazy()
/**
* Updates the global Chapter Settings in Preferences.
@ -23,19 +24,20 @@ object ChapterSettingsHelper {
* Updates a single manga's Chapter Settings to match what's set in Preferences.
*/
fun applySettingDefaults(manga: Manga) {
with(manga) {
readFilter = prefs.filterChapterByRead()
downloadedFilter = prefs.filterChapterByDownloaded()
bookmarkedFilter = prefs.filterChapterByBookmarked()
sorting = prefs.sortChapterBySourceOrNumber()
displayMode = prefs.displayChapterByNameOrNumber()
setChapterOrder(prefs.sortChapterByAscendingOrDescending())
launchIO {
setMangaChapterFlags.awaitSetAllFlags(
mangaId = manga.id!!,
unreadFilter = prefs.filterChapterByRead().toLong(),
downloadedFilter = prefs.filterChapterByDownloaded().toLong(),
bookmarkedFilter = prefs.filterChapterByBookmarked().toLong(),
sortingMode = prefs.sortChapterBySourceOrNumber().toLong(),
sortingDirection = prefs.sortChapterByAscendingOrDescending().toLong(),
displayMode = prefs.displayChapterByNameOrNumber().toLong(),
)
}
db.updateChapterFlags(manga).executeAsBlocking()
}
suspend fun applySettingDefaults(mangaId: Long, setMangaChapterFlags: SetMangaChapterFlags) {
suspend fun applySettingDefaults(mangaId: Long) {
setMangaChapterFlags.awaitSetAllFlags(
mangaId = mangaId,
unreadFilter = prefs.filterChapterByRead().toLong(),
@ -52,21 +54,18 @@ object ChapterSettingsHelper {
*/
fun updateAllMangasWithGlobalDefaults() {
launchIO {
val updatedMangas = db.getFavoriteMangas()
.executeAsBlocking()
getFavorites.await()
.map { manga ->
with(manga) {
readFilter = prefs.filterChapterByRead()
downloadedFilter = prefs.filterChapterByDownloaded()
bookmarkedFilter = prefs.filterChapterByBookmarked()
sorting = prefs.sortChapterBySourceOrNumber()
displayMode = prefs.displayChapterByNameOrNumber()
setChapterOrder(prefs.sortChapterByAscendingOrDescending())
}
manga
setMangaChapterFlags.awaitSetAllFlags(
mangaId = manga.id,
unreadFilter = prefs.filterChapterByRead().toLong(),
downloadedFilter = prefs.filterChapterByDownloaded().toLong(),
bookmarkedFilter = prefs.filterChapterByBookmarked().toLong(),
sortingMode = prefs.sortChapterBySourceOrNumber().toLong(),
sortingDirection = prefs.sortChapterByAscendingOrDescending().toLong(),
displayMode = prefs.displayChapterByNameOrNumber().toLong(),
)
}
db.updateChapterFlags(updatedMangas).executeAsBlocking()
}
}
}

View file

@ -145,6 +145,44 @@ deleteMangasNotInLibraryBySourceIds:
DELETE FROM mangas
WHERE favorite = 0 AND source IN :sourceIds;
INSERT INTO mangas(
source,
url,
artist,
author,
description,
genre,
title,
status,
thumbnail_url,
favorite,
last_update,
next_update,
initialized,
viewer,
chapter_flags,
cover_last_modified,
date_added
) VALUES (
:source,
:url,
:artist,
:author,
:description,
:genre,
:title,
:status,
:thumbnailUrl,
:favorite,
:lastUpdate,
0,
:initialized,
:viewerFlags,
:chapterFlags,
:coverLastModified,
:dateAdded
);
update:
UPDATE mangas SET
source = coalesce(:source, source),