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, 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

@ -7,9 +7,7 @@ import androidx.compose.ui.util.fastAny
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.presentation.browse.FeedItemUI import eu.kanade.presentation.browse.FeedItemUI
import eu.kanade.tachiyomi.source.CatalogueSource import eu.kanade.tachiyomi.source.CatalogueSource
@ -30,16 +28,12 @@ import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import logcat.LogPriority
import tachiyomi.core.util.lang.awaitSingle import tachiyomi.core.util.lang.awaitSingle
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.launchNonCancellable
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.toMangaUpdate
import tachiyomi.domain.source.interactor.CountFeedSavedSearchGlobal import tachiyomi.domain.source.interactor.CountFeedSavedSearchGlobal
import tachiyomi.domain.source.interactor.DeleteFeedSavedSearchById import tachiyomi.domain.source.interactor.DeleteFeedSavedSearchById
import tachiyomi.domain.source.interactor.GetFeedSavedSearchGlobal import tachiyomi.domain.source.interactor.GetFeedSavedSearchGlobal
@ -291,34 +285,10 @@ open class FeedScreenModel(
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 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() { override fun onDispose() {
super.onDispose() super.onDispose()
coroutineDispatcher.close() coroutineDispatcher.close()

View File

@ -22,9 +22,7 @@ class MigrateSearchScreen(private val mangaId: Long, private val validSources: L
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

@ -15,9 +15,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.interactor.GetExhSavedSearch import eu.kanade.domain.source.interactor.GetExhSavedSearch
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
@ -43,11 +41,9 @@ import kotlinx.coroutines.flow.filter
import kotlinx.coroutines.flow.filterNotNull import kotlinx.coroutines.flow.filterNotNull
import kotlinx.coroutines.flow.firstOrNull import kotlinx.coroutines.flow.firstOrNull
import kotlinx.coroutines.flow.flatMapLatest import kotlinx.coroutines.flow.flatMapLatest
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOf import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.launchIn import kotlinx.coroutines.flow.launchIn
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
@ -62,7 +58,6 @@ import tachiyomi.core.preference.mapAsCheckboxState
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.launchNonCancellable
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.lang.withUIContext
import tachiyomi.core.util.system.logcat import tachiyomi.core.util.system.logcat
import tachiyomi.domain.UnsortedPreferences import tachiyomi.domain.UnsortedPreferences
@ -211,7 +206,6 @@ open class BrowseSourceScreenModel(
.filter { localManga -> .filter { localManga ->
!sourcePreferences.hideInLibraryItems().get() || !localManga.favorite !sourcePreferences.hideInLibraryItems().get() || !localManga.favorite
} }
.onEach(::initializeManga)
// SY --> // SY -->
.combineMetadata(metadata) .combineMetadata(metadata)
// SY <-- // 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. * 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 cafe.adriel.voyager.core.model.coroutineScope
import eu.kanade.core.preference.asState import eu.kanade.core.preference.asState
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.interactor.GetExhSavedSearch import eu.kanade.domain.source.interactor.GetExhSavedSearch
import eu.kanade.domain.ui.UiPreferences import eu.kanade.domain.ui.UiPreferences
import eu.kanade.presentation.browse.SourceFeedUI import eu.kanade.presentation.browse.SourceFeedUI
@ -34,17 +32,13 @@ import kotlinx.coroutines.withContext
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import logcat.LogPriority
import tachiyomi.core.util.lang.awaitSingle import tachiyomi.core.util.lang.awaitSingle
import tachiyomi.core.util.lang.launchIO import tachiyomi.core.util.lang.launchIO
import tachiyomi.core.util.lang.launchNonCancellable import tachiyomi.core.util.lang.launchNonCancellable
import tachiyomi.core.util.lang.withIOContext import tachiyomi.core.util.lang.withIOContext
import tachiyomi.core.util.lang.withNonCancellableContext
import tachiyomi.core.util.lang.withUIContext import tachiyomi.core.util.lang.withUIContext
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.toMangaUpdate
import tachiyomi.domain.source.interactor.CountFeedSavedSearchBySourceId import tachiyomi.domain.source.interactor.CountFeedSavedSearchBySourceId
import tachiyomi.domain.source.interactor.DeleteFeedSavedSearchById import tachiyomi.domain.source.interactor.DeleteFeedSavedSearchById
import tachiyomi.domain.source.interactor.GetFeedSavedSearchBySourceId import tachiyomi.domain.source.interactor.GetFeedSavedSearchBySourceId
@ -205,34 +199,10 @@ open class SourceFeedScreenModel(
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(manga)
}
value = 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() = private suspend fun loadSearches() =
getExhSavedSearch.await(source.id, source::getFilterList) getExhSavedSearch.await(source.id, source::getFilterList)
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, EXHSavedSearch::name)) .sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, EXHSavedSearch::name))

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()