Update manga metadata on library update with sqldelight (#7293)

Co-Authored-By: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>

Co-authored-by: Ivan Iskandar <12537387+ivaniskandar@users.noreply.github.com>
(cherry picked from commit 5bb78eb77f2f68c40079548d5fe3b75a11f2bf71)

# Conflicts:
#	app/src/main/java/eu/kanade/domain/manga/model/Manga.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/queries/MangaQueries.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/database/resolvers/MangaLastUpdatedPutResolver.kt
#	app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt
This commit is contained in:
AntsyLich 2022-06-12 20:21:45 +06:00 committed by Jobobby04
parent 0888a7cd72
commit e3f577a6e2
18 changed files with 270 additions and 126 deletions

View File

@ -1,7 +1,10 @@
package eu.kanade.data.manga package eu.kanade.data.manga
import eu.kanade.data.DatabaseHandler import eu.kanade.data.DatabaseHandler
import eu.kanade.data.listOfStringsAdapter
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.repository.MangaRepository import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
@ -11,6 +14,10 @@ class MangaRepositoryImpl(
private val handler: DatabaseHandler, private val handler: DatabaseHandler,
) : MangaRepository { ) : MangaRepository {
override suspend fun getMangaById(id: Long): Manga {
return handler.awaitOne { mangasQueries.getMangaById(id, mangaMapper) }
}
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) }
} }
@ -25,11 +32,33 @@ class MangaRepositoryImpl(
} }
} }
override suspend fun updateLastUpdate(mangaId: Long, lastUpdate: Long) { override suspend fun update(update: MangaUpdate): Boolean {
try { return try {
handler.await { mangasQueries.updateLastUpdate(lastUpdate, mangaId) } handler.await {
mangasQueries.update(
source = update.source,
url = update.url,
artist = update.artist,
author = update.author,
description = update.description,
genre = update.genre?.let(listOfStringsAdapter::encode),
title = update.title,
status = update.status,
thumbnailUrl = update.thumbnailUrl,
favorite = update.favorite?.toLong(),
lastUpdate = update.lastUpdate,
initialized = update.initialized?.toLong(),
viewer = update.viewerFlags,
chapterFlags = update.chapterFlags,
coverLastModified = update.coverLastModified,
dateAdded = update.dateAdded,
mangaId = update.id,
)
}
true
} catch (e: Exception) { } catch (e: Exception) {
logcat(LogPriority.ERROR, e) logcat(LogPriority.ERROR, e)
false
} }
} }
} }

View File

@ -20,8 +20,9 @@ import eu.kanade.domain.history.interactor.RemoveHistoryByMangaId
import eu.kanade.domain.history.interactor.UpsertHistory 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.GetFavoritesBySourceId import eu.kanade.domain.manga.interactor.GetFavoritesBySourceId
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.ResetViewerFlags import eu.kanade.domain.manga.interactor.ResetViewerFlags
import eu.kanade.domain.manga.interactor.UpdateMangaLastUpdate 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
import eu.kanade.domain.source.interactor.GetLanguagesWithSources import eu.kanade.domain.source.interactor.GetLanguagesWithSources
@ -48,9 +49,10 @@ class DomainModule : InjektModule {
override fun InjektRegistrar.registerInjectables() { override fun InjektRegistrar.registerInjectables() {
addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) } addSingletonFactory<MangaRepository> { MangaRepositoryImpl(get()) }
addFactory { GetFavoritesBySourceId(get()) } addFactory { GetFavoritesBySourceId(get()) }
addFactory { GetMangaById(get()) }
addFactory { GetNextChapter(get()) } addFactory { GetNextChapter(get()) }
addFactory { ResetViewerFlags(get()) } addFactory { ResetViewerFlags(get()) }
addFactory { UpdateMangaLastUpdate(get()) } addFactory { UpdateManga(get()) }
addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) } addSingletonFactory<ChapterRepository> { ChapterRepositoryImpl(get()) }
addFactory { UpdateChapter(get()) } addFactory { UpdateChapter(get()) }

View File

