mirror of
https://github.com/mihonapp/mihon.git
synced 2025-11-13 04:28:55 +01:00
Add automatic gallery updating
This commit is contained in:
@@ -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>() }
|
||||
|
||||
@@ -57,6 +57,9 @@ object Migrations {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ===========[ ALL MIGRATIONS ABOVE HERE HAVE BEEN ALREADY REWRITTEN ]===========
|
||||
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
@@ -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()
|
||||
|
||||
|
||||
@@ -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 ->
|
||||
|
||||
@@ -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"
|
||||
}
|
||||
|
||||
@@ -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, "")
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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"
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -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) })
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user