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>
This commit is contained in:
parent
5fbf454652
commit
5bb78eb77f
13 changed files with 220 additions and 96 deletions
|
@ -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
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
@ -43,9 +44,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()) }
|
||||||
|
|
|
@ -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
|
||||||
|
@ -24,7 +24,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(
|
||||||
|
@ -171,7 +171,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())
|
||||||
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -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))
|
||||||
|
}
|
||||||
|
}
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,11 @@
|
||||||
package eu.kanade.domain.manga.model
|
package eu.kanade.domain.manga.model
|
||||||
|
|
||||||
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
|
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 eu.kanade.tachiyomi.data.database.models.Manga as DbManga
|
import eu.kanade.tachiyomi.data.database.models.Manga as DbManga
|
||||||
|
|
||||||
data class Manga(
|
data class Manga(
|
||||||
|
@ -62,3 +67,20 @@ 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(
|
||||||
|
artist = artist ?: "",
|
||||||
|
author = author ?: "",
|
||||||
|
cover = thumbnailUrl ?: "",
|
||||||
|
description = description ?: "",
|
||||||
|
genres = genre ?: emptyList(),
|
||||||
|
key = url,
|
||||||
|
status = status.toInt(),
|
||||||
|
title = title,
|
||||||
|
)
|
||||||
|
|
||||||
|
fun Manga.isLocal(): Boolean = source == LocalSource.ID
|
||||||
|
|
||||||
|
fun Manga.hasCustomCover(coverCache: CoverCache = Injekt.get()): Boolean {
|
||||||
|
return coverCache.getCustomCoverFile(id).exists()
|
||||||
|
}
|
||||||
|
|
|
@ -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,
|
||||||
|
)
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -10,7 +10,6 @@ import eu.kanade.tachiyomi.data.database.resolvers.LibraryMangaGetResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaCoverLastModifiedPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaCoverLastModifiedPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFavoritePutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
import eu.kanade.tachiyomi.data.database.resolvers.MangaFlagsPutResolver
|
||||||
import eu.kanade.tachiyomi.data.database.resolvers.MangaLastUpdatedPutResolver
|
|
||||||
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.CategoryTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
import eu.kanade.tachiyomi.data.database.tables.ChapterTable
|
||||||
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
import eu.kanade.tachiyomi.data.database.tables.MangaCategoryTable
|
||||||
|
@ -101,11 +100,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())
|
||||||
|
|
|
@ -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,
|
|
||||||
)
|
|
||||||
}
|
|
|
@ -7,6 +7,11 @@ 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.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 +19,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
|
||||||
|
@ -29,10 +35,8 @@ 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.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
|
||||||
|
@ -55,12 +59,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
|
||||||
|
@ -77,6 +84,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
|
||||||
|
@ -302,7 +312,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,
|
||||||
|
@ -322,22 +332,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)
|
mangaWithNotif.toDomainManga()?.let { domainManga ->
|
||||||
|
val (newChapters, _) = updateManga(domainManga)
|
||||||
|
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)
|
||||||
|
@ -394,39 +407,27 @@ 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): Pair<List<Chapter>, List<Chapter>> {
|
private suspend fun updateManga(manga: DomainManga): Pair<List<DomainChapter>, List<DomainChapter>> {
|
||||||
val source = sourceManager.getOrStub(manga.source)
|
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
|
val chapters = source.getChapterList(mangaInfo)
|
||||||
}
|
|
||||||
|
|
||||||
val chapters = source.getChapterList(updatedManga.toMangaInfo())
|
|
||||||
.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() {
|
||||||
|
|
|
@ -58,7 +58,22 @@ deleteMangasNotInLibraryBySourceIds:
|
||||||
DELETE FROM mangas
|
DELETE FROM mangas
|
||||||
WHERE favorite = 0 AND source IN :sourceIds;
|
WHERE favorite = 0 AND source IN :sourceIds;
|
||||||
|
|
||||||
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;
|
||||||
|
|
Reference in a new issue