[Manatoki] cache all requests

This commit is contained in:
tom5079
2021-12-22 21:23:10 +09:00
parent 57c4e249cf
commit d626cc09d5
2 changed files with 137 additions and 128 deletions

View File

@@ -103,9 +103,6 @@ class Manatoki(app: Application) : Source(), DIAware {
override val name = "manatoki.net"
override val iconResID = R.drawable.manatoki
private val readerInfoMutex = Mutex()
private val readerInfoCache = LruCache<String, ReaderInfo>(25)
override fun NavGraphBuilder.navGraph(navController: NavController) {
navigation(route = name, startDestination = "manatoki.net/") {
composable("manatoki.net/") { Main(navController) }
@@ -139,9 +136,6 @@ class Manatoki(app: Application) : Source(), DIAware {
val onReader: (ReaderInfo) -> Unit = { readerInfo ->
coroutineScope.launch {
readerInfoMutex.withLock {
readerInfoCache.put(readerInfo.itemID, readerInfo)
}
sheetState.snapTo(ModalBottomSheetValue.Hidden)
navController.navigate("manatoki.net/reader/${readerInfo.itemID}")
}
@@ -397,16 +391,13 @@ class Manatoki(app: Application) : Source(), DIAware {
LaunchedEffect(Unit) {
if (itemID != null)
readerInfoMutex.withLock {
readerInfoCache.get(itemID)?.let {
readerInfo = it
model.load(it.urls) {
set("User-Agent", imageUserAgent)
}
} ?: run {
model.error = true
client.getItem(itemID, onReader = {
readerInfo = it
model.load(it.urls) {
set("User-Agent", imageUserAgent)
}
}
})
else model.error = true
}
val bookmark by bookmarkDao.contains(name, itemID ?: "").observeAsState(false)
@@ -448,13 +439,8 @@ class Manatoki(app: Application) : Source(), DIAware {
client.getItem(
it,
onReader = {
coroutineScope.launch {
readerInfoMutex.withLock {
readerInfoCache.put(it.itemID, it)
}
navController.navigate("manatoki.net/reader/${it.itemID}") {
popUpTo("manatoki.net/")
}
navController.navigate("manatoki.net/reader/${it.itemID}") {
popUpTo("manatoki.net/")
}
}
)
@@ -648,9 +634,6 @@ class Manatoki(app: Application) : Source(), DIAware {
coroutineScope.launch {
client.getItem(it, onReader = {
launch {
readerInfoMutex.withLock {
readerInfoCache.put(it.itemID, it)
}
sheetState.snapTo(ModalBottomSheetValue.Hidden)
navController.navigate("manatoki.net/reader/${it.itemID}")
}

View File

@@ -19,20 +19,15 @@
package xyz.quaver.pupil.sources.manatoki
import android.os.Parcelable
import androidx.compose.foundation.clickable
import androidx.collection.LruCache
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Card
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material.MaterialTheme
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateMapOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.snapshots.SnapshotStateMap
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.google.common.util.concurrent.RateLimiter
import io.ktor.client.*
@@ -41,7 +36,6 @@ import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.withContext
import kotlinx.coroutines.yield
import kotlinx.parcelize.IgnoredOnParcel
import kotlinx.parcelize.Parcelize
import kotlinx.serialization.Serializable
import org.jsoup.Jsoup
@@ -104,119 +98,151 @@ fun Chip(text: String, selected: Boolean = false, onClick: () -> Unit = { }) {
}
}
private val cache = LruCache<String, Any>(50)
suspend fun HttpClient.getItem(
itemID: String,
onListing: (MangaListing) -> Unit = { },
onReader: (ReaderInfo) -> Unit = { }
onReader: (ReaderInfo) -> Unit = { },
onError: (Throwable) -> Unit = { throw it }
) = coroutineScope {
waitForRateLimit()
val content: String = get("https://manatoki116.net/comic/$itemID")
val cachedValue = synchronized(cache) {
cache.get(itemID)
}
val doc = Jsoup.parse(content)
yield()
if (doc.getElementsByClass("serial-list").size == 0) {
val htmlData = doc
.selectFirst(".view-padding > script")!!
.data()
.splitToSequence('\n')
.fold(StringBuilder()) { sb, line ->
if (!line.startsWith("html_data")) return@fold sb
line.drop(12).dropLast(2).split('.').forEach {
if (it.isNotBlank()) sb.appendCodePoint(it.toInt(16))
}
sb
}.toString()
val urls = Jsoup.parse(htmlData)
.select("img[^data-]:not([style])")
.map {
it.attributes()
.first { it.key.startsWith("data-") }
.value
}
val title = doc.getElementsByClass("toon-title").first()!!.ownText()
val listingItemID = doc.select("a:contains(전체목록)").first()!!.attr("href").takeLastWhile { it != '/' }
onReader(
ReaderInfo(
itemID,
title,
urls,
listingItemID
)
)
if (cachedValue != null) {
when (cachedValue) {
is MangaListing -> onListing(cachedValue)
is ReaderInfo -> onReader(cachedValue)
else -> onError(IllegalStateException("Cached value is not MangaListing nor ReaderInfo"))
}
} else {
val titleBlock = doc.selectFirst("div.view-title")!!
runCatching {
waitForRateLimit()
val content: String = get("https://manatoki116.net/comic/$itemID")
val title = titleBlock.select("div.view-content:not([itemprop])").first()!!.text()
val doc = Jsoup.parse(content)
val author =
titleBlock
.select("div.view-content:not([itemprop]):contains(작가)")
.first()!!
.getElementsByTag("a")
.first()!!
.text()
yield()
val tags =
titleBlock
.select("div.view-content:not([itemprop]):contains(분류)")
.first()!!
.getElementsByTag("a")
.map { it.text() }
if (doc.getElementsByClass("serial-list").size == 0) {
val htmlData = doc
.selectFirst(".view-padding > script")!!
.data()
.splitToSequence('\n')
.fold(StringBuilder()) { sb, line ->
if (!line.startsWith("html_data")) return@fold sb
val type =
titleBlock
.select("div.view-content:not([itemprop]):contains(발행구분)")
.first()!!
.getElementsByTag("a")
.first()!!
.text()
line.drop(12).dropLast(2).split('.').forEach {
if (it.isNotBlank()) sb.appendCodePoint(it.toInt(16))
}
sb
}.toString()
val thumbnail =
titleBlock.getElementsByTag("img").first()!!.attr("src")
val urls = Jsoup.parse(htmlData)
.select("img[^data-]:not([style])")
.map {
it.attributes()
.first { it.key.startsWith("data-") }
.value
}
val thumbsUpCount =
titleBlock.select("i.fa-thumbs-up + b").text().toInt()
val title = doc.getElementsByClass("toon-title").first()!!.ownText()
val entries =
doc.select("div.serial-list .list-item").map {
val episode = it.getElementsByClass("wr-num").first()!!.text().toInt()
val (itemID, title) = it.getElementsByClass("item-subject").first()!!.let { subject ->
subject.attr("href").dropLastWhile { it != '?' }.dropLast(1).takeLastWhile { it != '/' } to subject.ownText()
}
val starRating = it.getElementsByClass("wr-star").first()!!.text().drop(1).takeWhile { it != ')' }.toFloat()
val date = it.getElementsByClass("wr-date").first()!!.text()
val viewCount = it.getElementsByClass("wr-hit").first()!!.text().replace(",", "").toInt()
val thumbsUpCount = it.getElementsByClass("wr-good").first()!!.text().replace(",", "").toInt()
val listingItemID = doc.select("a:contains(전체목록)").first()!!.attr("href")
.takeLastWhile { it != '/' }
MangaListingEntry(
val readerInfo = ReaderInfo(
itemID,
episode,
title,
starRating,
date,
viewCount,
thumbsUpCount
urls,
listingItemID
)
}
onListing(
MangaListing(
itemID,
title,
thumbnail,
author,
tags,
type,
thumbsUpCount,
entries
)
)
synchronized(cache) {
cache.put(itemID, readerInfo)
}
onReader(readerInfo)
} else {
val titleBlock = doc.selectFirst("div.view-title")!!
val title = titleBlock.select("div.view-content:not([itemprop])").first()!!.text()
val author =
titleBlock
.select("div.view-content:not([itemprop]):contains(작가)")
.first()!!
.getElementsByTag("a")
.first()!!
.text()
val tags =
titleBlock
.select("div.view-content:not([itemprop]):contains(분류)")
.first()!!
.getElementsByTag("a")
.map { it.text() }
val type =
titleBlock
.select("div.view-content:not([itemprop]):contains(발행구분)")
.first()!!
.getElementsByTag("a")
.first()!!
.text()
val thumbnail =
titleBlock.getElementsByTag("img").first()!!.attr("src")
val thumbsUpCount =
titleBlock.select("i.fa-thumbs-up + b").text().toInt()
val entries =
doc.select("div.serial-list .list-item").map {
val episode = it.getElementsByClass("wr-num").first()!!.text().toInt()
val (itemID, title) = it.getElementsByClass("item-subject").first()!!
.let { subject ->
subject.attr("href").dropLastWhile { it != '?' }.dropLast(1)
.takeLastWhile { it != '/' } to subject.ownText()
}
val starRating = it.getElementsByClass("wr-star").first()!!.text().drop(1)
.takeWhile { it != ')' }.toFloat()
val date = it.getElementsByClass("wr-date").first()!!.text()
val viewCount =
it.getElementsByClass("wr-hit").first()!!.text().replace(",", "")
.toInt()
val thumbsUpCount =
it.getElementsByClass("wr-good").first()!!.text().replace(",", "")
.toInt()
MangaListingEntry(
itemID,
episode,
title,
starRating,
date,
viewCount,
thumbsUpCount
)
}
val mangaListing = MangaListing(
itemID,
title,
thumbnail,
author,
tags,
type,
thumbsUpCount,
entries
)
synchronized(cache) {
cache.put(itemID, mangaListing)
}
onListing(mangaListing)
}
}.onFailure(onError)
}
}