@ -5,7 +5,7 @@ import eu.kanade.domain.chapter.model.Chapter
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.chapter.repository.ChapterRepository import eu.kanade.domain.chapter.repository.ChapterRepository
import eu.kanade.domain.manga.interactor.UpdateMangaLastUpdate 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.toDbManga import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
@ -25,7 +25,7 @@ class SyncChaptersWithSource(
private val downloadManager: DownloadManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(),
private val chapterRepository: ChapterRepository = Injekt.get(), private val chapterRepository: ChapterRepository = Injekt.get(),
private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(), private val shouldUpdateDbChapter: ShouldUpdateDbChapter = Injekt.get(),
private val updateMangaLastUpdate: UpdateMangaLastUpdate = Injekt.get(), private val updateManga: UpdateManga = Injekt.get(),
) { ) {
suspend fun await( suspend fun await(
@ -188,7 +188,7 @@ class SyncChaptersWithSource(
// Set this manga as updated since chapters were changed // Set this manga as updated since chapters were changed
// Note that last_update actually represents last time the chapter list changed at all // Note that last_update actually represents last time the chapter list changed at all
updateMangaLastUpdate.await(manga.id, Date().time) updateManga.awaitUpdateLastUpdate(manga.id)
@Suppress("ConvertArgumentToSet") // See tachiyomiorg/tachiyomi#6372. @Suppress("ConvertArgumentToSet") // See tachiyomiorg/tachiyomi#6372.
return Pair(updatedToAdd.subtract(reAdded).toList(), toDelete.subtract(reAdded).toList()) return Pair(updatedToAdd.subtract(reAdded).toList(), toDelete.subtract(reAdded).toList())

View File

@ -0,0 +1,20 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.util.system.logcat
import logcat.LogPriority
class GetMangaById(
private val mangaRepository: MangaRepository,
) {
suspend fun await(id: Long): Manga? {
return try {
mangaRepository.getMangaById(id)
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
null
}
}
}

View File

@ -0,0 +1,61 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.model.Manga
import eu.kanade.domain.manga.model.MangaUpdate
import eu.kanade.domain.manga.model.hasCustomCover
import eu.kanade.domain.manga.model.isLocal
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.repository.MangaRepository
import eu.kanade.tachiyomi.data.cache.CoverCache
import tachiyomi.source.model.MangaInfo
import java.util.Date
class UpdateManga(
private val mangaRepository: MangaRepository,
) {
suspend fun awaitUpdateFromSource(
localManga: Manga,
remoteManga: MangaInfo,
manualFetch: Boolean,
coverCache: CoverCache,
): Boolean {
// if the manga isn't a favorite, set its title from source and update in db
val title = if (!localManga.favorite) remoteManga.title else null
// Never refresh covers if the url is empty to avoid "losing" existing covers
val updateCover = remoteManga.cover.isNotEmpty() && (manualFetch || localManga.thumbnailUrl != remoteManga.cover)
val coverLastModified = if (updateCover) {
when {
localManga.isLocal() -> Date().time
localManga.hasCustomCover(coverCache) -> {
coverCache.deleteFromCache(localManga.toDbManga(), false)
null
}
else -> {
coverCache.deleteFromCache(localManga.toDbManga(), false)
Date().time
}
}
} else null
return mangaRepository.update(
MangaUpdate(
id = localManga.id,
title = title?.takeIf { it.isNotEmpty() },
coverLastModified = coverLastModified,
author = remoteManga.author,
artist = remoteManga.artist,
description = remoteManga.description,
genre = remoteManga.genres,
thumbnailUrl = remoteManga.cover.takeIf { it.isNotEmpty() },
status = remoteManga.status.toLong(),
initialized = true,
),
)
}
suspend fun awaitUpdateLastUpdate(mangaId: Long): Boolean {
return mangaRepository.update(MangaUpdate(id = mangaId, lastUpdate = Date().time))
}
}

View File

@ -1,12 +0,0 @@
package eu.kanade.domain.manga.interactor
import eu.kanade.domain.manga.repository.MangaRepository
class UpdateMangaLastUpdate(
private val mangaRepository: MangaRepository,
) {
suspend fun await(mangaId: Long, lastUpdate: Long) {
mangaRepository.updateLastUpdate(mangaId, lastUpdate)
}
}

View File

@ -1,7 +1,12 @@
package eu.kanade.domain.manga.model package eu.kanade.domain.manga.model
import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.source.LocalSource
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import tachiyomi.source.model.MangaInfo
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import eu.kanade.tachiyomi.data.database.models.Manga as DbManga import eu.kanade.tachiyomi.data.database.models.Manga as DbManga
@ -88,7 +93,7 @@ data class Manga(
} }
// TODO: Remove when all deps are migrated // TODO: Remove when all deps are migrated
fun Manga.toDbManga(): DbManga = DbManga.create(url, title, source).also { fun Manga.toDbManga(): DbManga = DbManga.create(url, ogTitle, source).also {
it.id = id it.id = id
it.favorite = favorite it.favorite = favorite
it.last_update = lastUpdate it.last_update = lastUpdate
@ -97,3 +102,22 @@ fun Manga.toDbManga(): DbManga = DbManga.create(url, title, source).also {
it.chapter_flags = chapterFlags.toInt() it.chapter_flags = chapterFlags.toInt()
it.cover_last_modified = coverLastModified it.cover_last_modified = coverLastModified
} }
fun Manga.toMangaInfo(): MangaInfo = MangaInfo(
// SY -->
artist = ogArtist ?: "",
author = ogAuthor ?: "",
cover = thumbnailUrl ?: "",
description = ogDescription ?: "",
genres = ogGenre ?: emptyList(),
key = url,
status = ogStatus.toInt(),
title = ogTitle,
// SY <--
)
fun Manga.isLocal(): Boolean = source == LocalSource.ID
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
return coverCache.getCustomCoverFile(id).exists()
}

View File

@ -0,0 +1,21 @@
package eu.kanade.domain.manga.model
data class MangaUpdate(
val id: Long,
val source: Long? = null,
val favorite: Boolean? = null,
val lastUpdate: Long? = null,
val dateAdded: Long? = null,
val viewerFlags: Long? = null,
val chapterFlags: Long? = null,
val coverLastModified: Long? = null,
val url: String? = null,
val title: String? = null,
val artist: String? = null,
val author: String? = null,
val description: String? = null,
val genre: List<String>? = null,
val status: Long? = null,
val thumbnailUrl: String? = null,
val initialized: Boolean? = null,
)

View File

@ -1,13 +1,16 @@
package eu.kanade.domain.manga.repository 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 kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
interface MangaRepository { interface MangaRepository {
suspend fun getMangaById(id: Long): Manga
fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>> fun getFavoritesBySourceId(sourceId: Long): Flow<List<Manga>>
suspend fun resetViewerFlags(): Boolean suspend fun resetViewerFlags(): Boolean
suspend fun updateLastUpdate(mangaId: Long, lastUpdate: Long) suspend fun update(update: MangaUpdate): Boolean
} }

View File

@ -151,14 +151,18 @@ fun Manga.toDomainManga(): DomainManga? {
chapterFlags = chapter_flags.toLong(), chapterFlags = chapter_flags.toLong(),
coverLastModified = cover_last_modified, coverLastModified = cover_last_modified,
url = url, url = url,
ogTitle = title, // SY -->
ogArtist = artist, ogTitle = originalTitle,
ogAuthor = author, ogArtist = originalArtist,
ogDescription = description, ogAuthor = originalAuthor,
ogGenre = getGenres(), ogDescription = originalDescription,
ogStatus = status.toLong(), ogGenre = getOriginalGenres(),
ogStatus = originalStatus.toLong(),
// SY <--
thumbnailUrl = thumbnail_url, thumbnailUrl = thumbnail_url,
initialized = initialized, initialized = initialized,
// SY -->
filteredScanlators = MdUtil.getScanlators(filtered_scanlators).toList(), filteredScanlators = MdUtil.getScanlators(filtered_scanlators).toList(),
// SY <--
) )
} }

