diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/Manatoki.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/Manatoki.kt index 31702419..92b7a7f8 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/Manatoki.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/Manatoki.kt @@ -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(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}") } diff --git a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt index bf8ff685..fedf819d 100644 --- a/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt +++ b/app/src/main/java/xyz/quaver/pupil/sources/manatoki/util.kt @@ -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(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) } } \ No newline at end of file