Add automatic gallery updating

This commit is contained in:
NerdNumber9
2019-04-18 17:40:13 -04:00
parent a218f4a48b
commit 1d36c3269e
29 changed files with 1240 additions and 87 deletions

View File

@@ -11,6 +11,7 @@ import eu.kanade.tachiyomi.data.track.TrackManager
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.network.NetworkHelper
import eu.kanade.tachiyomi.source.SourceManager
import exh.eh.EHentaiUpdateHelper
import rx.Observable
import rx.schedulers.Schedulers
import uy.kohesive.injekt.api.*
@@ -41,6 +42,8 @@ class AppModule(val app: Application) : InjektModule {
addSingletonFactory { Gson() }
addSingletonFactory { EHentaiUpdateHelper(app) }
// Asynchronously init expensive components for a faster cold start
rxAsync { get<PreferencesHelper>() }

View File

@@ -57,6 +57,9 @@ object Migrations {
}
}
}
// ===========[ ALL MIGRATIONS ABOVE HERE HAVE BEEN ALREADY REWRITTEN ]===========
return true
}
return false

View File

@@ -15,12 +15,14 @@ import java.util.*
interface ChapterQueries : DbProvider {
fun getChapters(manga: Manga) = db.get()
fun getChapters(manga: Manga) = getChaptersByMangaId(manga.id)
fun getChaptersByMangaId(mangaId: Long?) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_MANGA_ID} = ?")
.whereArgs(manga.id)
.whereArgs(mangaId)
.build())
.prepare()
@@ -52,6 +54,15 @@ interface ChapterQueries : DbProvider {
.build())
.prepare()
fun getChapters(url: String) = db.get()
.listOfObjects(Chapter::class.java)
.withQuery(Query.builder()
.table(ChapterTable.TABLE)
.where("${ChapterTable.COL_URL} = ?")
.whereArgs(url)
.build())
.prepare()
fun insertChapter(chapter: Chapter) = db.put().`object`(chapter).prepare()

View File

