Use SQLDelight for most SY specific things
This commit is contained in:
parent
3cf4c3128f
commit
664f9b1484
14
app/src/main/java/eu/kanade/data/exh/FavoriteEntry.kt
Normal file
14
app/src/main/java/eu/kanade/data/exh/FavoriteEntry.kt
Normal file
@ -0,0 +1,14 @@
|
||||
package eu.kanade.data.exh
|
||||
|
||||
import exh.favorites.sql.models.FavoriteEntry
|
||||
|
||||
val favoriteEntryMapper: (Long, String, String, String, Long) -> FavoriteEntry =
|
||||
{ id, title, gid, token, category ->
|
||||
FavoriteEntry(
|
||||
id = id,
|
||||
title = title,
|
||||
gid = gid,
|
||||
token = token,
|
||||
category = category.toInt(),
|
||||
)
|
||||
}
|
@ -10,4 +10,10 @@ class RemoveHistoryById(
|
||||
suspend fun await(history: HistoryWithRelations) {
|
||||
repository.resetHistory(history.id)
|
||||
}
|
||||
|
||||
// SY -->
|
||||
suspend fun await(historyId: Long) {
|
||||
repository.resetHistory(historyId)
|
||||
}
|
||||
// SY <--
|
||||
}
|
||||
|
@ -44,7 +44,6 @@ import eu.kanade.tachiyomi.source.online.MetadataSource
|
||||
import eu.kanade.tachiyomi.util.system.logcat
|
||||
import exh.metadata.metadata.base.awaitFlatMetadataForManga
|
||||
import exh.metadata.metadata.base.awaitInsertFlatMetadata
|
||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import exh.source.getMainSource
|
||||
import exh.util.nullIfBlank
|
||||
@ -198,7 +197,7 @@ class FullBackupManager(context: Context) : AbstractBackupManager(context) {
|
||||
|
||||
val source = sourceManager.get(manga.source)?.getMainSource<MetadataSource<*, *>>()
|
||||
if (source != null) {
|
||||
handler.getFlatMetadataForManga(manga.id)?.let { flatMetadata ->
|
||||
handler.awaitFlatMetadataForManga(manga.id)?.let { flatMetadata ->
|
||||
mangaObject.flatMetadata = BackupFlatMetadata.copyFrom(flatMetadata)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,8 @@
|
||||
package eu.kanade.tachiyomi.source.online
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
@ -12,8 +14,8 @@ import eu.kanade.tachiyomi.ui.manga.MangaScreenState
|
||||
import eu.kanade.tachiyomi.util.lang.awaitSingle
|
||||
import eu.kanade.tachiyomi.util.lang.runAsObservable
|
||||
import exh.metadata.metadata.base.RaisedSearchMetadata
|
||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||
import exh.metadata.metadata.base.insertFlatMetadata
|
||||
import exh.metadata.metadata.base.awaitFlatMetadataForManga
|
||||
import exh.metadata.metadata.base.awaitInsertFlatMetadata
|
||||
import rx.Completable
|
||||
import rx.Single
|
||||
import tachiyomi.source.model.MangaInfo
|
||||
@ -26,6 +28,8 @@ import kotlin.reflect.KClass
|
||||
*/
|
||||
interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
|
||||
val db: DatabaseHelper get() = Injekt.get()
|
||||
val handler: DatabaseHandler get() = Injekt.get()
|
||||
val getMangaByUrlAndSource: GetMangaByUrlAndSource get() = Injekt.get()
|
||||
|
||||
/**
|
||||
* The class of the metadata used by this source
|
||||
@ -59,14 +63,14 @@ interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
|
||||
suspend fun parseToManga(manga: MangaInfo, input: I): MangaInfo {
|
||||
val mangaId = manga.id()
|
||||
val metadata = if (mangaId != null) {
|
||||
val flatMetadata = db.getFlatMetadataForManga(mangaId).executeAsBlocking()
|
||||
val flatMetadata = handler.awaitFlatMetadataForManga(mangaId)
|
||||
flatMetadata?.raise(metaClass) ?: newMetaInstance()
|
||||
} else newMetaInstance()
|
||||
|
||||
parseIntoMetadata(metadata, input)
|
||||
if (mangaId != null) {
|
||||
metadata.mangaId = mangaId
|
||||
db.insertFlatMetadata(metadata.flatten())
|
||||
handler.awaitInsertFlatMetadata(metadata.flatten())
|
||||
}
|
||||
|
||||
return metadata.createMangaInfo(manga)
|
||||
@ -95,7 +99,7 @@ interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
|
||||
*/
|
||||
suspend fun fetchOrLoadMetadata(mangaId: Long?, inputProducer: suspend () -> I): M {
|
||||
val meta = if (mangaId != null) {
|
||||
val flatMetadata = db.getFlatMetadataForManga(mangaId).executeAsBlocking()
|
||||
val flatMetadata = handler.awaitFlatMetadataForManga(mangaId)
|
||||
flatMetadata?.raise(metaClass)
|
||||
} else {
|
||||
null
|
||||
@ -106,15 +110,16 @@ interface MetadataSource<M : RaisedSearchMetadata, I> : CatalogueSource {
|
||||
parseIntoMetadata(newMeta, input)
|
||||
if (mangaId != null) {
|
||||
newMeta.mangaId = mangaId
|
||||
db.insertFlatMetadata(newMeta.flatten()).let { newMeta }
|
||||
} else newMeta
|
||||
handler.awaitInsertFlatMetadata(newMeta.flatten())
|
||||
}
|
||||
newMeta
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DescriptionComposable(state: MangaScreenState.Success, openMetadataViewer: () -> Unit, search: (String) -> Unit)
|
||||
|
||||
fun MangaInfo.id() = db.getManga(key, id).executeAsBlocking()?.id
|
||||
suspend fun MangaInfo.id() = getMangaByUrlAndSource.await(key, id)?.id
|
||||
val SManga.id get() = (this as? Manga)?.id
|
||||
val SChapter.mangaId get() = (this as? Chapter)?.manga_id
|
||||
}
|
||||
|
@ -807,7 +807,7 @@ class LibraryController(
|
||||
?.setMessage(activity!!.getString(R.string.favorites_sync_bad_library_state, status.message))
|
||||
?.setCancelable(false)
|
||||
?.setPositiveButton(R.string.show_gallery) { _, _ ->
|
||||
openManga(status.manga)
|
||||
openManga(status.manga.toDbManga())
|
||||
presenter.favoritesSync.status.value = FavoritesSyncStatus.Idle(activity!!)
|
||||
}
|
||||
?.setNegativeButton(android.R.string.ok) { _, _ ->
|
||||
|
@ -239,7 +239,7 @@ class MangaPresenter(
|
||||
if (chapters.isNotEmpty() && manga.isEhBasedManga() && DebugToggles.ENABLE_EXH_ROOT_REDIRECT.enabled) {
|
||||
// Check for gallery in library and accept manga with lowest id
|
||||
// Find chapters sharing same root
|
||||
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters.map { it.toDbChapter() })
|
||||
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters)
|
||||
.onEach { (acceptedChain, _) ->
|
||||
// Redirect if we are not the accepted root
|
||||
if (manga.id != acceptedChain.manga.id && acceptedChain.manga.favorite) {
|
||||
@ -250,7 +250,7 @@ class MangaPresenter(
|
||||
val update = (ourChapterUrls - acceptedChapterUrls).isNotEmpty()
|
||||
redirectFlow.emit(
|
||||
EXHRedirect(
|
||||
acceptedChain.manga.id!!,
|
||||
acceptedChain.manga.id,
|
||||
update,
|
||||
),
|
||||
)
|
||||
|
@ -2,33 +2,38 @@ package exh
|
||||
|
||||
import android.content.Context
|
||||
import androidx.core.net.toUri
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.data.chapter.chapterMapper
|
||||
import eu.kanade.data.manga.mangaMapper
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.manga.interactor.GetMangaById
|
||||
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.manga.model.toMangaInfo
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.toSChapter
|
||||
import eu.kanade.tachiyomi.source.model.toSManga
|
||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import exh.log.xLogStack
|
||||
import exh.source.getMainSource
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class GalleryAdder {
|
||||
class GalleryAdder(
|
||||
private val handler: DatabaseHandler = Injekt.get(),
|
||||
private val getMangaByUrlAndSource: GetMangaByUrlAndSource = Injekt.get(),
|
||||
private val getMangaById: GetMangaById = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
) {
|
||||
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
|
||||
private val filters: Pair<Set<String>, Set<Long>> = run {
|
||||
val preferences = Injekt.get<PreferencesHelper>()
|
||||
preferences.enabledLanguages().get() to preferences.disabledSources().get().map { it.toLong() }.toSet()
|
||||
private val filters: Pair<Set<String>, Set<Long>> = Injekt.get<PreferencesHelper>().run {
|
||||
enabledLanguages().get() to disabledSources().get().map { it.toLong() }.toSet()
|
||||
}
|
||||
|
||||
private val logger = xLogStack()
|
||||
@ -115,33 +120,28 @@ class GalleryAdder {
|
||||
} ?: return GalleryAddEvent.Fail.UnknownType(url, context)
|
||||
|
||||
// Use manga in DB if possible, otherwise, make a new manga
|
||||
val manga = db.getManga(cleanedMangaUrl, source.id).executeAsBlocking()
|
||||
?: Manga.create(source.id).apply {
|
||||
this.url = cleanedMangaUrl
|
||||
title = realMangaUrl
|
||||
var manga = getMangaByUrlAndSource.await(cleanedMangaUrl, source.id)
|
||||
?: handler.awaitOne(true) {
|
||||
// Insert created manga if not in DB before fetching details
|
||||
// This allows us to keep the metadata when fetching details
|
||||
mangasQueries.insertEmpty(
|
||||
source = source.id,
|
||||
url = cleanedMangaUrl,
|
||||
title = realMangaUrl,
|
||||
)
|
||||
mangasQueries.selectLastInsertRow(mangaMapper)
|
||||
}
|
||||
|
||||
// Insert created manga if not in DB before fetching details
|
||||
// This allows us to keep the metadata when fetching details
|
||||
if (manga.id == null) {
|
||||
db.insertManga(manga).executeAsBlocking().insertedId()?.let {
|
||||
manga.id = it
|
||||
}
|
||||
}
|
||||
|
||||
// Fetch and copy details
|
||||
val newManga = source.getMangaDetails(manga.toMangaInfo())
|
||||
|
||||
manga.copyFrom(newManga.toSManga())
|
||||
manga.initialized = true
|
||||
updateManga.awaitUpdateFromSource(manga, newManga, false, Injekt.get())
|
||||
manga = getMangaById.await(manga.id)!!
|
||||
|
||||
if (fav) {
|
||||
manga.favorite = true
|
||||
manga.date_added = System.currentTimeMillis()
|
||||
updateManga.awaitUpdateFavorite(manga.id, true)
|
||||
manga = manga.copy(favorite = true)
|
||||
}
|
||||
|
||||
db.insertManga(manga).executeAsBlocking()
|
||||
|
||||
// Fetch and copy chapters
|
||||
try {
|
||||
val chapterList = if (source is EHentai) {
|
||||
@ -151,7 +151,7 @@ class GalleryAdder {
|
||||
}.map { it.toSChapter() }
|
||||
|
||||
if (chapterList.isNotEmpty()) {
|
||||
syncChaptersWithSource(chapterList, manga, source)
|
||||
syncChaptersWithSource.await(chapterList, manga, source)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.w(context.getString(R.string.gallery_adder_chapter_fetch_error, manga.title), e)
|
||||
@ -159,7 +159,7 @@ class GalleryAdder {
|
||||
}
|
||||
|
||||
return if (cleanedChapterUrl != null) {
|
||||
val chapter = db.getChapter(cleanedChapterUrl, manga.id!!).executeAsBlocking()
|
||||
val chapter = handler.awaitOneOrNull { chaptersQueries.getChapterByUrlAndMangaId(cleanedChapterUrl, manga.id, chapterMapper) }
|
||||
if (chapter != null) {
|
||||
GalleryAddEvent.Success(url, manga, context, chapter)
|
||||
} else {
|
||||
|
@ -4,19 +4,21 @@ import android.app.Application
|
||||
import androidx.work.WorkManager
|
||||
import com.pushtorefresh.storio.sqlite.queries.RawQuery
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.data.manga.mangaMapper
|
||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.toMangaInfo
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
||||
import eu.kanade.tachiyomi.data.database.tables.MangaTable
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.toSManga
|
||||
import eu.kanade.tachiyomi.source.online.all.NHentai
|
||||
import exh.EXHMigrations
|
||||
import exh.eh.EHentaiThrottleManager
|
||||
import exh.eh.EHentaiUpdateWorker
|
||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||
import exh.metadata.metadata.base.insertFlatMetadataAsync
|
||||
import exh.metadata.metadata.base.awaitFlatMetadataForManga
|
||||
import exh.metadata.metadata.base.awaitInsertFlatMetadata
|
||||
import exh.source.EH_SOURCE_ID
|
||||
import exh.source.EXH_SOURCE_ID
|
||||
import exh.source.isEhBasedManga
|
||||
@ -35,9 +37,11 @@ import java.util.UUID
|
||||
object DebugFunctions {
|
||||
val app: Application by injectLazy()
|
||||
val db: DatabaseHelper by injectLazy()
|
||||
val database: DatabaseHandler by injectLazy()
|
||||
val handler: DatabaseHandler by injectLazy()
|
||||
val prefs: PreferencesHelper by injectLazy()
|
||||
val sourceManager: SourceManager by injectLazy()
|
||||
val updateManga: UpdateManga by injectLazy()
|
||||
val getFavorites: GetFavorites by injectLazy()
|
||||
|
||||
fun forceUpgradeMigration() {
|
||||
prefs.ehLastVersionCode().set(1)
|
||||
@ -59,10 +63,10 @@ object DebugFunctions {
|
||||
}.toList()
|
||||
|
||||
allManga.forEach { manga ->
|
||||
val meta = db.getFlatMetadataForManga(manga.id!!).executeOnIO()?.raise<EHentaiSearchMetadata>() ?: return@forEach
|
||||
val meta = handler.awaitFlatMetadataForManga(manga.id!!)?.raise<EHentaiSearchMetadata>() ?: return@forEach
|
||||
// remove age flag
|
||||
meta.aged = false
|
||||
db.insertFlatMetadataAsync(meta.flatten()).await()
|
||||
handler.awaitInsertFlatMetadata(meta.flatten())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -73,69 +77,49 @@ object DebugFunctions {
|
||||
fun resetEHGalleriesForUpdater() {
|
||||
throttleManager.resetThrottle()
|
||||
runBlocking {
|
||||
val metadataManga = db.getFavoriteMangaWithMetadata().executeOnIO()
|
||||
val allManga = handler
|
||||
.awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
|
||||
|
||||
val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga ->
|
||||
if (manga.isEhBasedManga()) manga
|
||||
else null
|
||||
}.toList()
|
||||
val eh = sourceManager.get(EH_SOURCE_ID)
|
||||
val ex = sourceManager.get(EXH_SOURCE_ID)
|
||||
|
||||
allManga.forEach { manga ->
|
||||
throttleManager.throttle()
|
||||
(
|
||||
when (manga.source) {
|
||||
EH_SOURCE_ID -> eh
|
||||
EXH_SOURCE_ID -> ex
|
||||
else -> return@forEach
|
||||
}
|
||||
)?.getMangaDetails(manga.toMangaInfo())?.let { networkManga ->
|
||||
manga.copyFrom(networkManga.toSManga())
|
||||
manga.initialized = true
|
||||
db.insertManga(manga).executeOnIO()
|
||||
}
|
||||
|
||||
val networkManga = when (manga.source) {
|
||||
EH_SOURCE_ID -> eh
|
||||
EXH_SOURCE_ID -> ex
|
||||
else -> return@forEach
|
||||
}?.getMangaDetails(manga.toMangaInfo()) ?: return@forEach
|
||||
|
||||
updateManga.awaitUpdateFromSource(manga, networkManga, true)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun getEHMangaListWithAgedFlagInfo(): String {
|
||||
val galleries = mutableListOf(String())
|
||||
runBlocking {
|
||||
val metadataManga = db.getFavoriteMangaWithMetadata().executeOnIO()
|
||||
return runBlocking {
|
||||
val allManga = handler
|
||||
.awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
|
||||
|
||||
val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga ->
|
||||
if (manga.isEhBasedManga()) manga
|
||||
else null
|
||||
}.toList()
|
||||
|
||||
allManga.forEach { manga ->
|
||||
val meta = db.getFlatMetadataForManga(manga.id!!).executeOnIO()?.raise<EHentaiSearchMetadata>() ?: return@forEach
|
||||
galleries += "Aged: ${meta.aged}\t Title: ${manga.title}"
|
||||
allManga.map { manga ->
|
||||
val meta = handler.awaitFlatMetadataForManga(manga.id)?.raise<EHentaiSearchMetadata>() ?: return@map
|
||||
"Aged: ${meta.aged}\t Title: ${manga.title}"
|
||||
}
|
||||
}
|
||||
return galleries.joinToString(",\n")
|
||||
}.joinToString(",\n")
|
||||
}
|
||||
|
||||
fun countAgedFlagInEXHManga(): Int {
|
||||
var agedAmount = 0
|
||||
runBlocking {
|
||||
val metadataManga = db.getFavoriteMangaWithMetadata().executeOnIO()
|
||||
|
||||
val allManga = metadataManga.asFlow().cancellable().mapNotNull { manga ->
|
||||
if (manga.isEhBasedManga()) manga
|
||||
else null
|
||||
}.toList()
|
||||
|
||||
allManga.forEach { manga ->
|
||||
val meta = db.getFlatMetadataForManga(manga.id!!).executeOnIO()?.raise<EHentaiSearchMetadata>() ?: return@forEach
|
||||
if (meta.aged) {
|
||||
// remove age flag
|
||||
agedAmount++
|
||||
return runBlocking {
|
||||
handler
|
||||
.awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
|
||||
.count { manga ->
|
||||
val meta = handler.awaitFlatMetadataForManga(manga.id)
|
||||
?.raise<EHentaiSearchMetadata>()
|
||||
?: return@count false
|
||||
meta.aged
|
||||
}
|
||||
}
|
||||
}
|
||||
return agedAmount
|
||||
}
|
||||
|
||||
fun addAllMangaInDatabaseToLibrary() {
|
||||
@ -154,7 +138,7 @@ object DebugFunctions {
|
||||
}
|
||||
}
|
||||
|
||||
fun countMangaInDatabaseInLibrary() = db.getMangas().executeAsBlocking().count { it.favorite }
|
||||
fun countMangaInDatabaseInLibrary() = runBlocking { getFavorites.await().size }
|
||||
|
||||
fun countMangaInDatabaseNotInLibrary() = db.getMangas().executeAsBlocking().count { !it.favorite }
|
||||
|
||||
@ -166,7 +150,7 @@ object DebugFunctions {
|
||||
it.favorite && db.getSearchMetadataForManga(it.id!!).executeAsBlocking() == null
|
||||
}
|
||||
|
||||
fun clearSavedSearches() = runBlocking { database.await { saved_searchQueries.deleteAll() } }
|
||||
fun clearSavedSearches() = runBlocking { handler.await { saved_searchQueries.deleteAll() } }
|
||||
|
||||
fun listAllSources() = sourceManager.getCatalogueSources().joinToString("\n") {
|
||||
"${it.id}: ${it.name} (${it.lang.uppercase()})"
|
||||
|
@ -1,12 +1,23 @@
|
||||
package exh.eh
|
||||
|
||||
import android.content.Context
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.History
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||
import exh.util.executeOnIO
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.data.chapter.chapterMapper
|
||||
import eu.kanade.data.history.historyMapper
|
||||
import eu.kanade.domain.category.interactor.GetCategories
|
||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.chapter.model.ChapterUpdate
|
||||
import eu.kanade.domain.chapter.repository.ChapterRepository
|
||||
import eu.kanade.domain.history.interactor.RemoveHistoryById
|
||||
import eu.kanade.domain.history.interactor.UpsertHistory
|
||||
import eu.kanade.domain.history.model.History
|
||||
import eu.kanade.domain.history.model.HistoryUpdate
|
||||
import eu.kanade.domain.manga.interactor.GetMangaById
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.manga.model.MangaUpdate
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
@ -24,7 +35,15 @@ class EHentaiUpdateHelper(context: Context) {
|
||||
File(context.filesDir, "exh-plt.maftable"),
|
||||
GalleryEntry.Serializer(),
|
||||
)
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
private val handler: DatabaseHandler by injectLazy()
|
||||
private val getChapterByMangaId: GetChapterByMangaId by injectLazy()
|
||||
private val getMangaById: GetMangaById by injectLazy()
|
||||
private val updateManga: UpdateManga by injectLazy()
|
||||
private val setMangaCategories: SetMangaCategories by injectLazy()
|
||||
private val getCategories: GetCategories by injectLazy()
|
||||
private val chapterRepository: ChapterRepository by injectLazy()
|
||||
private val upsertHistory: UpsertHistory by injectLazy()
|
||||
private val removeHistoryById: RemoveHistoryById by injectLazy()
|
||||
|
||||
/**
|
||||
* @param chapters Cannot be an empty list!
|
||||
@ -36,7 +55,8 @@ class EHentaiUpdateHelper(context: Context) {
|
||||
val chainsFlow = flowOf(chapters)
|
||||
.map { chapterList ->
|
||||
chapterList.flatMap { chapter ->
|
||||
db.getChapters(chapter.url).executeOnIO().mapNotNull { it.manga_id }
|
||||
handler.awaitList { chaptersQueries.getChapterByUrl(chapter.url, chapterMapper) }
|
||||
.map { it.mangaId }
|
||||
}.distinct()
|
||||
}
|
||||
.map { mangaIds ->
|
||||
@ -44,13 +64,13 @@ class EHentaiUpdateHelper(context: Context) {
|
||||
.mapNotNull { mangaId ->
|
||||
coroutineScope {
|
||||
val manga = async(Dispatchers.IO) {
|
||||
db.getManga(mangaId).executeAsBlocking()
|
||||
getMangaById.await(mangaId)
|
||||
}
|
||||
val chapterList = async(Dispatchers.IO) {
|
||||
db.getChapters(mangaId).executeAsBlocking()
|
||||
getChapterByMangaId.await(mangaId)
|
||||
}
|
||||
val history = async(Dispatchers.IO) {
|
||||
db.getHistoryByMangaId(mangaId).executeAsBlocking()
|
||||
handler.awaitList { historyQueries.getHistoryByMangaId(mangaId, historyMapper) }
|
||||
}
|
||||
ChapterChain(
|
||||
manga.await() ?: return@coroutineScope null,
|
||||
@ -64,66 +84,66 @@ class EHentaiUpdateHelper(context: Context) {
|
||||
|
||||
// Accept oldest chain
|
||||
val chainsWithAccepted = chainsFlow.map { chains ->
|
||||
val acceptedChain = chains.minByOrNull { it.manga.id!! }!!
|
||||
val acceptedChain = chains.minBy { it.manga.id }
|
||||
|
||||
acceptedChain to chains
|
||||
}
|
||||
|
||||
return chainsWithAccepted.map { (accepted, chains) ->
|
||||
val toDiscard = chains.filter { it.manga.favorite && it.manga.id != accepted.manga.id }
|
||||
val mangaUpdates = mutableListOf<MangaUpdate>()
|
||||
|
||||
val chainsAsChapters = chains.flatMap { it.chapters }
|
||||
val chainsAsHistory = chains.flatMap { it.history }
|
||||
|
||||
if (toDiscard.isNotEmpty()) {
|
||||
// Copy chain chapters to curChapters
|
||||
val (newChapters, new) = getChapterList(accepted, toDiscard, chainsAsChapters)
|
||||
val (history, urlHistory, deleteHistory) = getHistory(newChapters, chainsAsChapters, chainsAsHistory)
|
||||
val (chapterUpdates, newChapters, new) = getChapterList(accepted, toDiscard, chainsAsChapters)
|
||||
|
||||
toDiscard.forEach {
|
||||
it.manga.favorite = false
|
||||
it.manga.date_added = 0
|
||||
mangaUpdates += MangaUpdate(
|
||||
id = it.manga.id,
|
||||
favorite = false,
|
||||
dateAdded = 0,
|
||||
)
|
||||
}
|
||||
if (!accepted.manga.favorite) {
|
||||
accepted.manga.favorite = true
|
||||
accepted.manga.date_added = System.currentTimeMillis()
|
||||
mangaUpdates += MangaUpdate(
|
||||
id = accepted.manga.id,
|
||||
favorite = true,
|
||||
dateAdded = System.currentTimeMillis(),
|
||||
)
|
||||
}
|
||||
|
||||
val newAccepted = ChapterChain(accepted.manga, newChapters, history + urlHistory.map { it.second })
|
||||
val newAccepted = ChapterChain(accepted.manga, newChapters, emptyList())
|
||||
val rootsToMutate = toDiscard + newAccepted
|
||||
|
||||
db.inTransaction {
|
||||
// Apply changes to all manga
|
||||
db.insertMangas(rootsToMutate.map { it.manga }).executeAsBlocking()
|
||||
// Insert new chapters for accepted manga
|
||||
val chapterPutResults = db.insertChapters(newAccepted.chapters).executeAsBlocking().results()
|
||||
// Apply changes to all manga
|
||||
updateManga.awaitAll(mangaUpdates)
|
||||
// Insert new chapters for accepted manga
|
||||
chapterRepository.updateAll(chapterUpdates)
|
||||
chapterRepository.addAll(newChapters)
|
||||
|
||||
// Delete the duplicate history first
|
||||
if (deleteHistory.isNotEmpty()) {
|
||||
db.deleteHistoryIds(deleteHistory).executeAsBlocking()
|
||||
}
|
||||
// Get a updated history list
|
||||
val newHistory = urlHistory.mapNotNull { (url, history) ->
|
||||
val result = chapterPutResults.firstNotNullOfOrNull { (chapter, result) ->
|
||||
if (chapter.url == url) {
|
||||
result.insertedId()
|
||||
} else null
|
||||
}
|
||||
if (result != null) {
|
||||
history.chapter_id = result
|
||||
history
|
||||
} else null
|
||||
} + history
|
||||
// Copy the new history chapter ids
|
||||
db.updateHistoryChapterIds(newHistory).executeAsBlocking()
|
||||
val (newHistory, deleteHistory) = getHistory(getChapterByMangaId.await(accepted.manga.id), chainsAsChapters, chainsAsHistory)
|
||||
|
||||
// Copy categories from all chains to accepted manga
|
||||
val newCategories = rootsToMutate.flatMap {
|
||||
db.getCategoriesForManga(it.manga).executeAsBlocking()
|
||||
}.distinctBy { it.id }.map {
|
||||
MangaCategory.create(newAccepted.manga, it)
|
||||
// Delete the duplicate history first
|
||||
if (deleteHistory.isNotEmpty()) {
|
||||
deleteHistory.forEach {
|
||||
removeHistoryById.await(it)
|
||||
}
|
||||
db.setMangaCategories(newCategories, rootsToMutate.map { it.manga })
|
||||
}
|
||||
// Insert new history
|
||||
newHistory.forEach {
|
||||
upsertHistory.await(it)
|
||||
}
|
||||
|
||||
// Copy categories from all chains to accepted manga
|
||||
|
||||
val newCategories = rootsToMutate.flatMap {
|
||||
getCategories.await(it.manga.id).map { it.id }
|
||||
}.distinct()
|
||||
rootsToMutate.forEach {
|
||||
setMangaCategories.await(it.manga.id, newCategories)
|
||||
}
|
||||
|
||||
Triple(newAccepted, toDiscard, new)
|
||||
@ -140,105 +160,105 @@ class EHentaiUpdateHelper(context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
data class HistoryUpdates(
|
||||
val history: List<History>,
|
||||
val urlHistory: List<Pair<String, History>>,
|
||||
val historyToDelete: List<Long>,
|
||||
)
|
||||
|
||||
private fun getHistory(
|
||||
newChapters: List<Chapter>,
|
||||
fun getHistory(
|
||||
currentChapters: List<Chapter>,
|
||||
chainsAsChapters: List<Chapter>,
|
||||
chainsAsHistory: List<History>,
|
||||
): HistoryUpdates {
|
||||
val historyMap = chainsAsHistory
|
||||
.groupBy { history ->
|
||||
chainsAsChapters.find { it.id == history.chapter_id }?.url.orEmpty()
|
||||
}
|
||||
.filterKeys { it.isNotBlank() }
|
||||
val latestHistory = historyMap.mapValues { entry ->
|
||||
entry.value.maxByOrNull {
|
||||
it.time_read
|
||||
}!!
|
||||
}
|
||||
val oldHistory = historyMap.flatMap { entry ->
|
||||
val topEntry = entry.value.maxByOrNull {
|
||||
it.time_read
|
||||
}!!
|
||||
entry.value - topEntry
|
||||
}.mapNotNull { it.id }
|
||||
return HistoryUpdates(
|
||||
latestHistory.filter { (_, history) ->
|
||||
val oldChapter = chainsAsChapters.find { it.id == history.chapter_id }
|
||||
val newChapter = newChapters.find { it.url == oldChapter?.url }
|
||||
if (oldChapter != newChapter && newChapter?.id != null) {
|
||||
history.chapter_id = newChapter.id!!
|
||||
true
|
||||
} else false
|
||||
}.mapNotNull { it.value },
|
||||
latestHistory.mapNotNull { (url, history) ->
|
||||
val oldChapter = chainsAsChapters.find { it.id == history.chapter_id }
|
||||
val newChapter = newChapters.find { it.url == oldChapter?.url }
|
||||
if (oldChapter != newChapter && newChapter?.id == null) {
|
||||
url to history
|
||||
} else {
|
||||
null
|
||||
): Pair<List<HistoryUpdate>, List<Long>> {
|
||||
val history = chainsAsHistory.groupBy { history -> chainsAsChapters.find { it.id == history.chapterId }?.url }
|
||||
val newHistory = currentChapters.mapNotNull { chapter ->
|
||||
val newHistory = history[chapter.url]
|
||||
?.maxByOrNull {
|
||||
it.readAt?.time ?: 0
|
||||
}
|
||||
},
|
||||
oldHistory,
|
||||
)
|
||||
?.takeIf { it.chapterId != chapter.id && it.readAt != null }
|
||||
if (newHistory != null) {
|
||||
HistoryUpdate(chapter.id, newHistory.readAt!!, newHistory.readDuration)
|
||||
} else null
|
||||
}
|
||||
val currentChapterIds = currentChapters.map { it.id }
|
||||
val historyToDelete = chainsAsHistory.filterNot { it.chapterId in currentChapterIds }
|
||||
.map { it.id }
|
||||
return newHistory to historyToDelete
|
||||
}
|
||||
|
||||
private fun getChapterList(
|
||||
accepted: ChapterChain,
|
||||
toDiscard: List<ChapterChain>,
|
||||
chainsAsChapters: List<Chapter>,
|
||||
): Pair<List<Chapter>, Boolean> {
|
||||
): Triple<List<ChapterUpdate>, List<Chapter>, Boolean> {
|
||||
var new = false
|
||||
return toDiscard
|
||||
.flatMap { chain ->
|
||||
chain.chapters
|
||||
}
|
||||
.fold(accepted.chapters) { curChapters, chapter ->
|
||||
val existing = curChapters.find { it.url == chapter.url }
|
||||
val newLastPageRead = chainsAsChapters.maxOfOrNull { it.lastPageRead }
|
||||
|
||||
val newLastPageRead = chainsAsChapters.maxOfOrNull { it.last_page_read }
|
||||
|
||||
if (existing != null) {
|
||||
existing.read = existing.read || chapter.read
|
||||
existing.last_page_read = existing.last_page_read.coerceAtLeast(chapter.last_page_read)
|
||||
if (newLastPageRead != null && existing.last_page_read <= 0) {
|
||||
existing.last_page_read = newLastPageRead
|
||||
if (curChapters.any { it.url == chapter.url }) {
|
||||
curChapters.map {
|
||||
if (it.url == chapter.url) {
|
||||
val read = it.read || chapter.read
|
||||
var lastPageRead = it.lastPageRead.coerceAtLeast(chapter.lastPageRead)
|
||||
if (newLastPageRead != null && lastPageRead <= 0) {
|
||||
lastPageRead = newLastPageRead
|
||||
}
|
||||
val bookmark = it.bookmark || chapter.bookmark
|
||||
it.copy(
|
||||
read = read,
|
||||
lastPageRead = lastPageRead,
|
||||
bookmark = bookmark,
|
||||
)
|
||||
} else it
|
||||
}
|
||||
existing.bookmark = existing.bookmark || chapter.bookmark
|
||||
curChapters
|
||||
} else {
|
||||
new = true
|
||||
curChapters + Chapter.create().apply {
|
||||
manga_id = accepted.manga.id
|
||||
url = chapter.url
|
||||
name = chapter.name
|
||||
read = chapter.read
|
||||
bookmark = chapter.bookmark
|
||||
|
||||
last_page_read = chapter.last_page_read
|
||||
if (newLastPageRead != null && last_page_read <= 0) {
|
||||
last_page_read = newLastPageRead
|
||||
}
|
||||
|
||||
date_fetch = chapter.date_fetch
|
||||
date_upload = chapter.date_upload
|
||||
}
|
||||
curChapters + Chapter(
|
||||
id = -1,
|
||||
mangaId = accepted.manga.id,
|
||||
url = chapter.url,
|
||||
name = chapter.name,
|
||||
read = chapter.read,
|
||||
bookmark = chapter.bookmark,
|
||||
lastPageRead = if (newLastPageRead != null && chapter.lastPageRead <= 0) {
|
||||
newLastPageRead
|
||||
} else chapter.lastPageRead,
|
||||
dateFetch = chapter.dateFetch,
|
||||
dateUpload = chapter.dateUpload,
|
||||
chapterNumber = -1F,
|
||||
scanlator = null,
|
||||
sourceOrder = -1,
|
||||
)
|
||||
}
|
||||
}
|
||||
.sortedBy { it.date_upload }
|
||||
.sortedBy { it.dateUpload }
|
||||
.let { chapters ->
|
||||
chapters.onEachIndexed { index, chapter ->
|
||||
chapter.name = "v${index + 1}: " + chapter.name.substringAfter(" ")
|
||||
chapter.chapter_number = index + 1f
|
||||
chapter.source_order = chapters.lastIndex - index
|
||||
val updates = mutableListOf<ChapterUpdate>()
|
||||
val newChapters = mutableListOf<Chapter>()
|
||||
chapters.mapIndexed { index, chapter ->
|
||||
val name = "v${index + 1}: " + chapter.name.substringAfter(" ")
|
||||
val chapterNumber = index + 1f
|
||||
val sourceOrder = chapters.lastIndex - index.toLong()
|
||||
when (chapter.id) {
|
||||
-1L -> newChapters.add(
|
||||
chapter.copy(
|
||||
name = name,
|
||||
chapterNumber = chapterNumber,
|
||||
sourceOrder = sourceOrder,
|
||||
),
|
||||
)
|
||||
else -> updates.add(
|
||||
ChapterUpdate(
|
||||
id = chapter.id,
|
||||
name = name.takeUnless { chapter.name == it },
|
||||
chapterNumber = chapterNumber.takeUnless { chapter.chapterNumber == it },
|
||||
sourceOrder = sourceOrder.takeUnless { chapter.sourceOrder == it },
|
||||
),
|
||||
)
|
||||
}
|
||||
}
|
||||
} to new
|
||||
Triple(updates.toList(), newChapters.toList(), new)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -11,29 +11,35 @@ import androidx.work.WorkManager
|
||||
import androidx.work.WorkerParameters
|
||||
import com.elvishew.xlog.Logger
|
||||
import com.elvishew.xlog.XLog
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.data.manga.mangaMapper
|
||||
import eu.kanade.domain.chapter.interactor.GetChapterByMangaId
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.chapter.model.toDbChapter
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.manga.model.toDbManga
|
||||
import eu.kanade.domain.manga.model.toMangaInfo
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
|
||||
import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING
|
||||
import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.model.toSChapter
|
||||
import eu.kanade.tachiyomi.source.model.toSManga
|
||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||
import eu.kanade.tachiyomi.util.system.isConnectedToWifi
|
||||
import exh.debug.DebugToggles
|
||||
import exh.eh.EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION
|
||||
import exh.log.xLog
|
||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||
import exh.metadata.metadata.base.awaitFlatMetadataForManga
|
||||
import exh.metadata.metadata.base.insertFlatMetadataAsync
|
||||
import exh.source.EH_SOURCE_ID
|
||||
import exh.source.EXH_SOURCE_ID
|
||||
import exh.source.isEhBasedManga
|
||||
import exh.util.cancellable
|
||||
import exh.util.executeOnIO
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.single
|
||||
@ -49,10 +55,14 @@ import kotlin.time.Duration.Companion.days
|
||||
class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) :
|
||||
CoroutineWorker(context, workerParams) {
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
private val handler: DatabaseHandler by injectLazy()
|
||||
private val prefs: PreferencesHelper by injectLazy()
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
private val updateHelper: EHentaiUpdateHelper by injectLazy()
|
||||
private val logger: Logger = xLog()
|
||||
private val updateManga: UpdateManga by injectLazy()
|
||||
private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy()
|
||||
private val getChapterByMangaId: GetChapterByMangaId by injectLazy()
|
||||
|
||||
private val updateNotifier by lazy { LibraryUpdateNotifier(context) }
|
||||
|
||||
@ -76,7 +86,7 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
||||
val startTime = System.currentTimeMillis()
|
||||
|
||||
logger.d("Finding manga with metadata...")
|
||||
val metadataManga = db.getFavoriteMangaWithMetadata().executeOnIO()
|
||||
val metadataManga = handler.awaitList { mangasQueries.getEhMangaWithMetadata(EH_SOURCE_ID, EXH_SOURCE_ID, mangaMapper) }
|
||||
|
||||
logger.d("Filtering manga and raising metadata...")
|
||||
val curTime = System.currentTimeMillis()
|
||||
@ -85,7 +95,7 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val meta = db.getFlatMetadataForManga(manga.id!!).executeOnIO()
|
||||
val meta = handler.awaitFlatMetadataForManga(manga.id)
|
||||
?: return@mapNotNull null
|
||||
|
||||
val raisedMeta = meta.raise<EHentaiSearchMetadata>()
|
||||
@ -95,8 +105,8 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
||||
return@mapNotNull null
|
||||
}
|
||||
|
||||
val chapter = db.getChapters(manga.id!!).executeOnIO().minByOrNull {
|
||||
it.date_upload
|
||||
val chapter = getChapterByMangaId.await(manga.id).minByOrNull {
|
||||
it.dateUpload
|
||||
}
|
||||
|
||||
UpdateEntry(manga, raisedMeta, chapter)
|
||||
@ -176,8 +186,8 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
||||
updatedManga += acceptedRoot.manga to new.toTypedArray()
|
||||
}
|
||||
|
||||
modifiedThisIteration += acceptedRoot.manga.id!!
|
||||
modifiedThisIteration += discardedRoots.map { it.manga.id!! }
|
||||
modifiedThisIteration += acceptedRoot.manga.id
|
||||
modifiedThisIteration += discardedRoots.map { it.manga.id }
|
||||
updatedThisIteration++
|
||||
}
|
||||
} finally {
|
||||
@ -192,7 +202,7 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
||||
)
|
||||
|
||||
if (updatedManga.isNotEmpty()) {
|
||||
updateNotifier.showUpdateNotifications(updatedManga)
|
||||
updateNotifier.showUpdateNotifications(updatedManga.map { it.first.toDbManga() to it.second.map { it.toDbChapter() }.toTypedArray() })
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -204,17 +214,16 @@ class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerPara
|
||||
|
||||
try {
|
||||
val updatedManga = source.getMangaDetails(manga.toMangaInfo())
|
||||
manga.copyFrom(updatedManga.toSManga())
|
||||
db.insertManga(manga).executeOnIO()
|
||||
updateManga.awaitUpdateFromSource(manga, updatedManga, false)
|
||||
|
||||
val newChapters = source.getChapterList(manga.toMangaInfo())
|
||||
.map { it.toSChapter() }
|
||||
|
||||
val (new, _) = syncChaptersWithSource(newChapters, manga, source) // Not suspending, but does block, maybe fix this?
|
||||
return new to db.getChapters(manga).executeOnIO()
|
||||
val (new, _) = syncChaptersWithSource.await(newChapters, manga, source)
|
||||
return new to getChapterByMangaId.await(manga.id)
|
||||
} catch (t: Throwable) {
|
||||
if (t is EHentai.GalleryNotFoundException) {
|
||||
val meta = db.getFlatMetadataForManga(manga.id!!).executeOnIO()?.raise<EHentaiSearchMetadata>()
|
||||
val meta = handler.awaitFlatMetadataForManga(manga.id)?.raise<EHentaiSearchMetadata>()
|
||||
if (meta != null) {
|
||||
// Age dead galleries
|
||||
logger.d("Aged %s - notfound", manga.id)
|
||||
|
@ -3,18 +3,23 @@ package exh.favorites
|
||||
import android.content.Context
|
||||
import android.net.wifi.WifiManager
|
||||
import android.os.PowerManager
|
||||
import eu.kanade.data.AndroidDatabaseHandler
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.domain.category.interactor.GetCategories
|
||||
import eu.kanade.domain.category.interactor.SetMangaCategories
|
||||
import eu.kanade.domain.category.model.Category
|
||||
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.MangaCategory
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainManga
|
||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
import eu.kanade.tachiyomi.network.await
|
||||
import eu.kanade.tachiyomi.source.SourceManager
|
||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||
import eu.kanade.tachiyomi.util.lang.launchUI
|
||||
import eu.kanade.tachiyomi.util.lang.withIOContext
|
||||
import eu.kanade.tachiyomi.util.lang.withUIContext
|
||||
import eu.kanade.tachiyomi.util.system.powerManager
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.GalleryAddEvent
|
||||
@ -29,13 +34,11 @@ import exh.source.isEhBasedManga
|
||||
import exh.util.ignore
|
||||
import exh.util.wifiManager
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.DelicateCoroutinesApi
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.newSingleThreadContext
|
||||
import okhttp3.FormBody
|
||||
import okhttp3.Request
|
||||
import uy.kohesive.injekt.Injekt
|
||||
@ -43,16 +46,18 @@ import uy.kohesive.injekt.api.get
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlin.time.Duration.Companion.seconds
|
||||
|
||||
// TODO only apply database changes after sync
|
||||
class FavoritesSyncHelper(val context: Context) {
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
private val handler: DatabaseHandler by injectLazy()
|
||||
private val getCategories: GetCategories by injectLazy()
|
||||
private val getMangaByUrlAndSource: GetMangaByUrlAndSource by injectLazy()
|
||||
private val updateManga: UpdateManga by injectLazy()
|
||||
private val setMangaCategories: SetMangaCategories by injectLazy()
|
||||
|
||||
private val prefs: PreferencesHelper by injectLazy()
|
||||
|
||||
private val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||
|
||||
@OptIn(DelicateCoroutinesApi::class)
|
||||
private val dispatcher = newSingleThreadContext("Favorites-sync-worker")
|
||||
|
||||
private val exh by lazy {
|
||||
Injekt.get<SourceManager>().get(EXH_SOURCE_ID) as? EHentai
|
||||
?: EHentai(0, true, context)
|
||||
@ -79,7 +84,7 @@ class FavoritesSyncHelper(val context: Context) {
|
||||
|
||||
status.value = FavoritesSyncStatus.Initializing(context)
|
||||
|
||||
scope.launch(dispatcher) { beginSync() }
|
||||
scope.launch(Dispatchers.IO) { beginSync() }
|
||||
}
|
||||
|
||||
private suspend fun beginSync() {
|
||||
@ -91,14 +96,14 @@ class FavoritesSyncHelper(val context: Context) {
|
||||
|
||||
// Validate library state
|
||||
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_verifying_library), context = context)
|
||||
val libraryManga = db.getLibraryMangas().executeAsBlocking()
|
||||
val libraryManga = handler.awaitList { (handler as AndroidDatabaseHandler).getLibraryQuery() }
|
||||
val seenManga = HashSet<Long>(libraryManga.size)
|
||||
libraryManga.forEach {
|
||||
if (!it.isEhBasedManga()) return@forEach
|
||||
|
||||
if (it.id in seenManga) {
|
||||
val inCategories = db.getCategoriesForManga(it).executeAsBlocking()
|
||||
status.value = FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories(it, inCategories, context)
|
||||
val inCategories = getCategories.await(it.id!!)
|
||||
status.value = FavoritesSyncStatus.BadLibraryState.MangaInMultipleCategories(it.toDomainManga()!!, inCategories, context)
|
||||
|
||||
logger.w(context.getString(R.string.favorites_sync_manga_multiple_categories_error, it.id))
|
||||
return
|
||||
@ -139,32 +144,30 @@ class FavoritesSyncHelper(val context: Context) {
|
||||
// Do not update galleries while syncing favorites
|
||||
EHentaiUpdateWorker.cancelBackground(context)
|
||||
|
||||
db.inTransaction {
|
||||
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_remote_changes), context = context)
|
||||
val remoteChanges = storage.getChangedRemoteEntries(favorites.first)
|
||||
val localChanges = if (prefs.exhReadOnlySync().get()) {
|
||||
null // Do not build local changes if they are not going to be applied
|
||||
} else {
|
||||
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_local_changes), context = context)
|
||||
storage.getChangedDbEntries()
|
||||
}
|
||||
|
||||
// Apply remote categories
|
||||
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_syncing_category_names), context = context)
|
||||
applyRemoteCategories(favorites.second)
|
||||
|
||||
// Apply change sets
|
||||
applyChangeSetToLocal(errorList, remoteChanges)
|
||||
if (localChanges != null) {
|
||||
applyChangeSetToRemote(errorList, localChanges)
|
||||
}
|
||||
|
||||
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_cleaning_up), context = context)
|
||||
storage.snapshotEntries()
|
||||
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_remote_changes), context = context)
|
||||
val remoteChanges = storage.getChangedRemoteEntries(favorites.first)
|
||||
val localChanges = if (prefs.exhReadOnlySync().get()) {
|
||||
null // Do not build local changes if they are not going to be applied
|
||||
} else {
|
||||
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_calculating_local_changes), context = context)
|
||||
storage.getChangedDbEntries()
|
||||
}
|
||||
|
||||
launchUI {
|
||||
context.toast(context.getString(R.string.favorites_sync_complete))
|
||||
// Apply remote categories
|
||||
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_syncing_category_names), context = context)
|
||||
applyRemoteCategories(favorites.second)
|
||||
|
||||
// Apply change sets
|
||||
applyChangeSetToLocal(errorList, remoteChanges)
|
||||
if (localChanges != null) {
|
||||
applyChangeSetToRemote(errorList, localChanges)
|
||||
}
|
||||
|
||||
status.value = FavoritesSyncStatus.Processing(context.getString(R.string.favorites_sync_cleaning_up), context = context)
|
||||
storage.snapshotEntries()
|
||||
|
||||
withUIContext {
|
||||
context.toast(R.string.favorites_sync_complete)
|
||||
}
|
||||
} catch (e: IgnoredException) {
|
||||
// Do not display error as this error has already been reported
|
||||
@ -196,46 +199,33 @@ class FavoritesSyncHelper(val context: Context) {
|
||||
}
|
||||
}
|
||||
|
||||
private fun applyRemoteCategories(categories: List<String>) {
|
||||
val localCategories = db.getCategories().executeAsBlocking()
|
||||
private suspend fun applyRemoteCategories(categories: List<String>) {
|
||||
val localCategories = getCategories.await()
|
||||
|
||||
val newLocalCategories = localCategories.toMutableList()
|
||||
|
||||
var changed = false
|
||||
|
||||
categories.forEachIndexed { index, remote ->
|
||||
val local = localCategories.getOrElse(index) {
|
||||
changed = true
|
||||
val newCategoryId = handler.awaitOne(true) {
|
||||
categoriesQueries.insert(remote, index.toLong(), 0L, emptyList())
|
||||
categoriesQueries.selectLastInsertedRowId()
|
||||
}
|
||||
Category(newCategoryId, remote, index.toLong(), 0L, emptyList())
|
||||
.also { newLocalCategories += it }
|
||||
}
|
||||
|
||||
Category.create(remote).apply {
|
||||
order = index
|
||||
|
||||
// Going through categories list from front to back
|
||||
// If category does not exist, list size <= category index
|
||||
// Thus, we can just add it here and not worry about indexing
|
||||
newLocalCategories += this
|
||||
// Ensure consistent ordering and naming
|
||||
if (local.name != remote || local.order != index.toLong()) {
|
||||
handler.await {
|
||||
categoriesQueries.update(
|
||||
categoryId = local.id,
|
||||
order = index.toLong().takeIf { it != local.order },
|
||||
name = remote.takeIf { it != local.name },
|
||||
flags = null,
|
||||
mangaOrder = null,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (local.name != remote) {
|
||||
changed = true
|
||||
|
||||
local.name = remote
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure consistent ordering
|
||||
newLocalCategories.forEachIndexed { index, category ->
|
||||
if (category.order != index) {
|
||||
changed = true
|
||||
|
||||
category.order = index
|
||||
}
|
||||
}
|
||||
|
||||
// Only insert categories if changed
|
||||
if (changed) {
|
||||
db.insertCategories(newLocalCategories).executeAsBlocking()
|
||||
}
|
||||
}
|
||||
|
||||
@ -339,27 +329,25 @@ class FavoritesSyncHelper(val context: Context) {
|
||||
|
||||
// Consider both EX and EH sources
|
||||
listOf(
|
||||
db.getManga(url, EXH_SOURCE_ID),
|
||||
db.getManga(url, EH_SOURCE_ID),
|
||||
EXH_SOURCE_ID,
|
||||
EH_SOURCE_ID,
|
||||
).forEach {
|
||||
val manga = it.executeAsBlocking()
|
||||
val manga = getMangaByUrlAndSource.await(url, it)
|
||||
|
||||
if (manga?.favorite == true) {
|
||||
manga.favorite = false
|
||||
manga.date_added = 0
|
||||
db.updateMangaFavorite(manga).executeAsBlocking()
|
||||
updateManga.awaitUpdateFavorite(manga.id, false)
|
||||
removedManga += manga
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Can't do too many DB OPs in one go
|
||||
removedManga.chunked(10).forEach {
|
||||
db.deleteOldMangasCategories(it).executeAsBlocking()
|
||||
removedManga.forEach {
|
||||
setMangaCategories.await(it.id, emptyList())
|
||||
}
|
||||
|
||||
val insertedMangaCategories = mutableListOf<Pair<MangaCategory, Manga>>()
|
||||
val categories = db.getCategories().executeAsBlocking()
|
||||
val insertedMangaCategories = mutableListOf<Pair<Long, Manga>>()
|
||||
val categories = getCategories.await()
|
||||
|
||||
// Apply additions
|
||||
throttleManager.resetThrottle()
|
||||
@ -402,18 +390,13 @@ class FavoritesSyncHelper(val context: Context) {
|
||||
throw IgnoredException()
|
||||
}
|
||||
} else if (result is GalleryAddEvent.Success) {
|
||||
insertedMangaCategories += MangaCategory.create(
|
||||
result.manga,
|
||||
categories[it.category],
|
||||
) to result.manga
|
||||
insertedMangaCategories += categories[it.category].id to result.manga
|
||||
}
|
||||
}
|
||||
|
||||
// Can't do too many DB OPs in one go
|
||||
insertedMangaCategories.chunked(10).map { mangaCategories ->
|
||||
mangaCategories.map { it.first } to mangaCategories.map { it.second }
|
||||
}.forEach {
|
||||
db.setMangaCategories(it.first, it.second)
|
||||
insertedMangaCategories.forEach { (category, manga) ->
|
||||
setMangaCategories.await(manga.id, listOf(category))
|
||||
}
|
||||
}
|
||||
|
||||
@ -424,7 +407,6 @@ class FavoritesSyncHelper(val context: Context) {
|
||||
|
||||
fun onDestroy() {
|
||||
scope.cancel()
|
||||
dispatcher.close()
|
||||
}
|
||||
|
||||
companion object {
|
||||
|
@ -1,56 +1,77 @@
|
||||
package exh.favorites
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.data.exh.favoriteEntryMapper
|
||||
import eu.kanade.domain.category.interactor.GetCategories
|
||||
import eu.kanade.domain.manga.interactor.GetFavorites
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.toDomainManga
|
||||
import eu.kanade.tachiyomi.source.online.all.EHentai
|
||||
import exh.favorites.sql.models.FavoriteEntry
|
||||
import exh.metadata.metadata.EHentaiSearchMetadata
|
||||
import exh.source.isEhBasedManga
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.asFlow
|
||||
import kotlinx.coroutines.flow.filter
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.flow.mapNotNull
|
||||
import kotlinx.coroutines.flow.toList
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
|
||||
class LocalFavoritesStorage {
|
||||
private val db: DatabaseHelper by injectLazy()
|
||||
private val handler: DatabaseHandler by injectLazy()
|
||||
private val getFavorites: GetFavorites by injectLazy()
|
||||
private val getCategories: GetCategories by injectLazy()
|
||||
|
||||
fun getChangedDbEntries() = db.getFavoriteMangas()
|
||||
.executeAsBlocking()
|
||||
.asSequence()
|
||||
suspend fun getChangedDbEntries() = getFavorites.await()
|
||||
.asFlow()
|
||||
.loadDbCategories()
|
||||
.parseToFavoriteEntries()
|
||||
.getChangedEntries()
|
||||
|
||||
fun getChangedRemoteEntries(entries: List<EHentai.ParsedManga>) = entries
|
||||
.asSequence()
|
||||
suspend fun getChangedRemoteEntries(entries: List<EHentai.ParsedManga>) = entries
|
||||
.asFlow()
|
||||
.map {
|
||||
it.fav to it.manga.apply {
|
||||
id = -1
|
||||
favorite = true
|
||||
date_added = System.currentTimeMillis()
|
||||
}
|
||||
}.toDomainManga()!!
|
||||
}
|
||||
.parseToFavoriteEntries()
|
||||
.getChangedEntries()
|
||||
|
||||
fun snapshotEntries() {
|
||||
val dbMangas = db.getFavoriteMangas()
|
||||
.executeAsBlocking()
|
||||
.asSequence()
|
||||
suspend fun snapshotEntries() {
|
||||
val dbMangas = getFavorites.await()
|
||||
.asFlow()
|
||||
.loadDbCategories()
|
||||
.parseToFavoriteEntries()
|
||||
|
||||
// Delete old snapshot
|
||||
db.deleteAllFavoriteEntries().executeAsBlocking()
|
||||
handler.await { eh_favoritesQueries.deleteAll() }
|
||||
|
||||
// Insert new snapshots
|
||||
db.insertFavoriteEntries(dbMangas.toList()).executeAsBlocking()
|
||||
handler.await(true) {
|
||||
dbMangas.toList().forEach {
|
||||
eh_favoritesQueries.insertEhFavorites(
|
||||
it.id,
|
||||
it.title,
|
||||
it.gid,
|
||||
it.token,
|
||||
it.category.toLong(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun clearSnapshots() {
|
||||
db.deleteAllFavoriteEntries().executeAsBlocking()
|
||||
suspend fun clearSnapshots() {
|
||||
handler.await { eh_favoritesQueries.deleteAll() }
|
||||
}
|
||||
|
||||
private fun Sequence<FavoriteEntry>.getChangedEntries(): ChangeSet {
|
||||
private suspend fun Flow<FavoriteEntry>.getChangedEntries(): ChangeSet {
|
||||
val terminated = toList()
|
||||
|
||||
val databaseEntries = db.getFavoriteEntries().executeAsBlocking()
|
||||
val databaseEntries = handler.awaitList { eh_favoritesQueries.selectAll(favoriteEntryMapper) }
|
||||
|
||||
val added = terminated.filter {
|
||||
queryListForEntry(databaseEntries, it) == null
|
||||
@ -74,11 +95,11 @@ class LocalFavoritesStorage {
|
||||
it.category == entry.category
|
||||
}
|
||||
|
||||
private fun Sequence<Manga>.loadDbCategories(): Sequence<Pair<Int, Manga>> {
|
||||
val dbCategories = db.getCategories().executeAsBlocking()
|
||||
private suspend fun Flow<Manga>.loadDbCategories(): Flow<Pair<Int, Manga>> {
|
||||
val dbCategories = getCategories.await()
|
||||
|
||||
return filter(::validateDbManga).mapNotNull {
|
||||
val category = db.getCategoriesForManga(it).executeAsBlocking()
|
||||
val category = getCategories.await(it.id)
|
||||
|
||||
dbCategories.indexOf(
|
||||
category.firstOrNull()
|
||||
@ -87,12 +108,12 @@ class LocalFavoritesStorage {
|
||||
}
|
||||
}
|
||||
|
||||
private fun Sequence<Pair<Int, Manga>>.parseToFavoriteEntries() =
|
||||
private fun Flow<Pair<Int, Manga>>.parseToFavoriteEntries() =
|
||||
filter { (_, manga) ->
|
||||
validateDbManga(manga)
|
||||
}.mapNotNull { (categoryId, manga) ->
|
||||
FavoriteEntry(
|
||||
title = manga.originalTitle,
|
||||
title = manga.ogTitle,
|
||||
gid = EHentaiSearchMetadata.galleryId(manga.url),
|
||||
token = EHentaiSearchMetadata.galleryToken(manga.url),
|
||||
category = categoryId,
|
||||
|
@ -1,6 +1,7 @@
|
||||
package exh.md.handlers
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.data.DatabaseHandler
|
||||
import eu.kanade.domain.manga.interactor.GetMangaByUrlAndSource
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import exh.log.xLogE
|
||||
import exh.md.dto.ChapterDataDto
|
||||
@ -12,8 +13,8 @@ import exh.md.utils.MdUtil
|
||||
import exh.md.utils.asMdMap
|
||||
import exh.metadata.metadata.MangaDexSearchMetadata
|
||||
import exh.metadata.metadata.base.RaisedTag
|
||||
import exh.metadata.metadata.base.getFlatMetadataForManga
|
||||
import exh.metadata.metadata.base.insertFlatMetadata
|
||||
import exh.metadata.metadata.base.awaitFlatMetadataForManga
|
||||
import exh.metadata.metadata.base.awaitInsertFlatMetadata
|
||||
import exh.util.capitalize
|
||||
import exh.util.floor
|
||||
import exh.util.nullIfEmpty
|
||||
@ -25,7 +26,8 @@ import java.util.Locale
|
||||
class ApiMangaParser(
|
||||
private val lang: String,
|
||||
) {
|
||||
val db: DatabaseHelper by injectLazy()
|
||||
private val handler: DatabaseHandler by injectLazy()
|
||||
private val getMangaByUrlAndSource: GetMangaByUrlAndSource by injectLazy()
|
||||
|
||||
val metaClass = MangaDexSearchMetadata::class
|
||||
|
||||
@ -37,23 +39,23 @@ class ApiMangaParser(
|
||||
}?.call()
|
||||
?: error("Could not find no-args constructor for meta class: ${metaClass.qualifiedName}!")
|
||||
|
||||
fun parseToManga(
|
||||
suspend fun parseToManga(
|
||||
manga: MangaInfo,
|
||||
sourceId: Long,
|
||||
input: MangaDto,
|
||||
simpleChapters: List<String>,
|
||||
statistics: StatisticsMangaDto?,
|
||||
): MangaInfo {
|
||||
val mangaId = db.getManga(manga.key, sourceId).executeAsBlocking()?.id
|
||||
val mangaId = getMangaByUrlAndSource.await(manga.key, sourceId)?.id
|
||||
val metadata = if (mangaId != null) {
|
||||
val flatMetadata = db.getFlatMetadataForManga(mangaId).executeAsBlocking()
|
||||
val flatMetadata = handler.awaitFlatMetadataForManga(mangaId)
|
||||
flatMetadata?.raise(metaClass) ?: newMetaInstance()
|
||||
} else newMetaInstance()
|
||||
|
||||
parseIntoMetadata(metadata, input, simpleChapters, statistics)
|
||||
if (mangaId != null) {
|
||||
metadata.mangaId = mangaId
|
||||
db.insertFlatMetadata(metadata.flatten())
|
||||
handler.awaitInsertFlatMetadata(metadata.flatten())
|
||||
}
|
||||
|
||||
return metadata.createMangaInfo(manga)
|
||||
|
@ -1,7 +1,7 @@
|
||||
package exh.md.similar
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.database.models.toMangaInfo
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.domain.manga.model.toMangaInfo
|
||||
import eu.kanade.tachiyomi.source.model.MetadataMangasPage
|
||||
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.NoResultsException
|
||||
|
@ -1,26 +1,30 @@
|
||||
package exh.md.similar
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.domain.manga.interactor.GetMangaById
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.source.online.all.MangaDex
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.Pager
|
||||
import exh.source.getMainSource
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Presenter of [MangaDexSimilarController]. Inherit BrowseCataloguePresenter.
|
||||
*/
|
||||
class MangaDexSimilarPresenter(val mangaId: Long, sourceId: Long) : BrowseSourcePresenter(sourceId) {
|
||||
class MangaDexSimilarPresenter(
|
||||
val mangaId: Long,
|
||||
sourceId: Long,
|
||||
private val getMangaById: GetMangaById = Injekt.get(),
|
||||
) : BrowseSourcePresenter(sourceId) {
|
||||
|
||||
var manga: Manga? = null
|
||||
|
||||
val db: DatabaseHelper by injectLazy()
|
||||
|
||||
override fun createPager(query: String, filters: FilterList): Pager {
|
||||
val sourceAsMangaDex = source.getMainSource() as MangaDex
|
||||
this.manga = db.getManga(mangaId).executeAsBlocking()
|
||||
this.manga = runBlocking { getMangaById.await(mangaId) }
|
||||
return MangaDexSimilarPager(manga!!, sourceAsMangaDex)
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
package exh.recs
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.network.GET
|
||||
import eu.kanade.tachiyomi.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.network.POST
|
||||
@ -203,7 +203,7 @@ open class RecommendsPager(
|
||||
|
||||
val recs = apiList.firstNotNullOfOrNull { (key, api) ->
|
||||
try {
|
||||
val recs = api.getRecsBySearch(manga.originalTitle)
|
||||
val recs = api.getRecsBySearch(manga.ogTitle)
|
||||
logcat { key.toString() + " > Results: " + recs.count() }
|
||||
recs
|
||||
} catch (e: Exception) {
|
||||
|
@ -1,23 +1,27 @@
|
||||
package exh.recs
|
||||
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.domain.manga.interactor.GetMangaById
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourcePresenter
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.Pager
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
|
||||
/**
|
||||
* Presenter of [RecommendsController]. Inherit BrowseCataloguePresenter.
|
||||
*/
|
||||
class RecommendsPresenter(val mangaId: Long, sourceId: Long) : BrowseSourcePresenter(sourceId) {
|
||||
class RecommendsPresenter(
|
||||
val mangaId: Long,
|
||||
sourceId: Long,
|
||||
private val getMangaById: GetMangaById = Injekt.get(),
|
||||
) : BrowseSourcePresenter(sourceId) {
|
||||
|
||||
var manga: Manga? = null
|
||||
|
||||
val db: DatabaseHelper by injectLazy()
|
||||
|
||||
override fun createPager(query: String, filters: FilterList): Pager {
|
||||
this.manga = db.getManga(mangaId).executeAsBlocking()
|
||||
this.manga = runBlocking { getMangaById.await(mangaId) }
|
||||
return RecommendsPager(manga!!)
|
||||
}
|
||||
}
|
||||
|
@ -6,9 +6,9 @@ import android.view.MenuItem
|
||||
import androidx.core.view.isVisible
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import eu.kanade.domain.chapter.model.Chapter
|
||||
import eu.kanade.domain.manga.model.Manga
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.databinding.EhActivityInterceptBinding
|
||||
import eu.kanade.tachiyomi.source.online.UrlImportableSource
|
||||
import eu.kanade.tachiyomi.ui.base.activity.BaseActivity
|
||||
@ -70,7 +70,7 @@ class InterceptActivity : BaseActivity() {
|
||||
onBackPressed()
|
||||
startActivity(
|
||||
if (it.chapter != null) {
|
||||
ReaderActivity.newIntent(this, it.manga.id!!, it.chapter.id!!)
|
||||
ReaderActivity.newIntent(this, it.manga.id, it.chapter.id)
|
||||
} else {
|
||||
Intent(this, MainActivity::class.java)
|
||||
.setAction(MainActivity.SHORTCUT_MANGA)
|
||||
@ -133,9 +133,7 @@ class InterceptActivity : BaseActivity() {
|
||||
val result = galleryAdder.addGallery(this@InterceptActivity, gallery, forceSource = source)
|
||||
|
||||
status.value = when (result) {
|
||||
is GalleryAddEvent.Success -> result.manga.id?.let {
|
||||
InterceptResult.Success(it, result.manga, result.chapter)
|
||||
} ?: InterceptResult.Failure(getString(R.string.manga_id_is_null))
|
||||
is GalleryAddEvent.Success -> InterceptResult.Success(result.manga.id, result.manga, result.chapter)
|
||||
is GalleryAddEvent.Fail -> InterceptResult.Failure(result.logMessage)
|
||||
}
|
||||
}
|
||||
|
@ -8,6 +8,7 @@ import eu.kanade.tachiyomi.ui.reader.setting.ReadingModeType
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.util.Locale
|
||||
import eu.kanade.domain.manga.model.Manga as DomainManga
|
||||
|
||||
fun Manga.mangaType(context: Context): String {
|
||||
return context.getString(
|
||||
@ -48,6 +49,30 @@ fun Manga.mangaType(sourceName: String? = Injekt.get<SourceManager>().get(source
|
||||
}
|
||||
}
|
||||
|
||||
fun DomainManga.mangaType(sourceName: String? = Injekt.get<SourceManager>().get(source)?.name): MangaType {
|
||||
val currentTags = genre.orEmpty()
|
||||
return when {
|
||||
currentTags.any { tag -> isMangaTag(tag) } -> {
|
||||
MangaType.TYPE_MANGA
|
||||
}
|
||||
currentTags.any { tag -> isWebtoonTag(tag) } || sourceName?.let { isWebtoonSource(it) } == true -> {
|
||||
MangaType.TYPE_WEBTOON
|
||||
}
|
||||
currentTags.any { tag -> isComicTag(tag) } || sourceName?.let { isComicSource(it) } == true -> {
|
||||
MangaType.TYPE_COMIC
|
||||
}
|
||||
currentTags.any { tag -> isManhuaTag(tag) } || sourceName?.let { isManhuaSource(it) } == true -> {
|
||||
MangaType.TYPE_MANHUA
|
||||
}
|
||||
currentTags.any { tag -> isManhwaTag(tag) } || sourceName?.let { isManhwaSource(it) } == true -> {
|
||||
MangaType.TYPE_MANHWA
|
||||
}
|
||||
else -> {
|
||||
MangaType.TYPE_MANGA
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The type the reader should use. Different from manga type as certain manga has different
|
||||
* read types
|
||||
|
@ -24,7 +24,7 @@ fun UrlImportableSource.urlImportFetchSearchManga(context: Context, query: Strin
|
||||
.map { res ->
|
||||
MangasPage(
|
||||
if (res is GalleryAddEvent.Success) {
|
||||
listOf(res.manga)
|
||||
listOf(res.manga.toSManga())
|
||||
} else {
|
||||
emptyList()
|
||||
},
|
||||
|
@ -30,6 +30,9 @@ insert:
|
||||
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,:thumbnail_url,:favorite,:last_update,:next_update,:initialized,:viewer,:chapter_flags,:cover_last_modified,:date_added);
|
||||
|
||||
insertEmpty:
|
||||
INSERT INTO mangas (source, url, title, artist, author, description, genre, status, thumbnail_url, favorite, last_update, next_update, initialized, viewer, chapter_flags, cover_last_modified, date_added, filtered_scanlators) VALUES (?, ?, ?, NULL, NULL, NULL, NULL, 0, NULL, 0, NULL, NULL, 0, 0, 0, 0, 0, NULL);
|
||||
|
||||
getMangaById:
|
||||
SELECT *
|
||||
FROM mangas
|
||||
@ -186,3 +189,14 @@ WHERE _id = :mangaId;
|
||||
|
||||
selectLastInsertedRowId:
|
||||
SELECT last_insert_rowid();
|
||||
|
||||
getEhMangaWithMetadata:
|
||||
SELECT mangas.* FROM mangas
|
||||
INNER JOIN search_metadata
|
||||
ON mangas._id = search_metadata.manga_id
|
||||
WHERE mangas.favorite = 1 AND (mangas.source = :eh OR mangas.source = :exh);
|
||||
|
||||
selectLastInsertRow:
|
||||
SELECT *
|
||||
FROM mangas
|
||||
WHERE _id = last_insert_rowid();
|
||||
|
Loading…
x
Reference in New Issue
Block a user