Prefetch chapter
parent
8d9426f257
commit
e6ae9e8bd6
@ -0,0 +1,22 @@
|
|||||||
|
package org.koitharu.kotatsu.core.cache
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
|
||||||
|
interface ContentCache {
|
||||||
|
|
||||||
|
suspend fun getDetails(source: MangaSource, url: String): Manga?
|
||||||
|
|
||||||
|
fun putDetails(source: MangaSource, url: String, details: Deferred<Manga>)
|
||||||
|
|
||||||
|
suspend fun getPages(source: MangaSource, url: String): List<MangaPage>?
|
||||||
|
|
||||||
|
fun putPages(source: MangaSource, url: String, pages: Deferred<List<MangaPage>>)
|
||||||
|
|
||||||
|
data class Key(
|
||||||
|
val source: MangaSource,
|
||||||
|
val url: String,
|
||||||
|
)
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package org.koitharu.kotatsu.core.cache
|
||||||
|
|
||||||
|
import androidx.collection.LruCache
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
|
||||||
|
class DeferredLruCache<T>(maxSize: Int) : LruCache<ContentCache.Key, Deferred<T>>(maxSize) {
|
||||||
|
|
||||||
|
override fun entryRemoved(
|
||||||
|
evicted: Boolean,
|
||||||
|
key: ContentCache.Key,
|
||||||
|
oldValue: Deferred<T>,
|
||||||
|
newValue: Deferred<T>?,
|
||||||
|
) {
|
||||||
|
super.entryRemoved(evicted, key, oldValue, newValue)
|
||||||
|
oldValue.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,29 @@
|
|||||||
|
package org.koitharu.kotatsu.core.cache
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
|
||||||
|
@Suppress("DeferredResultUnused")
|
||||||
|
class MemoryContentCache : ContentCache {
|
||||||
|
|
||||||
|
private val detailsCache = DeferredLruCache<Manga>(10)
|
||||||
|
private val pagesCache = DeferredLruCache<List<MangaPage>>(10)
|
||||||
|
|
||||||
|
override suspend fun getDetails(source: MangaSource, url: String): Manga? {
|
||||||
|
return detailsCache[ContentCache.Key(source, url)]?.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putDetails(source: MangaSource, url: String, details: Deferred<Manga>) {
|
||||||
|
detailsCache.put(ContentCache.Key(source, url), details)
|
||||||
|
}
|
||||||
|
|
||||||
|
override suspend fun getPages(source: MangaSource, url: String): List<MangaPage>? {
|
||||||
|
return pagesCache[ContentCache.Key(source, url)]?.await()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun putPages(source: MangaSource, url: String, pages: Deferred<List<MangaPage>>) {
|
||||||
|
pagesCache.put(ContentCache.Key(source, url), pages)
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,17 @@
|
|||||||
|
package org.koitharu.kotatsu.core.cache
|
||||||
|
|
||||||
|
import kotlinx.coroutines.Deferred
|
||||||
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaPage
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
|
||||||
|
class StubContentCache : ContentCache {
|
||||||
|
|
||||||
|
override suspend fun getDetails(source: MangaSource, url: String): Manga? = null
|
||||||
|
|
||||||
|
override fun putDetails(source: MangaSource, url: String, details: Deferred<Manga>) = Unit
|
||||||
|
|
||||||
|
override suspend fun getPages(source: MangaSource, url: String): List<MangaPage>? = null
|
||||||
|
|
||||||
|
override fun putPages(source: MangaSource, url: String, pages: Deferred<List<MangaPage>>) = Unit
|
||||||
|
}
|
||||||
@ -0,0 +1,96 @@
|
|||||||
|
package org.koitharu.kotatsu.details.service
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
|
import dagger.hilt.android.EntryPointAccessors
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.coroutineScope
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.koitharu.kotatsu.base.ui.CoroutineIntentService
|
||||||
|
import org.koitharu.kotatsu.core.cache.ContentCache
|
||||||
|
import org.koitharu.kotatsu.core.cache.StubContentCache
|
||||||
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableManga
|
||||||
|
import org.koitharu.kotatsu.core.model.parcelable.ParcelableMangaChapters
|
||||||
|
import org.koitharu.kotatsu.core.parser.MangaRepository
|
||||||
|
import org.koitharu.kotatsu.parsers.model.Manga
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaChapter
|
||||||
|
import org.koitharu.kotatsu.parsers.model.MangaSource
|
||||||
|
import org.koitharu.kotatsu.utils.ext.getParcelableExtraCompat
|
||||||
|
import org.koitharu.kotatsu.utils.ext.processLifecycleScope
|
||||||
|
import org.koitharu.kotatsu.utils.ext.runCatchingCancellable
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@AndroidEntryPoint
|
||||||
|
class MangaPrefetchService : CoroutineIntentService() {
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var mangaRepositoryFactory: MangaRepository.Factory
|
||||||
|
|
||||||
|
@Inject
|
||||||
|
lateinit var cache: ContentCache
|
||||||
|
|
||||||
|
override suspend fun processIntent(startId: Int, intent: Intent) {
|
||||||
|
when (intent.action) {
|
||||||
|
ACTION_PREFETCH_DETAILS -> prefetchDetails(
|
||||||
|
manga = intent.getParcelableExtraCompat<ParcelableManga>(EXTRA_MANGA)?.manga ?: return,
|
||||||
|
)
|
||||||
|
|
||||||
|
ACTION_PREFETCH_PAGES -> prefetchPages(
|
||||||
|
chapter = intent.getParcelableExtraCompat<ParcelableMangaChapters>(EXTRA_CHAPTER)
|
||||||
|
?.chapters?.singleOrNull() ?: return,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onError(startId: Int, error: Throwable) = Unit
|
||||||
|
|
||||||
|
private suspend fun prefetchDetails(manga: Manga) = coroutineScope {
|
||||||
|
val source = mangaRepositoryFactory.create(manga.source)
|
||||||
|
processLifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
runCatchingCancellable { source.getDetails(manga) }
|
||||||
|
}.join()
|
||||||
|
}
|
||||||
|
|
||||||
|
private suspend fun prefetchPages(chapter: MangaChapter) {
|
||||||
|
val source = mangaRepositoryFactory.create(chapter.source)
|
||||||
|
processLifecycleScope.launch(Dispatchers.Default) {
|
||||||
|
runCatchingCancellable { source.getPages(chapter) }
|
||||||
|
}.join()
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
private const val EXTRA_MANGA = "manga"
|
||||||
|
private const val EXTRA_CHAPTER = "manga"
|
||||||
|
private const val ACTION_PREFETCH_DETAILS = "details"
|
||||||
|
private const val ACTION_PREFETCH_PAGES = "pages"
|
||||||
|
|
||||||
|
fun prefetchDetails(context: Context, manga: Manga) {
|
||||||
|
if (!isPrefetchAvailable(context, manga.source)) return
|
||||||
|
val intent = Intent(context, MangaPrefetchService::class.java)
|
||||||
|
intent.action = ACTION_PREFETCH_DETAILS
|
||||||
|
intent.putExtra(EXTRA_MANGA, ParcelableManga(manga, withChapters = false))
|
||||||
|
context.startService(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun prefetchPages(context: Context, chapter: MangaChapter) {
|
||||||
|
if (!isPrefetchAvailable(context, chapter.source)) return
|
||||||
|
val intent = Intent(context, MangaPrefetchService::class.java)
|
||||||
|
intent.action = ACTION_PREFETCH_PAGES
|
||||||
|
intent.putExtra(EXTRA_CHAPTER, ParcelableMangaChapters(listOf(chapter)))
|
||||||
|
context.startService(intent)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isPrefetchAvailable(context: Context, source: MangaSource): Boolean {
|
||||||
|
if (source == MangaSource.LOCAL) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
val entryPoint = EntryPointAccessors.fromApplication(context, PrefetchCompanionEntryPoint::class.java)
|
||||||
|
if (entryPoint.contentCache is StubContentCache) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,14 @@
|
|||||||
|
package org.koitharu.kotatsu.details.service
|
||||||
|
|
||||||
|
import dagger.hilt.EntryPoint
|
||||||
|
import dagger.hilt.InstallIn
|
||||||
|
import dagger.hilt.components.SingletonComponent
|
||||||
|
import org.koitharu.kotatsu.core.cache.ContentCache
|
||||||
|
import org.koitharu.kotatsu.core.prefs.AppSettings
|
||||||
|
|
||||||
|
@EntryPoint
|
||||||
|
@InstallIn(SingletonComponent::class)
|
||||||
|
interface PrefetchCompanionEntryPoint {
|
||||||
|
val settings: AppSettings
|
||||||
|
val contentCache: ContentCache
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue