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.runtime)
implementation(androidx.paging.compose) implementation(androidx.paging.compose)
implementation(libs.bundles.sqlite)
implementation(androidx.sqlite) implementation(androidx.sqlite)
implementation(libs.sqldelight.android.driver) implementation(libs.sqldelight.android.driver)
implementation(libs.sqldelight.coroutines) implementation(libs.sqldelight.coroutines)
@ -218,11 +219,6 @@ dependencies {
implementation(libs.unifile) implementation(libs.unifile)
implementation(libs.junrar) 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 // Preferences
implementation(libs.preferencektx) implementation(libs.preferencektx)
implementation(libs.flowpreferences) 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.chapter.model.Chapter
import eu.kanade.domain.manga.model.Manga 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 = 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 -> { 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, 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.Manga
import eu.kanade.domain.manga.model.MangaUpdate import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.repository.MangaRepository import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import logcat.LogPriority import logcat.LogPriority
@ -18,18 +19,26 @@ class MangaRepositoryImpl(
return handler.awaitOne { mangasQueries.getMangaById(id, mangaMapper) } 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> { override suspend fun getMangaByIdAsFlow(id: Long): Flow<Manga> {
return handler.subscribeToOne { mangasQueries.getMangaById(id, mangaMapper) } 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> { override suspend fun getFavorites(): List<Manga> {
return handler.awaitList { mangasQueries.getFavorites(mangaMapper) } 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>> { override fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>> {
return handler.subscribeToList { mangasQueries.getFavoriteBySourceId(sourceId, mangaMapper) } 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 { override suspend fun update(update: MangaUpdate): Boolean {
return try { return try {
partialUpdate(update) 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.history.repository.HistoryRepository
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga
import eu.kanade.domain.manga.interactor.GetFavorites 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.GetMangaWithChapters
import eu.kanade.domain.manga.interactor.InsertManga
import eu.kanade.domain.manga.interactor.ResetViewerFlags import eu.kanade.domain.manga.interactor.ResetViewerFlags
import eu.kanade.domain.manga.interactor.SetMangaChapterFlags 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.interactor.UpdateManga
import eu.kanade.domain.manga.repository.MangaRepository import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.domain.source.interactor.GetEnabledSources import eu.kanade.domain.source.interactor.GetEnabledSources
@ -71,11 +74,14 @@ class DomainModule : InjektModule {
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) } addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetDuplicateLibraryManga(get()) } addFactory { GetDuplicateLibraryManga(get()) }
addFactory { GetFavorites(get()) } addFactory { GetFavorites(get()) }
addFactory { GetLibraryManga(get()) }
addFactory { GetMangaWithChapters(get(), get()) } addFactory { GetMangaWithChapters(get(), get()) }
addFactory { GetMangaById(get()) } addFactory { GetManga(get()) }
addFactory { GetNextChapter(get()) } addFactory { GetNextChapter(get()) }
addFactory { ResetViewerFlags(get()) } addFactory { ResetViewerFlags(get()) }
addFactory { SetMangaChapterFlags(get()) } addFactory { SetMangaChapterFlags(get()) }
addFactory { SetMangaViewerFlags(get()) }
addFactory { InsertManga(get()) }
addFactory { UpdateManga(get()) } addFactory { UpdateManga(get()) }
addFactory { SetMangaCategories(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 kotlinx.coroutines.flow.Flow
import logcat.LogPriority import logcat.LogPriority
class GetMangaById( class GetManga(
private val mangaRepository: MangaRepository, private val mangaRepository: MangaRepository,
) { ) {
@ -20,6 +20,10 @@ class GetMangaById(
} }
suspend fun subscribe(id: Long): Flow<Manga> { 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>>> { suspend fun subscribe(id: Long): Flow<Pair<Manga, List<Chapter>>> {
return combine( return combine(
mangaRepository.subscribeMangaById(id), mangaRepository.getMangaByIdAsFlow(id),
chapterRepository.getChapterByMangaIdAsFlow(id), chapterRepository.getChapterByMangaIdAsFlow(id),
) { manga, chapters -> ) { manga, chapters ->
Pair(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, 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.isLocal(): Boolean = source == LocalSource.ID
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean { 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.Manga
import eu.kanade.domain.manga.model.MangaUpdate import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.tachiyomi.data.database.models.LibraryManga
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface MangaRepository { interface MangaRepository {
suspend fun getMangaById(id: Long): Manga suspend fun getMangaById(id: Long): Manga
suspend fun subscribeMangaById(id: Long): Flow<Manga>
suspend fun getMangaByIdAsFlow(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 getFavorites(): List<Manga>
suspend fun getLibraryManga(): List<LibraryManga>
fun getLibraryMangaAsFlow(): Flow<List<LibraryManga>>
fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>> fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>>
suspend fun getDuplicateLibraryManga(title: String, sourceId: Long): 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 setMangaCategories(mangaId: Long, categoryIds: List<Long>)
suspend fun insert(manga: Manga): Long?
suspend fun update(update: MangaUpdate): Boolean suspend fun update(update: MangaUpdate): Boolean
suspend fun updateAll(values: List<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.data.listOfStringsAdapter
import eu.kanade.tachiyomi.data.cache.ChapterCache import eu.kanade.tachiyomi.data.cache.ChapterCache
import eu.kanade.tachiyomi.data.cache.CoverCache 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.database.DbOpenCallback
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -76,8 +75,6 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { PreferencesHelper(app) } addSingletonFactory { PreferencesHelper(app) }
addSingletonFactory { DatabaseHelper(get()) }
addSingletonFactory { ChapterCache(app) } addSingletonFactory { ChapterCache(app) }
addSingletonFactory { CoverCache(app) } addSingletonFactory { CoverCache(app) }
@ -106,8 +103,6 @@ class AppModule(val app: Application) : InjektModule {
get<Database>() get<Database>()
get<DatabaseHelper>()
get<DownloadManager>() 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 com.jakewharton.rxrelay.BehaviorRelay
import eu.kanade.domain.category.interactor.GetCategories import eu.kanade.domain.category.interactor.GetCategories
import eu.kanade.tachiyomi.R 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.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
@ -32,7 +31,6 @@ import uy.kohesive.injekt.injectLazy
*/ */
class DownloadManager( class DownloadManager(
private val context: Context, private val context: Context,
private val db: DatabaseHelper = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
) { ) {

View file

@ -4,7 +4,7 @@ import android.content.Context
import androidx.core.content.edit import androidx.core.content.edit
import eu.kanade.domain.chapter.interactor.GetChapter import eu.kanade.domain.chapter.interactor.GetChapter
import eu.kanade.domain.chapter.model.toDbChapter 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.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
@ -34,7 +34,7 @@ class DownloadStore(
private val json: Json by injectLazy() private val json: Json by injectLazy()
private val getMangaById: GetMangaById by injectLazy() private val getManga: GetManga by injectLazy()
private val getChapter: GetChapter by injectLazy() private val getChapter: GetChapter by injectLazy()
/** /**
@ -96,7 +96,7 @@ class DownloadStore(
val cachedManga = mutableMapOf<Long, Manga?>() val cachedManga = mutableMapOf<Long, Manga?>()
for ((mangaId, chapterId) in objs) { for ((mangaId, chapterId) in objs) {
val manga = cachedManga.getOrPut(mangaId) { val manga = cachedManga.getOrPut(mangaId) {
runBlocking { getMangaById.await(mangaId)?.toDbManga() } runBlocking { getManga.await(mangaId)?.toDbManga() }
} ?: continue } ?: continue
val source = sourceManager.get(manga.source) as? HttpSource ?: continue val source = sourceManager.get(manga.source) as? HttpSource ?: continue
val chapter = runBlocking { getChapter.await(chapterId) }?.toDbChapter() ?: 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.interactor.GetChapter
import eu.kanade.domain.chapter.model.toDbChapter 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.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Chapter
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -69,11 +69,11 @@ data class Download(
suspend fun fromChapterId( suspend fun fromChapterId(
chapterId: Long, chapterId: Long,
getChapter: GetChapter = Injekt.get(), getChapter: GetChapter = Injekt.get(),
getMangaById: GetMangaById = Injekt.get(), getManga: GetManga = Injekt.get(),
sourceManager: SourceManager = Injekt.get(), sourceManager: SourceManager = Injekt.get(),
): Download? { ): Download? {
val chapter = getChapter.await(chapterId) ?: return null 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 val source = sourceManager.get(manga.source) as? HttpSource ?: return null
return Download(source, manga.toDbManga(), chapter.toDbChapter()) 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.SyncChaptersWithSource
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.chapter.model.toDbChapter 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.interactor.UpdateManga
import eu.kanade.domain.manga.model.toMangaInfo 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.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack import eu.kanade.domain.track.model.toDbTrack
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.cache.CoverCache 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.Chapter
import eu.kanade.tachiyomi.data.database.models.LibraryManga import eu.kanade.tachiyomi.data.database.models.LibraryManga
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
@ -61,6 +62,7 @@ import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.cancel import kotlinx.coroutines.cancel
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.supervisorScope import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
@ -84,13 +86,13 @@ import eu.kanade.domain.manga.model.Manga as DomainManga
* destroyed. * destroyed.
*/ */
class LibraryUpdateService( class LibraryUpdateService(
val db: DatabaseHelper = Injekt.get(),
val sourceManager: SourceManager = Injekt.get(), val sourceManager: SourceManager = Injekt.get(),
val preferences: PreferencesHelper = Injekt.get(), val preferences: PreferencesHelper = Injekt.get(),
val downloadManager: DownloadManager = Injekt.get(), val downloadManager: DownloadManager = Injekt.get(),
val trackManager: TrackManager = Injekt.get(), val trackManager: TrackManager = Injekt.get(),
val coverCache: CoverCache = 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 updateManga: UpdateManga = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val getCategories: GetCategories = 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. * @param categoryId the ID of the category to update, or -1 if no category specified.
*/ */
fun addMangaToQueue(categoryId: Long) { fun addMangaToQueue(categoryId: Long) {
val libraryManga = db.getLibraryMangas().executeAsBlocking() val libraryManga = runBlocking { getLibraryManga.await() }
val listToUpdate = if (categoryId != -1L) { val listToUpdate = if (categoryId != -1L) {
libraryManga.filter { it.category.toLong() == categoryId } libraryManga.filter { it.category.toLong() == categoryId }
@ -323,7 +325,7 @@ class LibraryUpdateService(
} }
// Don't continue to update if manga not in library // 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( withUpdateNotification(
currentlyUpdatingManga, currentlyUpdatingManga,
@ -434,7 +436,7 @@ class LibraryUpdateService(
.map { it.toSChapter() } .map { it.toSChapter() }
// Get manga from database to account for if it was removed during the update // 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()) ?: return Pair(emptyList(), emptyList())
// [dbmanga] was used so that manga data doesn't get overwritten // [dbmanga] was used so that manga data doesn't get overwritten
@ -471,7 +473,14 @@ class LibraryUpdateService(
mangaWithNotif.prepUpdateCover(coverCache, sManga, true) mangaWithNotif.prepUpdateCover(coverCache, sManga, true)
sManga.thumbnail_url?.let { sManga.thumbnail_url?.let {
mangaWithNotif.thumbnail_url = it 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) { } catch (e: Throwable) {
// Ignore errors and continue // 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.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.toChapterUpdate import eu.kanade.domain.chapter.model.toChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter 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.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.R import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.backup.BackupRestoreService import eu.kanade.tachiyomi.data.backup.BackupRestoreService
@ -46,7 +46,7 @@ import eu.kanade.tachiyomi.BuildConfig.APPLICATION_ID as ID
*/ */
class NotificationReceiver : BroadcastReceiver() { class NotificationReceiver : BroadcastReceiver() {
private val getMangaById: GetMangaById by injectLazy() private val getManga: GetManga by injectLazy()
private val getChapter: GetChapter by injectLazy() private val getChapter: GetChapter by injectLazy()
private val updateChapter: UpdateChapter by injectLazy() private val updateChapter: UpdateChapter by injectLazy()
private val downloadManager: DownloadManager by injectLazy() private val downloadManager: DownloadManager by injectLazy()
@ -178,7 +178,7 @@ class NotificationReceiver : BroadcastReceiver() {
* @param chapterId id of chapter * @param chapterId id of chapter
*/ */
private fun openChapter(context: Context, mangaId: Long, chapterId: Long) { 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) } val chapter = runBlocking { getChapter.await(chapterId) }
if (manga != null && chapter != null) { if (manga != null && chapter != null) {
val intent = ReaderActivity.newIntent(context, manga.id, chapter.id).apply { val intent = ReaderActivity.newIntent(context, manga.id, chapter.id).apply {
@ -248,7 +248,7 @@ class NotificationReceiver : BroadcastReceiver() {
.map { .map {
val chapter = it.copy(read = true) val chapter = it.copy(read = true)
if (preferences.removeAfterMarkedAsRead()) { if (preferences.removeAfterMarkedAsRead()) {
val manga = getMangaById.await(mangaId) val manga = getManga.await(mangaId)
if (manga != null) { if (manga != null) {
val source = sourceManager.get(manga.source) val source = sourceManager.get(manga.source)
if (source != null) { if (source != null) {
@ -270,7 +270,7 @@ class NotificationReceiver : BroadcastReceiver() {
*/ */
private fun downloadChapters(chapterUrls: Array<String>, mangaId: Long) { private fun downloadChapters(chapterUrls: Array<String>, mangaId: Long) {
launchIO { launchIO {
val manga = getMangaById.await(mangaId)?.toDbManga() val manga = getManga.await(mangaId)?.toDbManga()
val chapters = chapterUrls.mapNotNull { getChapter.await(it, mangaId)?.toDbChapter() } val chapters = chapterUrls.mapNotNull { getChapter.await(it, mangaId)?.toDbChapter() }
if (manga != null && chapters.isNotEmpty()) { if (manga != null && chapters.isNotEmpty()) {
downloadManager.downloadChapters(manga, chapters) downloadManager.downloadChapters(manga, chapters)

View file

@ -9,7 +9,7 @@ import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.WorkManager import androidx.work.WorkManager
import androidx.work.WorkerParameters 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.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack import eu.kanade.domain.track.model.toDbTrack
@ -26,7 +26,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
CoroutineWorker(context, workerParams) { CoroutineWorker(context, workerParams) {
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
val getMangaById = Injekt.get<GetMangaById>() val getManga = Injekt.get<GetManga>()
val getTracks = Injekt.get<GetTracks>() val getTracks = Injekt.get<GetTracks>()
val insertTrack = Injekt.get<InsertTrack>() val insertTrack = Injekt.get<InsertTrack>()
@ -35,7 +35,7 @@ class DelayedTrackingUpdateJob(context: Context, workerParams: WorkerParameters)
withContext(Dispatchers.IO) { withContext(Dispatchers.IO) {
val tracks = delayedTrackingStore.getItems().mapNotNull { 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) getTracks.await(manga.id)
.find { track -> track.id == it.trackId } .find { track -> track.id == it.trackId }
?.copy(lastChapterRead = it.lastChapterRead.toDouble()) ?.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.Controller
import com.bluelinelabs.conductor.RouterTransaction import com.bluelinelabs.conductor.RouterTransaction
import com.google.android.material.dialog.MaterialAlertDialogBuilder 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.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper 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.GlobalSearchController
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter import eu.kanade.tachiyomi.ui.browse.source.globalsearch.GlobalSearchPresenter
import eu.kanade.tachiyomi.ui.manga.MangaController import eu.kanade.tachiyomi.ui.manga.MangaController
import kotlinx.coroutines.runBlocking
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
@ -27,9 +29,11 @@ class SearchController(
) : GlobalSearchController(manga?.title) { ) : GlobalSearchController(manga?.title) {
constructor(mangaId: Long) : this( constructor(mangaId: Long) : this(
Injekt.get<DatabaseHelper>() runBlocking {
.getManga(mangaId) Injekt.get<GetManga>()
.executeAsBlocking(), .await(mangaId)
?.toDbManga()
},
) )
private var newManga: Manga? = null 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.GetChapterByMangaId
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.manga.interactor.GetDuplicateLibraryManga 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.toDbManga
import eu.kanade.domain.manga.model.toMangaUpdate
import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.data.cache.CoverCache 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.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
@ -67,13 +70,15 @@ open class BrowseSourcePresenter(
private val sourceId: Long, private val sourceId: Long,
searchQuery: String? = null, searchQuery: String? = null,
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val db: DatabaseHelper = Injekt.get(),
private val prefs: PreferencesHelper = Injekt.get(), private val prefs: PreferencesHelper = Injekt.get(),
private val coverCache: CoverCache = Injekt.get(), private val coverCache: CoverCache = Injekt.get(),
private val getManga: GetManga = Injekt.get(),
private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(), private val getDuplicateLibraryManga: GetDuplicateLibraryManga = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val setMangaCategories: SetMangaCategories = 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 insertTrack: InsertTrack = Injekt.get(),
private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(), private val syncChaptersWithTrackServiceTwoWay: SyncChaptersWithTrackServiceTwoWay = Injekt.get(),
) : BasePresenter<BrowseSourceController>() { ) : BasePresenter<BrowseSourceController>() {
@ -208,19 +213,22 @@ open class BrowseSourcePresenter(
* @return a manga from the database. * @return a manga from the database.
*/ */
private fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { 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) { if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId) val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga) newManga.copyFrom(sManga)
val result = db.insertManga(newManga).executeAsBlocking() newManga.id = -1
newManga.id = result.insertedId() val result = runBlocking {
localManga = newManga val id = insertManga.await(newManga.toDomainManga()!!)
getManga.await(id!!)
}
localManga = result
} else if (!localManga.favorite) { } else if (!localManga.favorite) {
// if the manga isn't a favorite, set its display title from source // 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 // 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()) val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga()) manga.copyFrom(networkManga.toSManga())
manga.initialized = true manga.initialized = true
db.insertManga(manga).executeAsBlocking() updateManga.await(
manga
.toDomainManga()
?.toMangaUpdate()!!,
)
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
} }
@ -282,7 +294,13 @@ open class BrowseSourcePresenter(
autoAddTrack(manga) autoAddTrack(manga)
} }
db.insertManga(manga).executeAsBlocking() runBlocking {
updateManga.await(
manga
.toDomainManga()
?.toMangaUpdate()!!,
)
}
} }
private fun autoAddTrack(manga: Manga) { private fun autoAddTrack(manga: Manga) {

View file

@ -1,8 +1,13 @@
package eu.kanade.tachiyomi.ui.browse.source.globalsearch package eu.kanade.tachiyomi.ui.browse.source.globalsearch
import android.os.Bundle 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.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.extension.ExtensionManager 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.ui.browse.source.browse.BrowseSourcePresenter
import eu.kanade.tachiyomi.util.lang.runAsObservable import eu.kanade.tachiyomi.util.lang.runAsObservable
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.runBlocking
import logcat.LogPriority import logcat.LogPriority
import rx.Observable import rx.Observable
import rx.Subscription import rx.Subscription
@ -38,8 +44,10 @@ open class GlobalSearchPresenter(
private val initialQuery: String? = "", private val initialQuery: String? = "",
private val initialExtensionFilter: String? = null, private val initialExtensionFilter: String? = null,
val sourceManager: SourceManager = Injekt.get(), val sourceManager: SourceManager = Injekt.get(),
val db: DatabaseHelper = Injekt.get(),
val preferences: PreferencesHelper = 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>() { ) : BasePresenter<GlobalSearchController>() {
/** /**
@ -248,7 +256,7 @@ open class GlobalSearchPresenter(
val networkManga = source.getMangaDetails(manga.toMangaInfo()) val networkManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(networkManga.toSManga()) manga.copyFrom(networkManga.toSManga())
manga.initialized = true manga.initialized = true
db.insertManga(manga).executeAsBlocking() runBlocking { updateManga.await(manga.toDomainManga()!!.toMangaUpdate()) }
return manga return manga
} }
@ -260,18 +268,21 @@ open class GlobalSearchPresenter(
* @return a manga from the database. * @return a manga from the database.
*/ */
protected open fun networkToLocalManga(sManga: SManga, sourceId: Long): Manga { 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) { if (localManga == null) {
val newManga = Manga.create(sManga.url, sManga.title, sourceId) val newManga = Manga.create(sManga.url, sManga.title, sourceId)
newManga.copyFrom(sManga) newManga.copyFrom(sManga)
val result = db.insertManga(newManga).executeAsBlocking() newManga.id = -1
newManga.id = result.insertedId() val result = runBlocking {
localManga = newManga val id = insertManga.await(newManga.toDomainManga()!!)
getManga.await(id!!)
}
localManga = result
} else if (!localManga.favorite) { } else if (!localManga.favorite) {
// if the manga isn't a favorite, set its display title from source // 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 // 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.interactor.UpdateChapter
import eu.kanade.domain.chapter.model.ChapterUpdate import eu.kanade.domain.chapter.model.ChapterUpdate
import eu.kanade.domain.chapter.model.toDbChapter 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.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaUpdate import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.track.interactor.GetTracks import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.models.Chapter 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.download.DownloadManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.data.track.TrackManager
@ -60,6 +60,7 @@ typealias LibraryMap = Map<Long, List<LibraryItem>>
*/ */
class LibraryPresenter( class LibraryPresenter(
private val handler: DatabaseHandler = Injekt.get(), private val handler: DatabaseHandler = Injekt.get(),
private val getLibraryManga: GetLibraryManga = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(), private val getTracks: GetTracks = Injekt.get(),
private val getCategories: GetCategories = Injekt.get(), private val getCategories: GetCategories = Injekt.get(),
private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(), private val getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
@ -410,35 +411,7 @@ class LibraryPresenter(
val defaultLibraryDisplayMode = preferences.libraryDisplayMode() val defaultLibraryDisplayMode = preferences.libraryDisplayMode()
val shouldSetFromCategory = preferences.categorizedDisplaySettings() val shouldSetFromCategory = preferences.categorizedDisplaySettings()
// TODO: Move this to domain/data layer return getLibraryManga.subscribe().asObservable()
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()
.map { list -> .map { list ->
list.map { libraryManga -> list.map { libraryManga ->
// Display mode based on user preference: take it from global library setting or category // Display mode based on user preference: take it from global library setting or category

View file

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

View file

@ -20,7 +20,7 @@ import androidx.core.os.bundleOf
import coil.imageLoader import coil.imageLoader
import coil.request.ImageRequest import coil.request.ImageRequest
import coil.size.Size 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.interactor.UpdateManga
import eu.kanade.domain.manga.model.Manga import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.hasCustomCover import eu.kanade.domain.manga.model.hasCustomCover
@ -161,7 +161,7 @@ class MangaFullCoverDialog : FullComposeController<MangaFullCoverDialog.MangaFul
inner class MangaFullCoverPresenter( inner class MangaFullCoverPresenter(
private val mangaId: Long, private val mangaId: Long,
private val getMangaById: GetMangaById = Injekt.get(), private val getManga: GetManga = Injekt.get(),
) : Presenter<MangaFullCoverDialog>() { ) : Presenter<MangaFullCoverDialog>() {
private var presenterScope: CoroutineScope = MainScope() private var presenterScope: CoroutineScope = MainScope()
@ -176,7 +176,7 @@ class MangaFullCoverDialog : FullComposeController<MangaFullCoverDialog.MangaFul
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
presenterScope.launchIO { presenterScope.launchIO {
getMangaById.subscribe(mangaId) getManga.subscribe(mangaId)
.collect { _mangaFlow.value = it } .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.chapter.model.toDbChapter
import eu.kanade.domain.history.interactor.UpsertHistory import eu.kanade.domain.history.interactor.UpsertHistory
import eu.kanade.domain.history.model.HistoryUpdate 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.isLocal
import eu.kanade.domain.manga.model.toDbManga import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.track.interactor.GetTracks import eu.kanade.domain.track.interactor.GetTracks
import eu.kanade.domain.track.interactor.InsertTrack import eu.kanade.domain.track.interactor.InsertTrack
import eu.kanade.domain.track.model.toDbTrack 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.Manga
import eu.kanade.tachiyomi.data.database.models.toDomainManga import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.download.DownloadManager 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. * Presenter used by the activity to perform background operations.
*/ */
class ReaderPresenter( class ReaderPresenter(
private val db: DatabaseHelper = Injekt.get(),
private val sourceManager: SourceManager = Injekt.get(), private val sourceManager: SourceManager = Injekt.get(),
private val downloadManager: DownloadManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(),
private val preferences: PreferencesHelper = Injekt.get(), private val preferences: PreferencesHelper = Injekt.get(),
private val delayedTrackingStore: DelayedTrackingStore = 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 getChapterByMangaId: GetChapterByMangaId = Injekt.get(),
private val getTracks: GetTracks = Injekt.get(), private val getTracks: GetTracks = Injekt.get(),
private val insertTrack: InsertTrack = Injekt.get(), private val insertTrack: InsertTrack = Injekt.get(),
private val upsertHistory: UpsertHistory = Injekt.get(), private val upsertHistory: UpsertHistory = Injekt.get(),
private val updateChapter: UpdateChapter = Injekt.get(), private val updateChapter: UpdateChapter = Injekt.get(),
private val setMangaViewerFlags: SetMangaViewerFlags = Injekt.get(),
) : BasePresenter<ReaderActivity>() { ) : BasePresenter<ReaderActivity>() {
/** /**
@ -242,7 +242,7 @@ class ReaderPresenter(
launchIO { launchIO {
try { try {
val manga = getMangaById.await(mangaId) val manga = getManga.await(mangaId)
withUIContext { withUIContext {
manga?.let { init(it.toDbManga(), initialChapterId) } manga?.let { init(it.toDbManga(), initialChapterId) }
} }
@ -570,7 +570,9 @@ class ReaderPresenter(
fun setMangaReadingMode(readingModeType: Int) { fun setMangaReadingMode(readingModeType: Int) {
val manga = manga ?: return val manga = manga ?: return
manga.readingModeType = readingModeType manga.readingModeType = readingModeType
db.updateViewerFlags(manga).executeAsBlocking() runBlocking {
setMangaViewerFlags.awaitSetMangaReadingMode(manga.id!!.toLong(), readingModeType.toLong())
}
Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread()) Observable.timer(250, TimeUnit.MILLISECONDS, AndroidSchedulers.mainThread())
.subscribeFirst({ view, _ -> .subscribeFirst({ view, _ ->
@ -605,7 +607,9 @@ class ReaderPresenter(
fun setMangaOrientationType(rotationType: Int) { fun setMangaOrientationType(rotationType: Int) {
val manga = manga ?: return val manga = manga ?: return
manga.orientationType = rotationType manga.orientationType = rotationType
db.updateViewerFlags(manga).executeAsBlocking() runBlocking {
setMangaViewerFlags.awaitSetOrientationType(manga.id!!.toLong(), rotationType.toLong())
}
logcat(LogPriority.INFO) { "Manga orientation is ${manga.orientationType}" } logcat(LogPriority.INFO) { "Manga orientation is ${manga.orientationType}" }

View file

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

View file

@ -145,6 +145,44 @@ deleteMangasNotInLibraryBySourceIds:
DELETE FROM mangas DELETE FROM mangas
WHERE favorite = 0 AND source IN :sourceIds; 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:
UPDATE mangas SET UPDATE mangas SET
source = coalesce(:source, source), source = coalesce(:source, source),