View File

@ -12,7 +12,6 @@ import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFilteredScanlatorsPutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaFilteredScanlatorsPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaInfoPutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaInfoPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaMigrationPutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaMigrationPutResolver
import eu.kanade.tachiyomi.data.database.resolvers.MangaThumbnailPutResolver import eu.kanade.tachiyomi.data.database.resolvers.MangaThumbnailPutResolver
import eu.kanade.tachiyomi.data.database.tables.CategoryTable import eu.kanade.tachiyomi.data.database.tables.CategoryTable
@ -146,11 +145,6 @@ interface MangaQueries : DbProvider {
.withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags)) .withPutResolver(MangaFlagsPutResolver(MangaTable.COL_VIEWER, Manga::viewer_flags))
.prepare() .prepare()
fun updateLastUpdated(manga: Manga) = db.put()
.`object`(manga)
.withPutResolver(MangaLastUpdatedPutResolver())
.prepare()
fun updateMangaFavorite(manga: Manga) = db.put() fun updateMangaFavorite(manga: Manga) = db.put()
.`object`(manga) .`object`(manga)
.withPutResolver(MangaFavoritePutResolver()) .withPutResolver(MangaFavoritePutResolver())

View File

@ -1,32 +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
class MangaLastUpdatedPutResolver : 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(
MangaTable.COL_LAST_UPDATE to manga.last_update,
)
}

View File

