Don't attempt to initialize manga details from BrowseSource or Search screens

This was effectively DDoSing sources as it does a request for every entry to get the details (primarily a cover image).
The expectation now is that users have to open individual entries to load the details/cover if needed.
This isn't necessary for most sources, which are able to provide covers as part of the listing normally.
This commit is contained in:
arkon 2023-03-26 13:12:32 -04:00
parent 1de4bc9586
commit 1a61130f0b
6 changed files with 10 additions and 72 deletions

View file

@ -29,7 +29,7 @@ fun GlobalSearchScreen(
navigateUp: () -> Unit, navigateUp: () -> Unit,
onChangeSearchQuery: (String?) -> Unit, onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit, onSearch: (String) -> Unit,
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>, getManga: @Composable (Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit, onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit, onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit, onLongClickItem: (Manga) -> Unit,
@ -62,7 +62,7 @@ fun GlobalSearchScreen(
private fun GlobalSearchContent( private fun GlobalSearchContent(
items: Map<CatalogueSource, SearchItemResult>, items: Map<CatalogueSource, SearchItemResult>,
contentPadding: PaddingValues, contentPadding: PaddingValues,
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>, getManga: @Composable (Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit, onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit, onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit, onLongClickItem: (Manga) -> Unit,
@ -96,7 +96,7 @@ private fun GlobalSearchContent(
GlobalSearchCardRow( GlobalSearchCardRow(
titles = result.result, titles = result.result,
getManga = { getManga(source, it) }, getManga = { getManga(it) },
onClick = onClickItem, onClick = onClickItem,
onLongClick = onLongClickItem, onLongClick = onLongClickItem,
) )

View file

@ -21,7 +21,7 @@ import tachiyomi.presentation.core.components.material.Scaffold
fun MigrateSearchScreen( fun MigrateSearchScreen(
navigateUp: () -> Unit, navigateUp: () -> Unit,
state: MigrateSearchState, state: MigrateSearchState,
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>, getManga: @Composable (Manga) -> State<Manga>,
onChangeSearchQuery: (String?) -> Unit, onChangeSearchQuery: (String?) -> Unit,
onSearch: (String) -> Unit, onSearch: (String) -> Unit,
onClickSource: (CatalogueSource) -> Unit, onClickSource: (CatalogueSource) -> Unit,
@ -58,7 +58,7 @@ private fun MigrateSearchContent(
sourceId: Long, sourceId: Long,
items: Map<CatalogueSource, SearchItemResult>, items: Map<CatalogueSource, SearchItemResult>,
contentPadding: PaddingValues, contentPadding: PaddingValues,
getManga: @Composable (CatalogueSource, Manga) -> State<Manga>, getManga: @Composable (Manga) -> State<Manga>,
onClickSource: (CatalogueSource) -> Unit, onClickSource: (CatalogueSource) -> Unit,
onClickItem: (Manga) -> Unit, onClickItem: (Manga) -> Unit,
onLongClickItem: (Manga) -> Unit, onLongClickItem: (Manga) -> Unit,
@ -85,7 +85,7 @@ private fun MigrateSearchContent(
GlobalSearchCardRow( GlobalSearchCardRow(
titles = result.result, titles = result.result,
getManga = { getManga(source, it) }, getManga = { getManga(it) },
onClick = onClickItem, onClick = onClickItem,
onLongClick = onLongClickItem, onLongClick = onLongClickItem,
) )

View file

@ -22,9 +22,7 @@ class MigrateSearchScreen(private val mangaId: Long) : Screen() {
MigrateSearchScreen( MigrateSearchScreen(
navigateUp = navigator::pop, navigateUp = navigator::pop,
state = state, state = state,
getManga = { source, manga -> getManga = { screenModel.getManga(it) },
screenModel.getManga(source = source, initialManga = manga)
},
onChangeSearchQuery = screenModel::updateSearchQuery, onChangeSearchQuery = screenModel::updateSearchQuery,
onSearch = screenModel::search, onSearch = screenModel::search,
onClickSource = { onClickSource = {

View file

@ -16,9 +16,7 @@ import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.core.preference.asState import eu.kanade.core.preference.asState
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.copyFrom
import eu.kanade.domain.manga.model.toDomainManga import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.model.toDomainTrack import eu.kanade.domain.track.model.toDomainTrack
import eu.kanade.tachiyomi.data.cache.CoverCache import eu.kanade.tachiyomi.data.cache.CoverCache
@ -36,7 +34,6 @@ import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flow import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@ -45,7 +42,6 @@ import tachiyomi.core.preference.CheckboxState
import tachiyomi.core.preference.mapAsCheckboxState import tachiyomi.core.preference.mapAsCheckboxState
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.category.interactor.GetCategories import tachiyomi.domain.category.interactor.GetCategories
import tachiyomi.domain.category.interactor.SetMangaCategories import tachiyomi.domain.category.interactor.SetMangaCategories
@ -133,7 +129,6 @@ class BrowseSourceScreenModel(
.filter { localManga -> .filter { localManga ->
!sourcePreferences.hideInLibraryItems().get() || !localManga.favorite !sourcePreferences.hideInLibraryItems().get() || !localManga.favorite
} }
.onEach(::initializeManga)
.stateIn(coroutineScope) .stateIn(coroutineScope)
} }
} }
@ -234,26 +229,6 @@ class BrowseSourceScreenModel(
} }
} }
/**
* Initialize a manga.
*
* @param manga to initialize.
*/
private suspend fun initializeManga(manga: Manga) {
if (manga.thumbnailUrl != null || manga.initialized) return
withNonCancellableContext {
try {
val networkManga = source.getMangaDetails(manga.toSManga())
val updatedManga = manga.copyFrom(networkManga)
.copy(initialized = true)
updateManga.await(updatedManga.toMangaUpdate())
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
}
/** /**
* Adds or removes a manga from the library. * Adds or removes a manga from the library.
* *

View file

@ -33,12 +33,7 @@ class GlobalSearchScreen(
navigateUp = navigator::pop, navigateUp = navigator::pop,
onChangeSearchQuery = screenModel::updateSearchQuery, onChangeSearchQuery = screenModel::updateSearchQuery,
onSearch = screenModel::search, onSearch = screenModel::search,
getManga = { source, manga -> getManga = { screenModel.getManga(it) },
screenModel.getManga(
source = source,
initialManga = manga,
)
},
onClickSource = { onClickSource = {
if (!screenModel.incognitoMode.get()) { if (!screenModel.incognitoMode.get()) {
screenModel.lastUsedSourceId.set(it.id) screenModel.lastUsedSourceId.set(it.id)

View file

@ -6,9 +6,7 @@ import androidx.compose.runtime.produceState
import cafe.adriel.voyager.core.model.StateScreenModel import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.domain.manga.interactor.UpdateManga import eu.kanade.domain.manga.interactor.UpdateManga
import eu.kanade.domain.manga.model.copyFrom
import eu.kanade.domain.manga.model.toDomainManga import eu.kanade.domain.manga.model.toDomainManga
import eu.kanade.domain.manga.model.toSManga
import eu.kanade.domain.source.service.SourcePreferences import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.ExtensionManager import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
@ -18,15 +16,11 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import logcat.LogPriority
import tachiyomi.core.util.lang.awaitSingle import tachiyomi.core.util.lang.awaitSingle
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.interactor.GetManga import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.NetworkToLocalManga import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.toMangaUpdate
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
import java.util.concurrent.Executors import java.util.concurrent.Executors
@ -57,43 +51,19 @@ abstract class SearchScreenModel<T>(
} }
@Composable @Composable
fun getManga(source: CatalogueSource, initialManga: Manga): State<Manga> { fun getManga(initialManga: Manga): State<Manga> {
return produceState(initialValue = initialManga) { return produceState(initialValue = initialManga) {
getManga.subscribe(initialManga.url, initialManga.source) getManga.subscribe(initialManga.url, initialManga.source)
.collectLatest { manga -> .collectLatest { manga ->
if (manga == null) return@collectLatest if (manga == null) return@collectLatest
withIOContext {
initializeManga(source, manga)
}
value = manga value = manga
} }
} }
} }
/**
* Initialize a manga.
*
* @param source to interact with
* @param manga to initialize.
*/
private suspend fun initializeManga(source: CatalogueSource, manga: Manga) {
if (manga.thumbnailUrl != null || manga.initialized) return
withNonCancellableContext {
try {
val networkManga = source.getMangaDetails(manga.toSManga())
val updatedManga = manga.copyFrom(networkManga)
.copy(initialized = true)
updateManga.await(updatedManga.toMangaUpdate())
} catch (e: Exception) {
logcat(LogPriority.ERROR, e)
}
}
}
abstract fun getEnabledSources(): List<CatalogueSource> abstract fun getEnabledSources(): List<CatalogueSource>
fun getSelectedSources(): List<CatalogueSource> { private fun getSelectedSources(): List<CatalogueSource> {
val filter = extensionFilter val filter = extensionFilter
val enabledSources = getEnabledSources() val enabledSources = getEnabledSources()