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:
parent
1de4bc9586
commit
1a61130f0b
6 changed files with 10 additions and 72 deletions
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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,
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 = {
|
||||||
|
|
|
@ -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.
|
||||||
*
|
*
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Reference in a new issue