@ -7,6 +7,12 @@ import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import eu.kanade.data.chapter.NoChaptersException import eu.kanade.data.chapter.NoChaptersException
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.interactor.GetMangaById
import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.domain.manga.model.toMangaInfo
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.DatabaseHelper
@ -14,6 +20,7 @@ import eu.kanade.tachiyomi.data.database.models.Category
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
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.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.DownloadService
@ -30,13 +37,11 @@ import eu.kanade.tachiyomi.data.track.TrackService
import eu.kanade.tachiyomi.source.SourceManager import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.UnmeteredSource import eu.kanade.tachiyomi.source.UnmeteredSource
import eu.kanade.tachiyomi.source.model.SManga import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toMangaInfo
import eu.kanade.tachiyomi.source.model.toSChapter import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.all.MergedSource import eu.kanade.tachiyomi.source.online.all.MergedSource
import eu.kanade.tachiyomi.ui.library.LibraryGroup import eu.kanade.tachiyomi.ui.library.LibraryGroup
import eu.kanade.tachiyomi.ui.manga.track.TrackItem import eu.kanade.tachiyomi.ui.manga.track.TrackItem
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay import eu.kanade.tachiyomi.util.chapter.syncChaptersWithTrackServiceTwoWay
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.prepUpdateCover import eu.kanade.tachiyomi.util.prepUpdateCover
@ -52,7 +57,6 @@ import exh.md.utils.MdUtil
import exh.metadata.metadata.base.insertFlatMetadataAsync import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.source.LIBRARY_UPDATE_EXCLUDED_SOURCES import exh.source.LIBRARY_UPDATE_EXCLUDED_SOURCES
import exh.source.MERGED_SOURCE_ID import exh.source.MERGED_SOURCE_ID
import exh.source.getMainSource
import exh.source.isMdBasedSource import exh.source.isMdBasedSource
import exh.source.mangaDexSourceIds import exh.source.mangaDexSourceIds
import exh.util.executeOnIO import exh.util.executeOnIO
@ -70,12 +74,15 @@ import kotlinx.coroutines.supervisorScope
import kotlinx.coroutines.sync.Semaphore import kotlinx.coroutines.sync.Semaphore
import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.sync.withPermit
import logcat.LogPriority import logcat.LogPriority
import tachiyomi.source.model.MangaInfo
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.io.File import java.io.File
import java.util.concurrent.CopyOnWriteArrayList import java.util.concurrent.CopyOnWriteArrayList
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
import java.util.concurrent.atomic.AtomicInteger import java.util.concurrent.atomic.AtomicInteger
import eu.kanade.domain.chapter.model.Chapter as DomainChapter
import eu.kanade.domain.manga.model.Manga as DomainManga
/** /**
* This class will take care of updating the chapters of the manga from the library. It can be * This class will take care of updating the chapters of the manga from the library. It can be
@ -92,6 +99,9 @@ class LibraryUpdateService(
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 updateManga: UpdateManga = Injekt.get(),
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
) : Service() { ) : Service() {
private lateinit var wakeLock: PowerManager.WakeLock private lateinit var wakeLock: PowerManager.WakeLock
@ -386,7 +396,7 @@ class LibraryUpdateService(
} }
// Don't continue to update if manga not in library // Don't continue to update if manga not in library
db.getManga(manga.id!!).executeAsBlocking() ?: return@forEach manga.id?.let { getMangaById.await(it) } ?: return@forEach
withUpdateNotification( withUpdateNotification(
currentlyUpdatingManga, currentlyUpdatingManga,
@ -406,22 +416,25 @@ class LibraryUpdateService(
else -> { else -> {
// Convert to the manga that contains new chapters // Convert to the manga that contains new chapters
val (newChapters, _) = updateManga(mangaWithNotif, loggedServices) mangaWithNotif.toDomainManga()?.let { domainManga ->
val (newChapters, _) = updateManga(domainManga, loggedServices)
val newDbChapters = newChapters.map { it.toDbChapter() }
if (newChapters.isNotEmpty()) { if (newChapters.isNotEmpty()) {
if (mangaWithNotif.shouldDownloadNewChapters(db, preferences)) { if (mangaWithNotif.shouldDownloadNewChapters(db, preferences)) {
downloadChapters(mangaWithNotif, newChapters) downloadChapters(mangaWithNotif, newDbChapters)
hasDownloads.set(true) hasDownloads.set(true)
} }
// Convert to the manga that contains new chapters // Convert to the manga that contains new chapters
newUpdates.add( newUpdates.add(
mangaWithNotif to newChapters.sortedByDescending { ch -> ch.source_order } mangaWithNotif to newDbChapters.sortedByDescending { ch -> ch.source_order }
.toTypedArray(), .toTypedArray(),
) )
} }
} }
} }
}
} catch (e: Throwable) { } catch (e: Throwable) {
val errorMessage = when (e) { val errorMessage = when (e) {
is NoChaptersException -> getString(R.string.no_chapters_error) is NoChaptersException -> getString(R.string.no_chapters_error)
@ -494,23 +507,15 @@ class LibraryUpdateService(
* @param manga the manga to update. * @param manga the manga to update.
* @return a pair of the inserted and removed chapters. * @return a pair of the inserted and removed chapters.
*/ */
private suspend fun updateManga(manga: Manga, loggedServices: List<TrackService>): Pair<List<Chapter>, List<Chapter>> { private suspend fun updateManga(manga: DomainManga, loggedServices: List<TrackService>): Pair<List<DomainChapter>, List<DomainChapter>> {
val source = sourceManager.getOrStub(manga.source).getMainSource() val source = sourceManager.getOrStub(manga.source)
var updatedManga: SManga = manga val mangaInfo: MangaInfo = manga.toMangaInfo()
// Update manga details metadata // Update manga metadata if needed
if (preferences.autoUpdateMetadata()) { if (preferences.autoUpdateMetadata()) {
val updatedMangaDetails = source.getMangaDetails(manga.toMangaInfo()) val updatedMangaInfo = source.getMangaDetails(manga.toMangaInfo())
val sManga = updatedMangaDetails.toSManga() updateManga.awaitUpdateFromSource(manga, updatedMangaInfo, manualFetch = false, coverCache)
// Avoid "losing" existing cover
if (!sManga.thumbnail_url.isNullOrEmpty()) {
manga.prepUpdateCover(coverCache, sManga, false)
} else {
sManga.thumbnail_url = manga.thumbnail_url
}
updatedManga = sManga
} }
// SY --> // SY -->
@ -521,7 +526,7 @@ class LibraryUpdateService(
ioScope?.launch(handler) { ioScope?.launch(handler) {
val tracks = db.getTracks(manga.id).executeAsBlocking() val tracks = db.getTracks(manga.id).executeAsBlocking()
if (tracks.isEmpty() || tracks.none { it.sync_id == TrackManager.MDLIST }) { if (tracks.isEmpty() || tracks.none { it.sync_id == TrackManager.MDLIST }) {
val track = trackManager.mdList.createInitialTracker(manga) val track = trackManager.mdList.createInitialTracker(manga.toDbManga())
db.insertTrack(trackManager.mdList.refresh(track)).executeAsBlocking() db.insertTrack(trackManager.mdList.refresh(track)).executeAsBlocking()
} }
} }
@ -532,20 +537,16 @@ class LibraryUpdateService(
} }
// SY <-- // SY <--
val chapters = source.getChapterList(updatedManga.toMangaInfo()) val chapters = source.getChapterList(mangaInfo)
.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 = db.getManga(manga.id!!).executeAsBlocking() val dbManga = getMangaById.await(manga.id)
?: return Pair(emptyList(), emptyList()) ?: return Pair(emptyList(), emptyList())
// Copy into [dbManga] to retain favourite value
dbManga.copyFrom(updatedManga)
db.insertManga(dbManga).executeAsBlocking()
// [dbmanga] was used so that manga data doesn't get overwritten // [dbmanga] was used so that manga data doesn't get overwritten
// in case manga gets new chapter // in case manga gets new chapter
return syncChaptersWithSource(chapters, dbManga, source) return syncChaptersWithSource.await(chapters, dbManga, source)
} }
private suspend fun updateCovers() { private suspend fun updateCovers() {

View File

@ -1,8 +1,12 @@
package eu.kanade.tachiyomi.source.online.all package eu.kanade.tachiyomi.source.online.all
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
import eu.kanade.domain.chapter.model.toDbChapter
import eu.kanade.domain.manga.model.toDbManga
import eu.kanade.tachiyomi.data.database.DatabaseHelper 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.database.models.toDomainManga
import eu.kanade.tachiyomi.data.database.models.toMangaInfo import eu.kanade.tachiyomi.data.database.models.toMangaInfo
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
@ -15,7 +19,6 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.toSChapter import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga import eu.kanade.tachiyomi.source.model.toSManga
import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import eu.kanade.tachiyomi.util.lang.withIOContext import eu.kanade.tachiyomi.util.lang.withIOContext
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
import exh.log.xLogW import exh.log.xLogW
@ -31,7 +34,11 @@ import okhttp3.Response
import rx.Observable import rx.Observable
import tachiyomi.source.model.ChapterInfo import tachiyomi.source.model.ChapterInfo
import tachiyomi.source.model.MangaInfo import tachiyomi.source.model.MangaInfo
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import eu.kanade.domain.chapter.model.Chapter as DomainChapter
import eu.kanade.domain.manga.model.Manga as DomainManga
class MergedSource : HttpSource() { class MergedSource : HttpSource() {
private val db: DatabaseHelper by injectLazy() private val db: DatabaseHelper by injectLazy()
@ -92,8 +99,8 @@ class MergedSource : HttpSource() {
} }
// TODO more chapter dedupe // TODO more chapter dedupe
private fun transformMergedChapters(manga: Manga, chapterList: List<Chapter>, editScanlators: Boolean, dedupe: Boolean): List<Chapter> { private fun transformMergedChapters(manga: DomainManga, chapterList: List<Chapter>, editScanlators: Boolean, dedupe: Boolean): List<Chapter> {
val mangaReferences = db.getMergedMangaReferences(manga.id!!).executeAsBlocking() val mangaReferences = db.getMergedMangaReferences(manga.id).executeAsBlocking()
if (editScanlators) { if (editScanlators) {
val sources = mangaReferences.map { sourceManager.getOrStub(it.mangaSourceId) to it.mangaId } val sources = mangaReferences.map { sourceManager.getOrStub(it.mangaSourceId) to it.mangaId }
chapterList.onEach { chapter -> chapterList.onEach { chapter ->
@ -107,12 +114,12 @@ class MergedSource : HttpSource() {
return if (dedupe) dedupeChapterList(mangaReferences, chapterList) else chapterList return if (dedupe) dedupeChapterList(mangaReferences, chapterList) else chapterList
} }
fun getChaptersAsBlocking(manga: Manga, editScanlators: Boolean = false, dedupe: Boolean = true): List<Chapter> { fun getChaptersAsBlocking(manga: DomainManga, editScanlators: Boolean = false, dedupe: Boolean = true): List<Chapter> {
return transformMergedChapters(manga, db.getChaptersByMergedMangaId(manga.id!!).executeAsBlocking(), editScanlators, dedupe) return transformMergedChapters(manga, db.getChaptersByMergedMangaId(manga.id).executeAsBlocking(), editScanlators, dedupe)
} }
fun getChaptersObservable(manga: Manga, editScanlators: Boolean = false, dedupe: Boolean = true): Observable<List<Chapter>> { fun getChaptersObservable(manga: DomainManga, editScanlators: Boolean = false, dedupe: Boolean = true): Observable<List<Chapter>> {
return db.getChaptersByMergedMangaId(manga.id!!).asRxObservable() return db.getChaptersByMergedMangaId(manga.id).asRxObservable()
.map { chapterList -> .map { chapterList ->
transformMergedChapters(manga, chapterList, editScanlators, dedupe) transformMergedChapters(manga, chapterList, editScanlators, dedupe)
} }
@ -144,20 +151,21 @@ class MergedSource : HttpSource() {
return chapterList.maxByOrNull { it.chapter_number }?.manga_id return chapterList.maxByOrNull { it.chapter_number }?.manga_id
} }
suspend fun fetchChaptersForMergedManga(manga: Manga, downloadChapters: Boolean = true, editScanlators: Boolean = false, dedupe: Boolean = true): List<Chapter> { suspend fun fetchChaptersForMergedManga(manga: DomainManga, downloadChapters: Boolean = true, editScanlators: Boolean = false, dedupe: Boolean = true): List<Chapter> {
return withIOContext { return withIOContext {
fetchChaptersAndSync(manga, downloadChapters) fetchChaptersAndSync(manga, downloadChapters)
getChaptersAsBlocking(manga, editScanlators, dedupe) getChaptersAsBlocking(manga, editScanlators, dedupe)
} }
} }
suspend fun fetchChaptersAndSync(manga: Manga, downloadChapters: Boolean = true): Pair<List<Chapter>, List<Chapter>> { suspend fun fetchChaptersAndSync(manga: DomainManga, downloadChapters: Boolean = true): Pair<List<DomainChapter>, List<DomainChapter>> {
val mangaReferences = db.getMergedMangaReferences(manga.id!!).executeAsBlocking() val syncChaptersWithSource = Injekt.get<SyncChaptersWithSource>()
val mangaReferences = db.getMergedMangaReferences(manga.id).executeAsBlocking()
if (mangaReferences.isEmpty()) { if (mangaReferences.isEmpty()) {
throw IllegalArgumentException("Manga references are empty, chapters unavailable, merge is likely corrupted") throw IllegalArgumentException("Manga references are empty, chapters unavailable, merge is likely corrupted")
} }
val ifDownloadNewChapters = downloadChapters && manga.shouldDownloadNewChapters(db, preferences) val ifDownloadNewChapters = downloadChapters && manga.toDbManga().shouldDownloadNewChapters(db, preferences)
val semaphore = Semaphore(5) val semaphore = Semaphore(5)
var exception: Exception? = null var exception: Exception? = null
return supervisorScope { return supervisorScope {
@ -175,11 +183,11 @@ class MergedSource : HttpSource() {
val chapterList = source.getChapterList(loadedManga.toMangaInfo()) val chapterList = source.getChapterList(loadedManga.toMangaInfo())
.map(ChapterInfo::toSChapter) .map(ChapterInfo::toSChapter)
val results = val results =
syncChaptersWithSource(chapterList, loadedManga, source) syncChaptersWithSource.await(chapterList, loadedManga.toDomainManga()!!, source)
if (ifDownloadNewChapters && reference.downloadChapters) { if (ifDownloadNewChapters && reference.downloadChapters) {
downloadManager.downloadChapters( downloadManager.downloadChapters(
loadedManga, loadedManga,
results.first, results.first.map(DomainChapter::toDbChapter),
) )
} }
results results

View File

@ -9,6 +9,7 @@ import eu.kanade.tachiyomi.data.database.models.Category
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.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.toDomainManga
import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.library.CustomMangaManager import eu.kanade.tachiyomi.data.library.CustomMangaManager
import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.preference.PreferencesHelper
@ -641,7 +642,7 @@ class LibraryPresenter(
val mergedSource = sourceManager.get(MERGED_SOURCE_ID) as MergedSource val mergedSource = sourceManager.get(MERGED_SOURCE_ID) as MergedSource
val mergedMangas = db.getMergedMangas(manga.id!!).executeAsBlocking() val mergedMangas = db.getMergedMangas(manga.id!!).executeAsBlocking()
mergedSource mergedSource
.getChaptersAsBlocking(manga) .getChaptersAsBlocking(manga.toDomainManga()!!)
.filter { !it.read } .filter { !it.read }
.groupBy { it.manga_id!! } .groupBy { it.manga_id!! }
.forEach ab@{ (mangaId, chapters) -> .forEach ab@{ (mangaId, chapters) ->
@ -710,7 +711,11 @@ class LibraryPresenter(
fun markReadStatus(mangas: List<Manga>, read: Boolean) { fun markReadStatus(mangas: List<Manga>, read: Boolean) {
mangas.forEach { manga -> mangas.forEach { manga ->
launchIO { launchIO {
val chapters = if (manga.source == MERGED_SOURCE_ID) (sourceManager.get(MERGED_SOURCE_ID) as MergedSource).getChaptersAsBlocking(manga) else db.getChapters(manga).executeAsBlocking() val chapters = if (manga.source == MERGED_SOURCE_ID) {
(sourceManager.get(MERGED_SOURCE_ID) as MergedSource).getChaptersAsBlocking(manga.toDomainManga()!!)
} else {
db.getChapters(manga).executeAsBlocking()
}
chapters.forEach { chapters.forEach {
it.read = read it.read = read
if (!read) { if (!read) {
@ -815,7 +820,7 @@ class LibraryPresenter(
/** Returns first unread chapter of a manga */ /** Returns first unread chapter of a manga */
fun getFirstUnread(manga: Manga): Chapter? { fun getFirstUnread(manga: Manga): Chapter? {
val chapters = if (manga.source == MERGED_SOURCE_ID) { val chapters = if (manga.source == MERGED_SOURCE_ID) {
(sourceManager.get(MERGED_SOURCE_ID) as MergedSource).getChaptersAsBlocking(manga) (sourceManager.get(MERGED_SOURCE_ID) as MergedSource).getChaptersAsBlocking(manga.toDomainManga()!!)
} else db.getChapters(manga).executeAsBlocking() } else db.getChapters(manga).executeAsBlocking()
return if (manga.isEhBasedManga()) { return if (manga.isEhBasedManga()) {
val chapter = chapters.sortedBy { it.source_order }.getOrNull(0) val chapter = chapters.sortedBy { it.source_order }.getOrNull(0)

View File

@ -12,6 +12,7 @@ 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.database.models.MangaCategory import eu.kanade.tachiyomi.data.database.models.MangaCategory
import eu.kanade.tachiyomi.data.database.models.Track import eu.kanade.tachiyomi.data.database.models.Track
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.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.download.model.Download
@ -222,7 +223,7 @@ class MangaPresenter(
// Add the subscription that retrieves the chapters from the database, keeps subscribed to // Add the subscription that retrieves the chapters from the database, keeps subscribed to
// changes, and sends the list of chapters to the relay. // changes, and sends the list of chapters to the relay.
add( add(
(/* SY --> */if (source is MergedSource) source.getChaptersObservable(manga, true, dedupe) else /* SY <-- */ db.getChapters(manga).asRxObservable()) (/* SY --> */if (source is MergedSource) source.getChaptersObservable(manga.toDomainManga()!!, true, dedupe) else /* SY <-- */ db.getChapters(manga).asRxObservable())
.map { chapters -> .map { chapters ->
// Convert every chapter to a model. // Convert every chapter to a model.
chapters.map { it.toModel() } chapters.map { it.toModel() }
@ -761,7 +762,7 @@ class MangaPresenter(
downloadNewChapters(newChapters) downloadNewChapters(newChapters)
} }
} else { } else {
source.fetchChaptersForMergedManga(manga, manualFetch, true, dedupe) source.fetchChaptersForMergedManga(manga.toDomainManga()!!, manualFetch, true, dedupe)
} }
withUIContext { view?.onFetchChaptersDone() } withUIContext { view?.onFetchChaptersDone() }

View File

@ -15,6 +15,7 @@ import eu.kanade.tachiyomi.data.cache.CoverCache
import eu.kanade.tachiyomi.data.database.DatabaseHelper 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.database.models.toDomainManga
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.saver.Image import eu.kanade.tachiyomi.data.saver.Image
@ -47,7 +48,6 @@ import eu.kanade.tachiyomi.util.lang.takeBytes
import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.DiskUtil
import eu.kanade.tachiyomi.util.storage.cacheImageDir import eu.kanade.tachiyomi.util.storage.cacheImageDir
import eu.kanade.tachiyomi.util.system.ImageUtil import eu.kanade.tachiyomi.util.system.ImageUtil
import eu.kanade.tachiyomi.util.system.isLTR
import eu.kanade.tachiyomi.util.system.isOnline import eu.kanade.tachiyomi.util.system.isOnline
import eu.kanade.tachiyomi.util.system.logcat import eu.kanade.tachiyomi.util.system.logcat
import eu.kanade.tachiyomi.util.updateCoverLastModified import eu.kanade.tachiyomi.util.updateCoverLastModified
@ -146,7 +146,7 @@ class ReaderPresenter(
// SY <-- // SY <--
val dbChapters = /* SY --> */ if (manga.source == MERGED_SOURCE_ID) { val dbChapters = /* SY --> */ if (manga.source == MERGED_SOURCE_ID) {
(sourceManager.get(MERGED_SOURCE_ID) as MergedSource) (sourceManager.get(MERGED_SOURCE_ID) as MergedSource)
.getChaptersAsBlocking(manga) .getChaptersAsBlocking(manga.toDomainManga()!!)
} else /* SY <-- */ db.getChapters(manga).executeAsBlocking() } else /* SY <-- */ db.getChapters(manga).executeAsBlocking()
val selectedChapter = dbChapters.find { it.id == chapterId } val selectedChapter = dbChapters.find { it.id == chapterId }

View File

@ -69,7 +69,22 @@ WHERE favorite = 0 AND source IN :sourceIdsAND AND _id NOT IN (
SELECT manga_id FROM chapters WHERE read = 1 OR last_page_read != 0 SELECT manga_id FROM chapters WHERE read = 1 OR last_page_read != 0
); );
updateLastUpdate: update:
UPDATE mangas UPDATE mangas SET
SET last_update = :lastUpdate source = coalesce(:source, source),
url = coalesce(:url, url),
artist = coalesce(:artist, artist),
author = coalesce(:author, author),
description = coalesce(:description, description),
genre = coalesce(:genre, genre),
title = coalesce(:title, title),
status = coalesce(:status, status),
thumbnail_url = coalesce(:thumbnailUrl, thumbnail_url),
favorite = coalesce(:favorite, favorite),
last_update = coalesce(:lastUpdate, last_update),
initialized = coalesce(:initialized, initialized),
viewer = coalesce(:viewer, viewer),
chapter_flags = coalesce(:chapterFlags, chapter_flags),
cover_last_modified = coalesce(:coverLastModified, cover_last_modified),
date_added = coalesce(:dateAdded, date_added)
WHERE _id = :mangaId; WHERE _id = :mangaId;