@@ -29,6 +29,8 @@ import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.util.*
import exh.EH_SOURCE_ID
import exh.EXH_SOURCE_ID
import rx.Observable
import rx.Subscription
import rx.schedulers.Schedulers
@@ -283,24 +285,29 @@ class LibraryUpdateService(
.doOnNext { showProgressNotification(it, count.andIncrement, mangaToUpdate.size) }
// Update the chapters of the manga.
.concatMap { manga ->
updateManga(manga)
// If there's any error, return empty update and continue.
.onErrorReturn {
failedUpdates.add(manga)
Pair(emptyList(), emptyList())
}
// Filter out mangas without new chapters (or failed).
.filter { pair -> pair.first.isNotEmpty() }
.doOnNext {
if (downloadNew && (categoriesToDownload.isEmpty() ||
manga.category in categoriesToDownload)) {
downloadChapters(manga, it.first)
hasDownloads = true
if(manga.source == EXH_SOURCE_ID || manga.source == EH_SOURCE_ID) {
// Ignore EXH manga, updating chapters for every manga will get you banned
Observable.just(manga)
} else {
updateManga(manga)
// If there's any error, return empty update and continue.
.onErrorReturn {
failedUpdates.add(manga)
Pair(emptyList(), emptyList())
}
}
// Convert to the manga that contains new chapters.
.map { manga }
// Filter out mangas without new chapters (or failed).
.filter { pair -> pair.first.isNotEmpty() }
.doOnNext {
if (downloadNew && (categoriesToDownload.isEmpty() ||
manga.category in categoriesToDownload)) {
downloadChapters(manga, it.first)
hasDownloads = true
}
}
// Convert to the manga that contains new chapters.
.map { manga }
}
}
// Add manga with new chapters to the list.
.doOnNext { manga ->

View File

@@ -186,4 +186,10 @@ object PreferenceKeys {
const val eh_logLevel = "eh_log_level"
const val eh_enableSourceBlacklist = "eh_enable_source_blacklist"
const val eh_autoUpdateFrequency = "eh_auto_update_frequency"
const val eh_autoUpdateRestrictions = "eh_auto_update_restrictions"
const val eh_autoUpdateStats = "eh_auto_update_stats"
}

View File

@@ -259,4 +259,10 @@ class PreferencesHelper(val context: Context) {
fun eh_logLevel() = rxPrefs.getInteger(Keys.eh_logLevel, 0)
fun eh_enableSourceBlacklist() = rxPrefs.getBoolean(Keys.eh_enableSourceBlacklist, true)
fun eh_autoUpdateFrequency() = rxPrefs.getInteger(Keys.eh_autoUpdateFrequency, 1)
fun eh_autoUpdateRequirements() = prefs.getStringSet(Keys.eh_autoUpdateRestrictions, emptySet())
fun eh_autoUpdateStats() = rxPrefs.getString(Keys.eh_autoUpdateStats, "")
}

View File

@@ -23,6 +23,11 @@ interface SManga : Serializable {
var initialized: Boolean
fun copyFrom(other: SManga) {
// EXH -->
url = other.url // Allow dynamically mutating one manga into another
title = other.title
// EXH <--
if (other.author != null)
author = other.author

View File

@@ -2,6 +2,7 @@ package eu.kanade.tachiyomi.source.online.all
import android.content.Context
import android.net.Uri
import com.elvishew.xlog.XLog
import eu.kanade.tachiyomi.data.database.models.Manga
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
import eu.kanade.tachiyomi.data.preference.getOrDefault
@@ -12,6 +13,7 @@ import eu.kanade.tachiyomi.source.model.*
import eu.kanade.tachiyomi.source.online.HttpSource
import eu.kanade.tachiyomi.source.online.LewdSource
import eu.kanade.tachiyomi.util.asJsoup
import exh.eh.EHentaiUpdateHelper
import exh.metadata.EX_DATE_FORMAT
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.EHentaiSearchMetadata.Companion.EH_GENRE_NAMESPACE
@@ -33,11 +35,17 @@ import uy.kohesive.injekt.injectLazy
import java.net.URLEncoder
import java.util.*
import exh.metadata.metadata.base.RaisedTag
import exh.eh.EHentaiUpdateWorker
import exh.eh.GalleryEntry
import kotlinx.coroutines.runBlocking
import org.jsoup.nodes.TextNode
import rx.Single
import java.lang.RuntimeException
// TODO Consider gallery updating when doing tabbed browsing
class EHentai(override val id: Long,
val exh: Boolean,
val context: Context) : HttpSource(), LewdSource<EHentaiSearchMetadata, Response> {
val context: Context) : HttpSource(), LewdSource<EHentaiSearchMetadata, Document> {
override val metaClass = EHentaiSearchMetadata::class
val schema: String
@@ -58,7 +66,8 @@ class EHentai(override val id: Long,
override val lang = "all"
override val supportsLatest = true
val prefs: PreferencesHelper by injectLazy()
private val prefs: PreferencesHelper by injectLazy()
private val updateHelper: EHentaiUpdateHelper by injectLazy()
/**
* Gallery list entry
@@ -115,15 +124,83 @@ class EHentai(override val id: Long,
MangasPage(it.first.map { it.manga }, it.second)
}
override fun fetchChapterList(manga: SManga): Observable<List<SChapter>>
= Observable.just(listOf(SChapter.create().apply {
url = manga.url
name = "Chapter"
chapter_number = 1f
}))
override fun fetchChapterList(manga: SManga)
= fetchChapterList(manga) {}
fun fetchChapterList(manga: SManga, throttleFunc: () -> Unit): Observable<List<SChapter>> {
return Single.fromCallable {
// Pull all the way to the root gallery
// We can't do this with RxJava or we run into stack overflows on shit like this:
// https://exhentai.org/g/1073061/f9345f1c12/
var url: String = manga.url
var doc: Document? = null
runBlocking {
while (true) {
val gid = EHentaiSearchMetadata.galleryId(url).toInt()
val cachedParent = updateHelper.parentLookupTable.get(
gid
)
if(cachedParent == null) {
throttleFunc()
val resp = client.newCall(exGet(baseUrl + url)).execute()
if (!resp.isSuccessful) error("HTTP error (${resp.code()})!")
doc = resp.asJsoup()
val parentLink = doc!!.select("#gdd .gdt1").find { el ->
el.text().toLowerCase() == "parent:"
}!!.nextElementSibling().selectFirst("a")?.attr("href")
if (parentLink != null) {
updateHelper.parentLookupTable.put(
gid,
GalleryEntry(
EHentaiSearchMetadata.galleryId(parentLink),
EHentaiSearchMetadata.galleryToken(parentLink)
)
)
url = EHentaiSearchMetadata.normalizeUrl(parentLink)
} else break
} else {
XLog.d("Parent cache hit: %s!", gid)
url = EHentaiSearchMetadata.idAndTokenToUrl(
cachedParent.gId,
cachedParent.gToken
)
}
}
}
doc!!
}.map { d ->
val newDisplay = d.select("#gnd a")
// Build chapter for root gallery
val self = SChapter.create().apply {
url = EHentaiSearchMetadata.normalizeUrl(d.location())
name = "v1: " + d.selectFirst("#gn").text()
chapter_number = 1f
date_upload = EX_DATE_FORMAT.parse(d.select("#gdd .gdt1").find { el ->
el.text().toLowerCase() == "posted:"
}!!.nextElementSibling().text()).time
}
// Build and append the rest of the galleries
listOf(self) + newDisplay.mapIndexed { index, newGallery ->
val link = newGallery.attr("href")
val name = newGallery.text()
val posted = (newGallery.nextSibling() as TextNode).text().removePrefix(", added ")
SChapter.create().apply {
this.url = EHentaiSearchMetadata.normalizeUrl(link)
this.name = "v${index + 2}: $name"
this.chapter_number = index + 2f
this.date_upload = EX_DATE_FORMAT.parse(posted).time
}
}
}.toObservable()
}
override fun fetchPageList(chapter: SChapter)
= fetchChapterPage(chapter, "$baseUrl/${chapter.url}").map {
= fetchChapterPage(chapter, baseUrl + chapter.url).map {
it.mapIndexed { i, s ->
Page(i, s)
}
@@ -241,9 +318,20 @@ class EHentai(override val id: Long,
.asObservableWithAsyncStacktrace()
.flatMap { (stacktrace, response) ->
if(response.isSuccessful) {
parseToManga(manga, response).andThen(Observable.just(manga.apply {
initialized = true
}))
// Pull to most recent
val doc = response.asJsoup()
val newerGallery = doc.select("#gnd a").lastOrNull()
val pre = if(newerGallery != null) {
manga.url = EHentaiSearchMetadata.normalizeUrl(newerGallery.attr("href"))
client.newCall(mangaDetailsRequest(manga))
.asObservableSuccess().map { it.asJsoup() }
} else Observable.just(doc)
pre.flatMap {
parseToManga(manga, it).andThen(Observable.just(manga.apply {
initialized = true
}))
}
} else {
response.close()
@@ -261,10 +349,10 @@ class EHentai(override val id: Long,
*/
override fun mangaDetailsParse(response: Response) = throw UnsupportedOperationException()
override fun parseIntoMetadata(metadata: EHentaiSearchMetadata, input: Response) {
override fun parseIntoMetadata(metadata: EHentaiSearchMetadata, input: Document) {
with(metadata) {
with(input.asJsoup()) {
val url = input.request().url().encodedPath()
with(input) {
val url = input.location()
gId = EHentaiSearchMetadata.galleryId(url)
gToken = EHentaiSearchMetadata.galleryToken(url)
@@ -296,6 +384,8 @@ class EHentai(override val id: Long,
.toLowerCase()) {
"posted" -> datePosted = EX_DATE_FORMAT.parse(right).time
// Example gallery with parent: https://e-hentai.org/g/1390451/7f181c2426/
// Example JP gallery: https://exhentai.org/g/1375385/03519d541b/
// Parent is older variation of the gallery
"parent" -> parent = if (!right.equals("None", true)) {
rightElement.child(0).attr("href")
} else null
@@ -312,6 +402,12 @@ class EHentai(override val id: Long,
}
}
lastUpdateCheck = System.currentTimeMillis()
if(datePosted != null
&& lastUpdateCheck - datePosted!! > EHentaiUpdateWorker.GALLERY_AGE_TIME) {
aged = true
}
//Parse ratings
ignore {
averageRating = select("#rating_label")

View File

@@ -26,6 +26,7 @@ import eu.kanade.tachiyomi.ui.base.controller.RxController
import eu.kanade.tachiyomi.ui.base.controller.TabbedController
import eu.kanade.tachiyomi.ui.base.controller.requestPermissionsSafe
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersController
import eu.kanade.tachiyomi.ui.manga.chapter.ChaptersPresenter
import eu.kanade.tachiyomi.ui.manga.info.MangaInfoController
import eu.kanade.tachiyomi.ui.manga.track.TrackController
import eu.kanade.tachiyomi.util.toast
@@ -48,6 +49,18 @@ class MangaController : RxController, TabbedController {
}
}
// EXH -->
constructor(redirect: ChaptersPresenter.EXHRedirect) : super(Bundle().apply {
putLong(MANGA_EXTRA, redirect.manga.id!!)
putBoolean(UPDATE_EXTRA, redirect.update)
}) {
this.manga = redirect.manga
if (manga != null) {
source = Injekt.get<SourceManager>().getOrStub(redirect.manga.source)
}
}
// EXH <--
constructor(mangaId: Long) : this(
Injekt.get<DatabaseHelper>().getManga(mangaId).executeAsBlocking())
@@ -64,6 +77,8 @@ class MangaController : RxController, TabbedController {
val fromCatalogue = args.getBoolean(FROM_CATALOGUE_EXTRA, false)
val update = args.getBoolean(UPDATE_EXTRA, false)
val lastUpdateRelay: BehaviorRelay<Date> = BehaviorRelay.create()
val chapterCountRelay: BehaviorRelay<Float> = BehaviorRelay.create()
@@ -180,6 +195,9 @@ class MangaController : RxController, TabbedController {
companion object {
// EXH -->
const val UPDATE_EXTRA = "update"
// EXH <--
const val FROM_CATALOGUE_EXTRA = "from_catalogue"
const val MANGA_EXTRA = "manga"

View File

@@ -11,6 +11,7 @@ import android.support.v7.view.ActionMode
import android.support.v7.widget.DividerItemDecoration
import android.support.v7.widget.LinearLayoutManager
import android.view.*
import com.bluelinelabs.conductor.RouterTransaction
import com.elvishew.xlog.XLog
import com.jakewharton.rxbinding.support.v4.widget.refreshes
import com.jakewharton.rxbinding.view.clicks
@@ -28,6 +29,7 @@ import eu.kanade.tachiyomi.util.getCoordinates
import eu.kanade.tachiyomi.util.snack
import eu.kanade.tachiyomi.util.toast
import kotlinx.android.synthetic.main.chapters_controller.*
import rx.android.schedulers.AndroidSchedulers
import timber.log.Timber
class ChaptersController : NucleusController<ChaptersPresenter>(),
@@ -104,6 +106,14 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
view.context.toast(R.string.no_next_chapter)
}
}
presenter.redirectUserRelay
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { redirect ->
XLog.d("Redirecting to updated manga (manga.id: %s, manga.title: %s, update: %s)!", redirect.manga.id, redirect.manga.title, redirect.update)
// Replace self
parentController?.router?.replaceTopController(RouterTransaction.with(MangaController(redirect)))
}
}
override fun onDestroyView(view: View) {
@@ -188,6 +198,9 @@ class ChaptersController : NucleusController<ChaptersPresenter>(),
if (presenter.chapters.isEmpty())
initialFetchChapters()
if ((parentController as MangaController).update)
fetchChaptersFromSource()
val adapter = adapter ?: return
adapter.updateDataSet(chapters)

View File

@@ -15,6 +15,9 @@ import eu.kanade.tachiyomi.source.Source
import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.isNullOrUnsubscribed
import eu.kanade.tachiyomi.util.syncChaptersWithSource
import exh.EH_SOURCE_ID
import exh.EXH_SOURCE_ID
import exh.eh.EHentaiUpdateHelper
import rx.Observable
import rx.Subscription
import rx.android.schedulers.AndroidSchedulers
@@ -22,6 +25,7 @@ import rx.schedulers.Schedulers
import timber.log.Timber
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import uy.kohesive.injekt.injectLazy
import java.util.Date
/**
@@ -66,6 +70,14 @@ class ChaptersPresenter(
*/
private var observeDownloadsSubscription: Subscription? = null
// EXH -->
private val updateHelper: EHentaiUpdateHelper by injectLazy()
val redirectUserRelay = BehaviorRelay.create<EXHRedirect>()
data class EXHRedirect(val manga: Manga, val update: Boolean)
// EXH <--
override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState)
@@ -100,6 +112,25 @@ class ChaptersPresenter(
lastUpdateRelay.call(Date(chapters.maxBy { it.date_upload }?.date_upload
?: 0))
// EXH -->
if(chapters.isNotEmpty()
&& (source.id == EXH_SOURCE_ID || source.id == EH_SOURCE_ID)) {
// Check for gallery in library and accept manga with lowest id
// Find chapters sharing same root
add(updateHelper.findAcceptedRootAndDiscardOthers(chapters)
.subscribeOn(Schedulers.io())
.subscribe { (acceptedChain, _) ->
// Redirect if we are not the accepted root
if(manga.id != acceptedChain.manga.id) {
// Update if any of our chapters are not in accepted manga's chapters
val ourChapterUrls = chapters.map { it.url }.toSet()
val acceptedChapterUrls = acceptedChain.chapters.map { it.url }.toSet()
val update = (ourChapterUrls - acceptedChapterUrls).isNotEmpty()
redirectUserRelay.call(EXHRedirect(acceptedChain.manga, update))
}
})
}
// EXH <--
}
.subscribe { chaptersRelay.call(it) })
}

View File

@@ -22,6 +22,7 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy
import com.bumptech.glide.load.resource.bitmap.RoundedCorners
import com.bumptech.glide.request.target.SimpleTarget
import com.bumptech.glide.request.transition.Transition
import com.bumptech.glide.signature.ObjectKey
import com.elvishew.xlog.XLog
import com.jakewharton.rxbinding.support.v4.widget.refreshes
import com.jakewharton.rxbinding.view.clicks
@@ -74,7 +75,9 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
*/
private val preferences: PreferencesHelper by injectLazy()
private val sourceManager: SourceManager by injectLazy()
// EXH -->
private var lastMangaThumbnail: String? = null
// EXH <--
init {
setHasOptionsMenu(true)
@@ -181,6 +184,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
// Update view.
setMangaInfo(manga, source)
if((parentController as MangaController).update) fetchMangaFromSource()
} else {
// Initialize manga.
fetchMangaFromSource()
@@ -247,10 +251,17 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
// Set the favorite drawable to the correct one.
setFavoriteDrawable(manga.favorite)
// Set cover if it wasn't already.
if (manga_cover.drawable == null && !manga.thumbnail_url.isNullOrEmpty()) {
// Set cover if it matches
val tagMatches = lastMangaThumbnail == manga.thumbnail_url
val coverLoaded = manga_cover.drawable != null
if ((!tagMatches || !coverLoaded) && !manga.thumbnail_url.isNullOrEmpty()) {
lastMangaThumbnail = manga.thumbnail_url
val coverSig = ObjectKey(manga.thumbnail_url ?: "")
GlideApp.with(view.context)
.load(manga)
.signature(coverSig)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(manga_cover)
@@ -258,6 +269,7 @@ class MangaInfoController : NucleusController<MangaInfoPresenter>(),
if (backdrop != null) {
GlideApp.with(view.context)
.load(manga)
.signature(coverSig)
.diskCacheStrategy(DiskCacheStrategy.RESOURCE)
.centerCrop()
.into(backdrop)

View File

@@ -1,26 +1,54 @@
package eu.kanade.tachiyomi.ui.setting
import android.os.Build
import android.os.Handler
import android.support.v7.preference.PreferenceScreen
import android.widget.Toast
import com.afollestad.materialdialogs.MaterialDialog
import com.bluelinelabs.conductor.RouterTransaction
import com.bluelinelabs.conductor.changehandler.FadeChangeHandler
import com.f2prateek.rx.preferences.Preference
import com.github.salomonbrys.kotson.fromJson
import com.google.gson.Gson
import com.kizitonwose.time.Interval
import com.kizitonwose.time.days
import com.kizitonwose.time.hours
import eu.kanade.tachiyomi.R
import eu.kanade.tachiyomi.data.database.DatabaseHelper
import eu.kanade.tachiyomi.data.preference.PreferenceKeys
import eu.kanade.tachiyomi.data.preference.getOrDefault
import eu.kanade.tachiyomi.util.toast
import exh.EH_SOURCE_ID
import exh.EXH_SOURCE_ID
import exh.eh.EHentaiUpdateWorker
import exh.eh.EHentaiUpdaterStats
import exh.favorites.FavoritesIntroDialog
import exh.favorites.LocalFavoritesStorage
import exh.metadata.metadata.EHentaiSearchMetadata
import exh.metadata.metadata.base.getFlatMetadataForManga
import exh.metadata.nullIfBlank
import exh.uconfig.WarnConfigureDialogController
import exh.ui.login.LoginController
import exh.util.await
import exh.util.trans
import humanize.Humanize
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import rx.android.schedulers.AndroidSchedulers
import rx.schedulers.Schedulers
import uy.kohesive.injekt.injectLazy
import java.util.*
/**
* EH Settings fragment
*/
class SettingsEhController : SettingsController() {
private val gson: Gson by injectLazy()
private val db: DatabaseHelper by injectLazy()
private fun Preference<*>.reconfigure(): Boolean {
//Listen for change commit
asObservable()
@@ -183,5 +211,113 @@ class SettingsEhController : SettingsController() {
}
}
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
preferenceCategory {
title = "Gallery update checker"
intListPreference {
key = PreferenceKeys.eh_autoUpdateFrequency
title = "Time between update batches"
entries = arrayOf("Never update galleries", "1 hour", "2 hours", "3 hours", "6 hours", "12 hours", "24 hours", "48 hours")
entryValues = arrayOf("0", "1", "2", "3", "6", "12", "24", "48")
defaultValue = "0"
preferences.eh_autoUpdateFrequency().asObservable().subscribeUntilDestroy { newVal ->
summary = if(newVal == 0) {
"${context.getString(R.string.app_name)} will currently never check galleries in your library for updates."
} else {
"${context.getString(R.string.app_name)} checks/updates galleries in batches. " +
"This means it will wait $newVal hour(s), check ${EHentaiUpdateWorker.UPDATES_PER_ITERATION} galleries," +
" wait $newVal hour(s), check ${EHentaiUpdateWorker.UPDATES_PER_ITERATION} and so on..."
}
}
onChange { newValue ->
val interval = (newValue as String).toInt()
EHentaiUpdateWorker.scheduleBackground(context, interval)
true
}
}
multiSelectListPreference {
key = PreferenceKeys.eh_autoUpdateRestrictions
title = "Auto update restrictions"
entriesRes = arrayOf(R.string.wifi, R.string.charging)
entryValues = arrayOf("wifi", "ac")
summaryRes = R.string.pref_library_update_restriction_summary
preferences.eh_autoUpdateFrequency().asObservable()
.subscribeUntilDestroy { isVisible = it > 0 }
onChange {
// Post to event looper to allow the preference to be updated.
Handler().post { EHentaiUpdateWorker.scheduleBackground(context) }
true
}
}
preference {
title = "Show updater statistics"
onClick {
val progress = MaterialDialog.Builder(context)
.progress(true, 0)
.content("Collecting statistics...")
.cancelable(false)
.show()
GlobalScope.launch(Dispatchers.IO) {
val updateInfo = try {
val stats = preferences.eh_autoUpdateStats().getOrDefault().nullIfBlank()?.let {
gson.fromJson<EHentaiUpdaterStats>(it)
}
val statsText = if (stats != null) {
"The updater last ran ${Humanize.naturalTime(Date(stats.startTime))}, and checked ${stats.updateCount} out of the ${stats.possibleUpdates} galleries that were ready for checking."
} else "The updater has not ran yet."
val allMeta = db.getMangaWithMetadata().await().filter {
it.favorite && (it.source == EH_SOURCE_ID || it.source == EXH_SOURCE_ID)
}.mapNotNull {
db.getFlatMetadataForManga(it.id!!).await()?.raise<EHentaiSearchMetadata>()
}.toList()
fun metaInRelativeDuration(duration: Interval<*>): Int {
val durationMs = duration.inMilliseconds.longValue
return allMeta.asSequence().filter {
System.currentTimeMillis() - it.lastUpdateCheck < durationMs
}.count()
}
"""
$statsText
Galleries that were checked in the last:
- hour: ${metaInRelativeDuration(1.hours)}
- 6 hours: ${metaInRelativeDuration(6.hours)}
- 12 hours: ${metaInRelativeDuration(12.hours)}
- day: ${metaInRelativeDuration(1.days)}
- 2 days: ${metaInRelativeDuration(2.days)}
- week: ${metaInRelativeDuration(7.days)}
- month: ${metaInRelativeDuration(30.days)}
- year: ${metaInRelativeDuration(365.days)}
""".trimIndent()
} finally {
progress.dismiss()
}
withContext(Dispatchers.Main) {
MaterialDialog.Builder(context)
.title("Gallery updater statistics")
.content(updateInfo)
.positiveText("Ok")
.show()
}
}
}
}
}
}
}
}

View File

@@ -3,6 +3,7 @@ package eu.kanade.tachiyomi.util
import android.app.ActivityManager
import android.app.Notification
import android.app.NotificationManager
import android.app.job.JobScheduler
import android.content.*
import android.content.Context.VIBRATOR_SERVICE
import android.content.pm.PackageManager
@@ -128,11 +129,11 @@ val Context.wifiManager: WifiManager
get() = applicationContext.getSystemService(Context.WIFI_SERVICE) as WifiManager
// --> EH
/**
* Property to get the wifi manager from the context.
*/
val Context.clipboardManager: ClipboardManager
get() = applicationContext.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager
val Context.jobScheduler: JobScheduler
get() = applicationContext.getSystemService(Context.JOB_SCHEDULER_SERVICE) as JobScheduler
// <-- EH
/**