komikku/app/src/main/java/exh/eh/EHentaiUpdateWorker.kt

284 lines
11 KiB
Kotlin
Raw Normal View History

2019-04-18 23:40:13 +02:00
package exh.eh
import android.content.Context
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.ExistingPeriodicWorkPolicy
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.PeriodicWorkRequestBuilder
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import com.elvishew.xlog.Logger
2019-04-18 23:40:13 +02:00
import com.elvishew.xlog.XLog
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.CHARGING
2019-04-18 23:40:13 +02:00
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.UNMETERED_NETWORK
2019-04-18 23:40:13 +02:00
import eu.kanade.tachiyomi.source.SourceManager
import eu.kanade.tachiyomi.source.model.toSChapter
import eu.kanade.tachiyomi.source.model.toSManga
2019-04-18 23:40:13 +02:00
import eu.kanade.tachiyomi.source.online.all.EHentai
2020-03-31 22:38:40 +02:00
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
import exh.debug.DebugToggles
import exh.eh.EHentaiUpdateWorkerConstants.UPDATES_PER_ITERATION
import exh.log.xLog
2019-04-18 23:40:13 +02:00
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.metadata.base.insertFlatMetadataAsync
import exh.source.isEhBasedManga
2019-04-18 23:40:13 +02:00
import exh.util.cancellable
2021-06-02 06:10:36 +02:00
import exh.util.days
import exh.util.executeOnIO
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.mapNotNull
import kotlinx.coroutines.flow.single
import kotlinx.coroutines.flow.toList
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
2019-04-18 23:40:13 +02:00
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.util.concurrent.TimeUnit
2019-04-18 23:40:13 +02:00
class EHentaiUpdateWorker(private val context: Context, workerParams: WorkerParameters) :
CoroutineWorker(context, workerParams) {
2019-04-18 23:40:13 +02:00
private val db: DatabaseHelper 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()
2019-04-18 23:40:13 +02:00
private val updateNotifier by lazy { LibraryUpdateNotifier(context) }
2019-04-18 23:40:13 +02:00
override suspend fun doWork(): Result {
return try {
2019-04-18 23:40:13 +02:00
startUpdating()
logger.d("Update job completed!")
Result.success()
} catch (e: Exception) {
Result.failure()
2019-04-18 23:40:13 +02:00
}
}
2020-08-05 01:32:36 +02:00
private suspend fun startUpdating() {
2019-04-18 23:40:13 +02:00
logger.d("Update job started!")
val startTime = System.currentTimeMillis()
logger.d("Finding manga with metadata...")
val metadataManga = db.getFavoriteMangaWithMetadata().executeOnIO()
2019-04-18 23:40:13 +02:00
logger.d("Filtering manga and raising metadata...")
val curTime = System.currentTimeMillis()
val allMeta = metadataManga.asFlow().cancellable().mapNotNull { manga ->
if (!manga.isEhBasedManga()) {
2019-04-18 23:40:13 +02:00
return@mapNotNull null
2020-05-02 06:46:24 +02:00
}
2019-04-18 23:40:13 +02:00
val meta = db.getFlatMetadataForManga(manga.id!!).executeOnIO()
2020-05-02 06:46:24 +02:00
?: return@mapNotNull null
2019-04-18 23:40:13 +02:00
val raisedMeta = meta.raise<EHentaiSearchMetadata>()
// Don't update galleries too frequently
2020-05-02 06:46:24 +02:00
if (raisedMeta.aged || (curTime - raisedMeta.lastUpdateCheck < MIN_BACKGROUND_UPDATE_FREQ && DebugToggles.RESTRICT_EXH_GALLERY_UPDATE_CHECK_FREQUENCY.enabled)) {
2019-04-18 23:40:13 +02:00
return@mapNotNull null
2020-05-02 06:46:24 +02:00
}
2019-04-18 23:40:13 +02:00
val chapter = db.getChapters(manga.id!!).executeOnIO().minByOrNull {
2019-04-18 23:40:13 +02:00
it.date_upload
}
UpdateEntry(manga, raisedMeta, chapter)
}.toList().sortedBy { it.meta.lastUpdateCheck }
2019-04-18 23:40:13 +02:00
logger.d("Found %s manga to update, starting updates!", allMeta.size)
val mangaMetaToUpdateThisIter = allMeta.take(UPDATES_PER_ITERATION)
var failuresThisIteration = 0
var updatedThisIteration = 0
val updatedManga = mutableListOf<Pair<Manga, Array<Chapter>>>()
2019-04-18 23:40:13 +02:00
val modifiedThisIteration = mutableSetOf<Long>()
try {
for ((index, entry) in mangaMetaToUpdateThisIter.withIndex()) {
val (manga, meta) = entry
if (failuresThisIteration > MAX_UPDATE_FAILURES) {
logger.w("Too many update failures, aborting...")
break
}
2020-05-02 06:46:24 +02:00
logger.d(
"Updating gallery (index: %s, manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s, modifiedThisIteration.size: %s)...",
index,
manga.id,
meta.gId,
meta.gToken,
failuresThisIteration,
modifiedThisIteration.size
)
2019-04-18 23:40:13 +02:00
if (manga.id in modifiedThisIteration) {
// We already processed this manga!
logger.w("Gallery already updated this iteration, skipping...")
updatedThisIteration++
continue
}
val (new, chapters) = try {
2019-04-18 23:40:13 +02:00
updateEntryAndGetChapters(manga)
} catch (e: GalleryNotUpdatedException) {
if (e.network) {
failuresThisIteration++
logger.e("> Network error while updating gallery!", e)
2020-05-02 06:46:24 +02:00
logger.e(
"> (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)",
manga.id,
meta.gId,
meta.gToken,
failuresThisIteration
)
2019-04-18 23:40:13 +02:00
}
continue
}
if (chapters.isEmpty()) {
2020-05-02 06:46:24 +02:00
logger.e(
"No chapters found for gallery (manga.id: %s, meta.gId: %s, meta.gToken: %s, failures-so-far: %s)!",
manga.id,
meta.gId,
meta.gToken,
failuresThisIteration
)
2019-04-18 23:40:13 +02:00
continue
}
// Find accepted root and discard others
val (acceptedRoot, discardedRoots, hasNew) =
updateHelper.findAcceptedRootAndDiscardOthers(manga.source, chapters).single()
2019-04-18 23:40:13 +02:00
if ((new.isNotEmpty() && manga.id == acceptedRoot.manga.id) ||
(hasNew && updatedManga.none { it.first.id == acceptedRoot.manga.id })
2020-05-02 06:46:24 +02:00
) {
2021-03-23 01:25:46 +01:00
updatedManga += acceptedRoot.manga to new.toTypedArray()
}
2019-04-18 23:40:13 +02:00
modifiedThisIteration += acceptedRoot.manga.id!!
modifiedThisIteration += discardedRoots.map { it.manga.id!! }
updatedThisIteration++
}
} finally {
2020-11-30 20:35:31 +01:00
prefs.exhAutoUpdateStats().set(
Json.encodeToString(
2020-05-02 06:46:24 +02:00
EHentaiUpdaterStats(
startTime,
allMeta.size,
updatedThisIteration
2019-04-18 23:40:13 +02:00
)
2020-05-02 06:46:24 +02:00
)
2019-04-18 23:40:13 +02:00
)
if (updatedManga.isNotEmpty()) {
updateNotifier.showUpdateNotifications(updatedManga)
2019-08-09 10:40:44 +02:00
}
2019-04-18 23:40:13 +02:00
}
}
// New, current
2020-08-05 01:32:36 +02:00
private suspend fun updateEntryAndGetChapters(manga: Manga): Pair<List<Chapter>, List<Chapter>> {
2019-07-28 08:32:10 +02:00
val source = sourceManager.get(manga.source) as? EHentai
2020-05-02 06:46:24 +02:00
?: throw GalleryNotUpdatedException(false, IllegalStateException("Missing EH-based source (${manga.source})!"))
2019-04-18 23:40:13 +02:00
try {
val updatedManga = source.getMangaDetails(manga.toMangaInfo())
manga.copyFrom(updatedManga.toSManga())
db.insertManga(manga).executeOnIO()
2019-04-18 23:40:13 +02:00
val newChapters = source.getChapterList(manga.toMangaInfo())
.map { it.toSChapter() }
2020-12-12 18:36:29 +01:00
val (new, _) = syncChaptersWithSource(db, newChapters, manga, source) // Not suspending, but does block, maybe fix this?
return new to db.getChapters(manga).executeOnIO()
} catch (t: Throwable) {
if (t is EHentai.GalleryNotFoundException) {
val meta = db.getFlatMetadataForManga(manga.id!!).executeOnIO()?.raise<EHentaiSearchMetadata>()
if (meta != null) {
2019-04-18 23:40:13 +02:00
// Age dead galleries
2020-02-10 20:04:16 +01:00
logger.d("Aged %s - notfound", manga.id)
2019-04-18 23:40:13 +02:00
meta.aged = true
db.insertFlatMetadataAsync(meta.flatten()).await()
2019-04-18 23:40:13 +02:00
}
throw GalleryNotUpdatedException(false, t)
}
throw GalleryNotUpdatedException(true, t)
}
}
companion object {
private const val MAX_UPDATE_FAILURES = 5
2021-06-02 06:10:36 +02:00
private val MIN_BACKGROUND_UPDATE_FREQ = 1.days.inWholeMilliseconds
2019-04-18 23:40:13 +02:00
private const val TAG = "EHBackgroundUpdater"
2019-04-18 23:40:13 +02:00
private val logger by lazy { XLog.tag("EHUpdaterScheduler") }
fun launchBackgroundTest(context: Context) {
WorkManager.getInstance(context).enqueue(OneTimeWorkRequestBuilder<EHentaiUpdateWorker>().build())
}
2019-04-18 23:40:13 +02:00
fun scheduleBackground(context: Context, prefInterval: Int? = null) {
val preferences = Injekt.get<PreferencesHelper>()
val interval = prefInterval ?: preferences.exhAutoUpdateFrequency().get()
if (interval > 0) {
2020-11-30 20:35:31 +01:00
val restrictions = preferences.exhAutoUpdateRequirements().get()
val acRestriction = CHARGING in restrictions
val wifiRestriction = if (UNMETERED_NETWORK in restrictions) {
NetworkType.UNMETERED
} else {
NetworkType.CONNECTED
}
val constraints = Constraints.Builder()
.setRequiredNetworkType(wifiRestriction)
.setRequiresCharging(acRestriction)
.build()
2019-04-18 23:40:13 +02:00
val request = PeriodicWorkRequestBuilder<EHentaiUpdateWorker>(
interval.toLong(),
TimeUnit.HOURS,
10,
TimeUnit.MINUTES
2019-04-18 23:40:13 +02:00
)
.addTag(TAG)
.setConstraints(constraints)
.build()
2019-04-18 23:40:13 +02:00
WorkManager.getInstance(context).enqueueUniquePeriodicWork(TAG, ExistingPeriodicWorkPolicy.REPLACE, request)
logger.d("Successfully scheduled background update job!")
} else {
cancelBackground(context)
2019-04-18 23:40:13 +02:00
}
}
fun cancelBackground(context: Context) {
WorkManager.getInstance(context).cancelAllWorkByTag(TAG)
2019-04-18 23:40:13 +02:00
}
}
}
data class UpdateEntry(val manga: Manga, val meta: EHentaiSearchMetadata, val rootChapter: Chapter?)
object EHentaiUpdateWorkerConstants {
const val UPDATES_PER_ITERATION = 50
2020-12-12 18:36:29 +01:00
2021-06-02 06:10:36 +02:00
val GALLERY_AGE_TIME = 365.days.inWholeMilliseconds
}