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.

(cherry picked from commit 1a61130f0b46addef036687b6c98f930e13147f8)

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/browse/source/browse/BrowseSourceScreenModel.kt
This commit is contained in:
arkon 2023-03-26 13:12:32 -04:00 committed by Jobobby04
parent 57d9ebb0b3
commit 016ca790e0
8 changed files with 10 additions and 133 deletions

View File

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

View File

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

View File

@ -7,9 +7,7 @@ import androidx.compose.ui.util.fastAny
import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
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.toSManga
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.presentation.browse.FeedItemUI
import eu.kanade.tachiyomi.source.CatalogueSource
@ -30,16 +28,12 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json
import logcat.LogPriority
import tachiyomi.core.util.lang.awaitSingle
import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable
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.NetworkToLocalManga
import tachiyomi.domain.manga.model.toMangaUpdate
import tachiyomi.domain.source.interactor.CountFeedSavedSearchGlobal
import tachiyomi.domain.source.interactor.DeleteFeedSavedSearchById
import tachiyomi.domain.source.interactor.GetFeedSavedSearchGlobal
@ -291,34 +285,10 @@ open class FeedScreenModel(
getManga.subscribe(initialManga.url, initialManga.source)
.collectLatest { manga ->
if (manga == null) return@collectLatest
withIOContext {
initializeManga(source, manga)
}
value = manga
}
}
}
/**
* Initialize a manga.
*
* @param manga to initialize.
*/
private suspend fun initializeManga(source: CatalogueSource?, manga: DomainManga) {
if (source == null || 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)
}
}
}
override fun onDispose() {
super.onDispose()
coroutineDispatcher.close()

View File

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

View File

@ -15,9 +15,7 @@ import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.core.preference.asState
import eu.kanade.domain.chapter.interactor.SyncChaptersWithTrackServiceTwoWay
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.toSManga
import eu.kanade.domain.source.interactor.GetExhSavedSearch
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.domain.track.model.toDomainTrack
@ -43,11 +41,9 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch
@ -62,7 +58,6 @@ import tachiyomi.core.preference.mapAsCheckboxState
import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.UnsortedPreferences
@ -211,7 +206,6 @@ open class BrowseSourceScreenModel(
.filter { localManga ->
!sourcePreferences.hideInLibraryItems().get() || !localManga.favorite
}
.onEach(::initializeManga)
// SY -->
.combineMetadata(metadata)
// SY <--
@ -335,26 +329,6 @@ open 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.
*

View File

@ -9,9 +9,7 @@ import cafe.adriel.voyager.core.model.StateScreenModel
import cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.core.preference.asState
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.toSManga
import eu.kanade.domain.source.interactor.GetExhSavedSearch
import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.browse.SourceFeedUI
@ -34,17 +32,13 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
import logcat.LogPriority
import tachiyomi.core.util.lang.awaitSingle
import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable
import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat
import tachiyomi.domain.manga.interactor.GetManga
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
import tachiyomi.domain.manga.model.toMangaUpdate
import tachiyomi.domain.source.interactor.CountFeedSavedSearchBySourceId
import tachiyomi.domain.source.interactor.DeleteFeedSavedSearchById
import tachiyomi.domain.source.interactor.GetFeedSavedSearchBySourceId
@ -205,34 +199,10 @@ open class SourceFeedScreenModel(
getManga.subscribe(initialManga.url, initialManga.source)
.collectLatest { manga ->
if (manga == null) return@collectLatest
withIOContext {
initializeManga(manga)
}
value = manga
}
}
}
/**
* Initialize a manga.
*
* @param manga to initialize.
*/
private suspend fun initializeManga(manga: DomainManga) {
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)
}
}
}
private suspend fun loadSearches() =
getExhSavedSearch.await(source.id, source::getFilterList)
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, EXHSavedSearch::name))

View File

@ -33,12 +33,7 @@ class GlobalSearchScreen(
navigateUp = navigator::pop,
onChangeSearchQuery = screenModel::updateSearchQuery,
onSearch = screenModel::search,
getManga = { source, manga ->
screenModel.getManga(
source = source,
initialManga = manga,
)
},
getManga = { screenModel.getManga(it) },
onClickSource = {
if (!screenModel.incognitoMode.get()) {
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.coroutineScope
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.toSManga
import eu.kanade.domain.source.service.SourcePreferences
import eu.kanade.tachiyomi.extension.ExtensionManager
import eu.kanade.tachiyomi.source.CatalogueSource
@ -18,15 +16,11 @@ import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import logcat.LogPriority
import tachiyomi.core.util.lang.awaitSingle
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.NetworkToLocalManga
import tachiyomi.domain.manga.model.Manga
import tachiyomi.domain.manga.model.toMangaUpdate
import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get
import java.util.concurrent.Executors
@ -57,43 +51,19 @@ abstract class SearchScreenModel<T>(
}
@Composable
fun getManga(source: CatalogueSource, initialManga: Manga): State<Manga> {
fun getManga(initialManga: Manga): State<Manga> {
return produceState(initialValue = initialManga) {
getManga.subscribe(initialManga.url, initialManga.source)
.collectLatest { manga ->
if (manga == null) return@collectLatest
withIOContext {
initializeManga(source, 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>
fun getSelectedSources(): List<CatalogueSource> {
private fun getSelectedSources(): List<CatalogueSource> {
val filter = extensionFilter
val enabledSources = getEnabledSources()