Adapt source implementation for TachiyomiX 1.6 (#3243)
This commit is contained in:
@@ -35,6 +35,7 @@ import mihon.domain.extension.interactor.RemoveExtensionStore
|
||||
import mihon.domain.extension.interactor.UpdateExtensionStores
|
||||
import mihon.domain.extension.repository.ExtensionStoreRepository
|
||||
import mihon.domain.migration.usecases.MigrateMangaUseCase
|
||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||
import mihon.domain.upcoming.interactor.GetUpcomingManga
|
||||
import tachiyomi.data.category.CategoryRepositoryImpl
|
||||
import tachiyomi.data.chapter.ChapterRepositoryImpl
|
||||
@@ -204,5 +205,7 @@ class DomainModule : InjektModule {
|
||||
|
||||
addFactory { ToggleIncognito(get()) }
|
||||
addFactory { GetIncognitoState(get(), get(), get()) }
|
||||
|
||||
addFactory { UpdateMangaFromRemote(get(), get(), get(), get(), get(), get(), get()) }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,17 +1,9 @@
|
||||
package eu.kanade.domain.manga.interactor
|
||||
|
||||
import eu.kanade.domain.manga.model.hasCustomCover
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.MangaUpdate
|
||||
import tachiyomi.domain.manga.repository.MangaRepository
|
||||
import tachiyomi.source.local.isLocal
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.time.Instant
|
||||
import java.time.ZonedDateTime
|
||||
|
||||
@@ -28,67 +20,6 @@ class UpdateManga(
|
||||
return mangaRepository.updateAll(mangaUpdates)
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFromSource(
|
||||
localManga: Manga,
|
||||
remoteManga: SManga,
|
||||
manualFetch: Boolean,
|
||||
coverCache: CoverCache = Injekt.get(),
|
||||
libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
downloadManager: DownloadManager = Injekt.get(),
|
||||
): Boolean {
|
||||
val remoteTitle = try {
|
||||
remoteManga.title
|
||||
} catch (_: UninitializedPropertyAccessException) {
|
||||
""
|
||||
}
|
||||
|
||||
// if the manga isn't a favorite (or 'update titles' preference is enabled), set its title from source and update in db
|
||||
val title =
|
||||
if (remoteTitle.isNotEmpty() && (!localManga.favorite || libraryPreferences.updateMangaTitles.get())) {
|
||||
remoteTitle
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val coverLastModified =
|
||||
when {
|
||||
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
||||
remoteManga.thumbnail_url.isNullOrEmpty() -> null
|
||||
!manualFetch && localManga.thumbnailUrl == remoteManga.thumbnail_url -> null
|
||||
localManga.isLocal() -> Instant.now().toEpochMilli()
|
||||
localManga.hasCustomCover(coverCache) -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
null
|
||||
}
|
||||
else -> {
|
||||
coverCache.deleteFromCache(localManga, false)
|
||||
Instant.now().toEpochMilli()
|
||||
}
|
||||
}
|
||||
|
||||
val thumbnailUrl = remoteManga.thumbnail_url?.takeIf { it.isNotEmpty() }
|
||||
|
||||
val success = mangaRepository.update(
|
||||
MangaUpdate(
|
||||
id = localManga.id,
|
||||
title = title,
|
||||
coverLastModified = coverLastModified,
|
||||
author = remoteManga.author,
|
||||
artist = remoteManga.artist,
|
||||
description = remoteManga.description,
|
||||
genre = remoteManga.getGenres(),
|
||||
thumbnailUrl = thumbnailUrl,
|
||||
status = remoteManga.status.toLong(),
|
||||
updateStrategy = remoteManga.update_strategy,
|
||||
initialized = true,
|
||||
),
|
||||
)
|
||||
if (success && title != null) {
|
||||
downloadManager.renameManga(localManga, title)
|
||||
}
|
||||
return success
|
||||
}
|
||||
|
||||
suspend fun awaitUpdateFetchInterval(
|
||||
manga: Manga,
|
||||
dateTime: ZonedDateTime = ZonedDateTime.now(),
|
||||
|
||||
@@ -10,7 +10,7 @@ import eu.kanade.presentation.browse.components.GlobalSearchErrorResultItem
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchLoadingResultItem
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchResultItem
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||
@@ -27,7 +27,7 @@ fun GlobalSearchScreen(
|
||||
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
) {
|
||||
@@ -62,10 +62,10 @@ fun GlobalSearchScreen(
|
||||
|
||||
@Composable
|
||||
internal fun GlobalSearchContent(
|
||||
items: Map<CatalogueSource, SearchItemResult>,
|
||||
items: Map<Source, SearchItemResult>,
|
||||
contentPadding: PaddingValues,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
fromSourceId: Long? = null,
|
||||
|
||||
@@ -3,7 +3,7 @@ package eu.kanade.presentation.browse
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.State
|
||||
import eu.kanade.presentation.browse.components.GlobalSearchToolbar
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SourceFilter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
@@ -19,7 +19,7 @@ fun MigrateSearchScreen(
|
||||
onChangeSearchFilter: (SourceFilter) -> Unit,
|
||||
onToggleResults: () -> Unit,
|
||||
getManga: @Composable (Manga) -> State<Manga>,
|
||||
onClickSource: (CatalogueSource) -> Unit,
|
||||
onClickSource: (Source) -> Unit,
|
||||
onClickItem: (Manga) -> Unit,
|
||||
onLongClickItem: (Manga) -> Unit,
|
||||
) {
|
||||
|
||||
+1
-1
@@ -111,7 +111,7 @@ object SettingsTrackingScreen : SearchableSettings {
|
||||
.filter { it is EnhancedTracker }
|
||||
.partition { service ->
|
||||
val acceptedSources = (service as EnhancedTracker).getAcceptedSources()
|
||||
sourceManager.getCatalogueSources().any { it::class.qualifiedName in acceptedSources }
|
||||
sourceManager.getAll().any { it::class.qualifiedName in acceptedSources }
|
||||
}
|
||||
var enhancedTrackerInfo = stringResource(MR.strings.enhanced_tracking_info)
|
||||
if (enhancedTrackers.second.isNotEmpty()) {
|
||||
|
||||
+1
-1
@@ -28,7 +28,7 @@ class PreferenceBackupCreator(
|
||||
}
|
||||
|
||||
fun createSource(includePrivatePreferences: Boolean): List<BackupSourcePreferences> {
|
||||
return sourceManager.getCatalogueSources()
|
||||
return sourceManager.getAll()
|
||||
.filterIsInstance<ConfigurableSource>()
|
||||
.map {
|
||||
BackupSourcePreferences(
|
||||
|
||||
@@ -18,10 +18,6 @@ import androidx.work.WorkInfo
|
||||
import androidx.work.WorkQuery
|
||||
import androidx.work.WorkerParameters
|
||||
import androidx.work.workDataOf
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.toSManga
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
@@ -41,6 +37,7 @@ import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import logcat.LogPriority
|
||||
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.preference.getAndSet
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
@@ -83,13 +80,11 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
private val sourceManager: SourceManager = Injekt.get()
|
||||
private val libraryPreferences: LibraryPreferences = Injekt.get()
|
||||
private val downloadManager: DownloadManager = Injekt.get()
|
||||
private val coverCache: CoverCache = Injekt.get()
|
||||
private val getLibraryManga: GetLibraryManga = Injekt.get()
|
||||
private val getManga: GetManga = Injekt.get()
|
||||
private val updateManga: UpdateManga = Injekt.get()
|
||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
|
||||
private val fetchInterval: FetchInterval = Injekt.get()
|
||||
private val filterChaptersForDownload: FilterChaptersForDownload = Injekt.get()
|
||||
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get()
|
||||
|
||||
private val notifier = LibraryUpdateNotifier(context)
|
||||
|
||||
@@ -331,19 +326,16 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
private suspend fun updateManga(manga: Manga, fetchWindow: Pair<Long, Long>): List<Chapter> {
|
||||
val source = sourceManager.getOrStub(manga.source)
|
||||
|
||||
// Update manga metadata if needed
|
||||
if (libraryPreferences.autoUpdateMetadata.get()) {
|
||||
val networkManga = source.getMangaDetails(manga.toSManga())
|
||||
updateManga.awaitUpdateFromSource(manga, networkManga, manualFetch = false, coverCache)
|
||||
}
|
||||
val update = updateMangaFromRemote(
|
||||
source = source,
|
||||
manga = manga,
|
||||
fetchDetails = libraryPreferences.autoUpdateMetadata.get(),
|
||||
fetchChapters = true,
|
||||
fetchWindow = fetchWindow,
|
||||
)
|
||||
.getOrThrow()
|
||||
|
||||
val chapters = source.getChapterList(manga.toSManga())
|
||||
|
||||
// Get manga from database to account for if it was removed during the update and
|
||||
// to get latest data so it doesn't get overwritten later on
|
||||
val dbManga = getManga.await(manga.id)?.takeIf { it.favorite } ?: return emptyList()
|
||||
|
||||
return syncChaptersWithSource.await(chapters, dbManga, source, false, fetchWindow)
|
||||
return if (update.manga.favorite) update.newChapters else emptyList()
|
||||
}
|
||||
|
||||
private suspend fun withUpdateNotification(
|
||||
|
||||
@@ -10,12 +10,7 @@ import androidx.work.OneTimeWorkRequestBuilder
|
||||
import androidx.work.WorkInfo
|
||||
import androidx.work.WorkQuery
|
||||
import androidx.work.WorkerParameters
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.copyFrom
|
||||
import eu.kanade.domain.manga.model.toSManga
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||
import eu.kanade.tachiyomi.util.prepUpdateCover
|
||||
import eu.kanade.tachiyomi.util.system.isRunning
|
||||
import eu.kanade.tachiyomi.util.system.setForegroundSafely
|
||||
import eu.kanade.tachiyomi.util.system.workManager
|
||||
@@ -27,12 +22,12 @@ import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import logcat.LogPriority
|
||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.library.model.LibraryManga
|
||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
@@ -46,9 +41,8 @@ class MetadataUpdateJob(private val context: Context, workerParams: WorkerParame
|
||||
CoroutineWorker(context, workerParams) {
|
||||
|
||||
private val sourceManager: SourceManager = Injekt.get()
|
||||
private val coverCache: CoverCache = Injekt.get()
|
||||
private val getLibraryManga: GetLibraryManga = Injekt.get()
|
||||
private val updateManga: UpdateManga = Injekt.get()
|
||||
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get()
|
||||
|
||||
private val notifier = LibraryUpdateNotifier(context)
|
||||
|
||||
@@ -120,14 +114,11 @@ class MetadataUpdateJob(private val context: Context, workerParams: WorkerParame
|
||||
) {
|
||||
val source = sourceManager.get(manga.source) ?: return@withUpdateNotification
|
||||
try {
|
||||
val networkManga = source.getMangaDetails(manga.toSManga())
|
||||
val updatedManga = manga.prepUpdateCover(coverCache, networkManga, true)
|
||||
.copyFrom(networkManga)
|
||||
try {
|
||||
updateManga.await(updatedManga.toMangaUpdate())
|
||||
} catch (e: Exception) {
|
||||
logcat(LogPriority.ERROR) { "Manga doesn't exist anymore" }
|
||||
}
|
||||
updateMangaFromRemote(
|
||||
source = source,
|
||||
manga = manga,
|
||||
fetchDetails = true,
|
||||
).getOrThrow()
|
||||
} catch (e: Throwable) {
|
||||
// Ignore errors and continue
|
||||
logcat(LogPriority.ERROR, e)
|
||||
|
||||
@@ -10,7 +10,6 @@ import eu.kanade.domain.extension.interactor.TrustExtension
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.extension.model.Extension
|
||||
import eu.kanade.tachiyomi.extension.model.LoadResult
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.SourceFactory
|
||||
import eu.kanade.tachiyomi.util.lang.Hash
|
||||
@@ -301,9 +300,7 @@ internal object ExtensionLoader {
|
||||
}
|
||||
}
|
||||
|
||||
val langs = sources.filterIsInstance<CatalogueSource>()
|
||||
.map { it.lang }
|
||||
.toSet()
|
||||
val langs = sources.map { it.lang }.toSet()
|
||||
val lang = when (langs.size) {
|
||||
0 -> ""
|
||||
1 -> langs.first()
|
||||
|
||||
@@ -38,9 +38,7 @@ class AndroidSourceManager(
|
||||
|
||||
private val stubSourcesMap = ConcurrentHashMap<Long, StubSource>()
|
||||
|
||||
override val catalogueSources: Flow<List<CatalogueSource>> = sourcesMapFlow.map {
|
||||
it.values.filterIsInstance<CatalogueSource>()
|
||||
}
|
||||
override val sources: Flow<List<Source>> = sourcesMapFlow.map { it.values.toList() }
|
||||
|
||||
init {
|
||||
scope.launchIO {
|
||||
@@ -87,9 +85,9 @@ class AndroidSourceManager(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance<HttpSource>()
|
||||
override fun getAll() = sourcesMapFlow.value.values.toList()
|
||||
|
||||
override fun getCatalogueSources() = sourcesMapFlow.value.values.filterIsInstance<CatalogueSource>()
|
||||
override fun getOnlineSources() = sourcesMapFlow.value.values.filterIsInstance<HttpSource>()
|
||||
|
||||
override fun getStubSources(): List<StubSource> {
|
||||
val onlineSourceIds = getOnlineSources().map { it.id }
|
||||
|
||||
@@ -120,7 +120,7 @@ class ExtensionsScreenModel(
|
||||
when (extension) {
|
||||
is Extension.Installed -> extension.sources.any { source ->
|
||||
source.name.contains(subquery, ignoreCase = true) ||
|
||||
(source as? HttpSource)?.baseUrl?.contains(subquery, ignoreCase = true) == true ||
|
||||
(source as? HttpSource)?.getHomeUrl()?.contains(subquery, ignoreCase = true) == true ||
|
||||
source.id == subquery.toLongOrNull()
|
||||
}
|
||||
|
||||
|
||||
+2
-1
@@ -99,7 +99,8 @@ class ExtensionDetailsScreenModel(
|
||||
|
||||
val urls = extension.sources
|
||||
.filterIsInstance<HttpSource>()
|
||||
.mapNotNull { it.baseUrl.takeUnless { url -> url.isEmpty() } }
|
||||
.flatMap { listOf(it.baseUrl, it.getHomeUrl()) }
|
||||
.filter { it.isNotEmpty() }
|
||||
.distinct()
|
||||
|
||||
val cleared = urls.sumOf {
|
||||
|
||||
+5
-5
@@ -2,7 +2,7 @@ package eu.kanade.tachiyomi.ui.browse.migration.search
|
||||
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchItemResult
|
||||
import eu.kanade.tachiyomi.ui.browse.source.globalsearch.SearchScreenModel
|
||||
import kotlinx.coroutines.flow.update
|
||||
@@ -21,8 +21,8 @@ class MigrateSearchScreenModel(
|
||||
|
||||
private val migrationSources by lazy { sourcePreferences.migrationSources.get() }
|
||||
|
||||
override val sortComparator = { map: Map<CatalogueSource, SearchItemResult> ->
|
||||
compareBy<CatalogueSource>(
|
||||
override val sortComparator = { map: Map<Source, SearchItemResult> ->
|
||||
compareBy<Source>(
|
||||
{ (map[it] as? SearchItemResult.Success)?.isEmpty ?: true },
|
||||
{ migrationSources.indexOf(it.id) },
|
||||
)
|
||||
@@ -41,7 +41,7 @@ class MigrateSearchScreenModel(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEnabledSources(): List<CatalogueSource> {
|
||||
return migrationSources.mapNotNull { sourceManager.get(it) as? CatalogueSource }
|
||||
override fun getEnabledSources(): List<Source> {
|
||||
return migrationSources.mapNotNull { sourceManager.get(it) }
|
||||
}
|
||||
}
|
||||
|
||||
+1
-1
@@ -110,7 +110,7 @@ data class MigrateSourceSearchScreen(
|
||||
val source = screenModel.source as? HttpSource ?: return@BrowseSourceContent
|
||||
navigator.push(
|
||||
WebViewScreen(
|
||||
url = source.baseUrl,
|
||||
url = source.getHomeUrl(),
|
||||
initialTitle = source.name,
|
||||
sourceId = source.id,
|
||||
),
|
||||
|
||||
@@ -44,7 +44,6 @@ import eu.kanade.presentation.category.components.ChangeCategoryDialog
|
||||
import eu.kanade.presentation.manga.DuplicateMangaDialog
|
||||
import eu.kanade.presentation.util.AssistContentScreen
|
||||
import eu.kanade.presentation.util.Screen
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.ui.browse.extension.details.SourcePreferencesScreen
|
||||
import eu.kanade.tachiyomi.ui.browse.source.browse.BrowseSourceScreenModel.Listing
|
||||
@@ -111,7 +110,7 @@ data class BrowseSourceScreen(
|
||||
val source = screenModel.source as? HttpSource ?: return@f
|
||||
navigator.push(
|
||||
WebViewScreen(
|
||||
url = source.baseUrl,
|
||||
url = source.getHomeUrl(),
|
||||
initialTitle = source.name,
|
||||
sourceId = source.id,
|
||||
),
|
||||
@@ -119,7 +118,7 @@ data class BrowseSourceScreen(
|
||||
}
|
||||
|
||||
LaunchedEffect(screenModel.source) {
|
||||
assistUrl = (screenModel.source as? HttpSource)?.baseUrl
|
||||
assistUrl = (screenModel.source as? HttpSource)?.getHomeUrl()
|
||||
}
|
||||
|
||||
Scaffold(
|
||||
@@ -166,7 +165,7 @@ data class BrowseSourceScreen(
|
||||
Text(text = stringResource(MR.strings.popular))
|
||||
},
|
||||
)
|
||||
if ((screenModel.source as CatalogueSource).supportsLatest) {
|
||||
if (screenModel.source.supportsLatest) {
|
||||
FilterChip(
|
||||
selected = state.listing == Listing.Latest,
|
||||
onClick = {
|
||||
|
||||
+12
-23
@@ -20,7 +20,6 @@ import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.domain.track.interactor.AddTracks
|
||||
import eu.kanade.presentation.util.ioCoroutineScope
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.model.FilterList
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
@@ -74,22 +73,20 @@ class BrowseSourceScreenModel(
|
||||
val source = sourceManager.getOrStub(sourceId)
|
||||
|
||||
init {
|
||||
if (source is CatalogueSource) {
|
||||
mutableState.update {
|
||||
var query: String? = null
|
||||
var listing = it.listing
|
||||
mutableState.update {
|
||||
var query: String? = null
|
||||
var listing = it.listing
|
||||
|
||||
if (listing is Listing.Search) {
|
||||
query = listing.query
|
||||
listing = Listing.Search(query, source.getFilterList())
|
||||
}
|
||||
|
||||
it.copy(
|
||||
listing = listing,
|
||||
filters = source.getFilterList(),
|
||||
toolbarQuery = query,
|
||||
)
|
||||
if (listing is Listing.Search) {
|
||||
query = listing.query
|
||||
listing = Listing.Search(query, source.getFilterList())
|
||||
}
|
||||
|
||||
it.copy(
|
||||
listing = listing,
|
||||
filters = source.getFilterList(),
|
||||
toolbarQuery = query,
|
||||
)
|
||||
}
|
||||
|
||||
if (!getIncognitoState.await(source.id)) {
|
||||
@@ -129,8 +126,6 @@ class BrowseSourceScreenModel(
|
||||
}
|
||||
|
||||
fun resetFilters() {
|
||||
if (source !is CatalogueSource) return
|
||||
|
||||
mutableState.update { it.copy(filters = source.getFilterList()) }
|
||||
}
|
||||
|
||||
@@ -139,8 +134,6 @@ class BrowseSourceScreenModel(
|
||||
}
|
||||
|
||||
fun setFilters(filters: FilterList) {
|
||||
if (source !is CatalogueSource) return
|
||||
|
||||
mutableState.update {
|
||||
it.copy(
|
||||
filters = filters,
|
||||
@@ -149,8 +142,6 @@ class BrowseSourceScreenModel(
|
||||
}
|
||||
|
||||
fun search(query: String? = null, filters: FilterList? = null) {
|
||||
if (source !is CatalogueSource) return
|
||||
|
||||
val input = state.value.listing as? Listing.Search
|
||||
?: Listing.Search(query = null, filters = source.getFilterList())
|
||||
|
||||
@@ -166,8 +157,6 @@ class BrowseSourceScreenModel(
|
||||
}
|
||||
|
||||
fun searchGenre(genreName: String) {
|
||||
if (source !is CatalogueSource) return
|
||||
|
||||
val defaultFilters = source.getFilterList()
|
||||
var genreExists = false
|
||||
|
||||
|
||||
+2
-2
@@ -1,6 +1,6 @@
|
||||
package eu.kanade.tachiyomi.ui.browse.source.globalsearch
|
||||
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
|
||||
class GlobalSearchScreenModel(
|
||||
initialQuery: String = "",
|
||||
@@ -18,7 +18,7 @@ class GlobalSearchScreenModel(
|
||||
}
|
||||
}
|
||||
|
||||
override fun getEnabledSources(): List<CatalogueSource> {
|
||||
override fun getEnabledSources(): List<Source> {
|
||||
return super.getEnabledSources()
|
||||
.filter { state.value.sourceFilter != SourceFilter.PinnedOnly || "${it.id}" in pinnedSources }
|
||||
}
|
||||
|
||||
+9
-10
@@ -8,7 +8,7 @@ import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import eu.kanade.domain.source.service.SourcePreferences
|
||||
import eu.kanade.presentation.util.ioCoroutineScope
|
||||
import eu.kanade.tachiyomi.extension.ExtensionManager
|
||||
import eu.kanade.tachiyomi.source.CatalogueSource
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.asCoroutineDispatcher
|
||||
import kotlinx.coroutines.async
|
||||
@@ -52,8 +52,8 @@ abstract class SearchScreenModel(
|
||||
|
||||
protected var extensionFilter: String? = null
|
||||
|
||||
open val sortComparator = { map: Map<CatalogueSource, SearchItemResult> ->
|
||||
compareBy<CatalogueSource>(
|
||||
open val sortComparator = { map: Map<Source, SearchItemResult> ->
|
||||
compareBy<Source>(
|
||||
{ (map[it] as? SearchItemResult.Success)?.isEmpty ?: true },
|
||||
{ "${it.id}" !in pinnedSources },
|
||||
{ "${it.name.lowercase()} (${it.lang})" },
|
||||
@@ -79,8 +79,8 @@ abstract class SearchScreenModel(
|
||||
}
|
||||
}
|
||||
|
||||
open fun getEnabledSources(): List<CatalogueSource> {
|
||||
return sourceManager.getCatalogueSources()
|
||||
open fun getEnabledSources(): List<Source> {
|
||||
return sourceManager.getAll()
|
||||
.filter { it.lang in enabledLanguages && "${it.id}" !in disabledSources }
|
||||
.sortedWith(
|
||||
compareBy(
|
||||
@@ -90,7 +90,7 @@ abstract class SearchScreenModel(
|
||||
)
|
||||
}
|
||||
|
||||
private fun getSelectedSources(): List<CatalogueSource> {
|
||||
private fun getSelectedSources(): List<Source> {
|
||||
val enabledSources = getEnabledSources()
|
||||
|
||||
val filter = extensionFilter
|
||||
@@ -101,7 +101,6 @@ abstract class SearchScreenModel(
|
||||
return extensionManager.installedExtensionsFlow.value
|
||||
.filter { it.pkgName == filter }
|
||||
.flatMap { it.sources }
|
||||
.filterIsInstance<CatalogueSource>()
|
||||
.filter { it in enabledSources }
|
||||
}
|
||||
|
||||
@@ -179,7 +178,7 @@ abstract class SearchScreenModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateItems(items: Map<CatalogueSource, SearchItemResult>) {
|
||||
private fun updateItems(items: Map<Source, SearchItemResult>) {
|
||||
mutableState.update {
|
||||
it.copy(
|
||||
items = items
|
||||
@@ -188,7 +187,7 @@ abstract class SearchScreenModel(
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateItem(source: CatalogueSource, result: SearchItemResult) {
|
||||
private fun updateItem(source: Source, result: SearchItemResult) {
|
||||
updateItems(state.value.items + (source to result))
|
||||
}
|
||||
|
||||
@@ -209,7 +208,7 @@ abstract class SearchScreenModel(
|
||||
val searchQuery: String? = null,
|
||||
val sourceFilter: SourceFilter = SourceFilter.PinnedOnly,
|
||||
val onlyShowHasResults: Boolean = false,
|
||||
val items: Map<CatalogueSource, SearchItemResult> = mapOf(),
|
||||
val items: Map<Source, SearchItemResult> = mapOf(),
|
||||
val dialog: Dialog? = null,
|
||||
) {
|
||||
val progress: Int = items.count { it.value !is SearchItemResult.Loading }
|
||||
|
||||
@@ -3,14 +3,13 @@ package eu.kanade.tachiyomi.ui.deeplink
|
||||
import androidx.compose.runtime.Immutable
|
||||
import cafe.adriel.voyager.core.model.StateScreenModel
|
||||
import cafe.adriel.voyager.core.model.screenModelScope
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.domain.manga.model.toSManga
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.source.model.SChapter
|
||||
import eu.kanade.tachiyomi.source.online.ResolvableSource
|
||||
import eu.kanade.tachiyomi.source.online.UriType
|
||||
import kotlinx.coroutines.flow.update
|
||||
import mihon.domain.manga.model.toDomainManga
|
||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.domain.chapter.interactor.GetChapterByUrlAndMangaId
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
@@ -25,12 +24,12 @@ class DeepLinkScreenModel(
|
||||
private val sourceManager: SourceManager = Injekt.get(),
|
||||
private val networkToLocalManga: NetworkToLocalManga = Injekt.get(),
|
||||
private val getChapterByUrlAndMangaId: GetChapterByUrlAndMangaId = Injekt.get(),
|
||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
|
||||
) : StateScreenModel<DeepLinkScreenModel.State>(State.Loading) {
|
||||
|
||||
init {
|
||||
screenModelScope.launchIO {
|
||||
val source = sourceManager.getCatalogueSources()
|
||||
val source = sourceManager.getAll()
|
||||
.filterIsInstance<ResolvableSource>()
|
||||
.firstOrNull { it.getUriType(query) != UriType.Unknown }
|
||||
|
||||
@@ -61,13 +60,11 @@ class DeepLinkScreenModel(
|
||||
private suspend fun getChapterFromSChapter(sChapter: SChapter, manga: Manga, source: Source): Chapter? {
|
||||
val localChapter = getChapterByUrlAndMangaId.await(sChapter.url, manga.id)
|
||||
|
||||
return if (localChapter == null) {
|
||||
val sourceChapters = source.getChapterList(manga.toSManga())
|
||||
val newChapters = syncChaptersWithSource.await(sourceChapters, manga, source, false)
|
||||
newChapters.find { it.url == sChapter.url }
|
||||
} else {
|
||||
localChapter
|
||||
}
|
||||
return localChapter
|
||||
?: updateMangaFromRemote(manga, fetchChapters = true)
|
||||
.getOrElse { return null }
|
||||
.newChapters
|
||||
.find { it.url == sChapter.url }
|
||||
}
|
||||
|
||||
sealed interface State {
|
||||
|
||||
@@ -16,13 +16,11 @@ import eu.kanade.core.util.addOrRemove
|
||||
import eu.kanade.core.util.insertSeparators
|
||||
import eu.kanade.domain.chapter.interactor.GetAvailableScanlators
|
||||
import eu.kanade.domain.chapter.interactor.SetReadStatus
|
||||
import eu.kanade.domain.chapter.interactor.SyncChaptersWithSource
|
||||
import eu.kanade.domain.manga.interactor.GetExcludedScanlators
|
||||
import eu.kanade.domain.manga.interactor.SetExcludedScanlators
|
||||
import eu.kanade.domain.manga.interactor.UpdateManga
|
||||
import eu.kanade.domain.manga.model.chaptersFiltered
|
||||
import eu.kanade.domain.manga.model.downloadedFilter
|
||||
import eu.kanade.domain.manga.model.toSManga
|
||||
import eu.kanade.domain.track.interactor.AddTracks
|
||||
import eu.kanade.domain.track.interactor.RefreshTracks
|
||||
import eu.kanade.domain.track.interactor.TrackChapter
|
||||
@@ -36,14 +34,12 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.track.EnhancedTracker
|
||||
import eu.kanade.tachiyomi.data.track.TrackerManager
|
||||
import eu.kanade.tachiyomi.network.HttpException
|
||||
import eu.kanade.tachiyomi.source.Source
|
||||
import eu.kanade.tachiyomi.ui.reader.setting.ReaderPreferences
|
||||
import eu.kanade.tachiyomi.util.chapter.getNextUnread
|
||||
import eu.kanade.tachiyomi.util.removeCovers
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.flow.catch
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.combine
|
||||
@@ -54,13 +50,13 @@ import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||
import mihon.domain.source.interactor.UpdateMangaFromRemote
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.preference.CheckboxState
|
||||
import tachiyomi.core.common.preference.TriState
|
||||
import tachiyomi.core.common.preference.mapAsCheckboxState
|
||||
import tachiyomi.core.common.util.lang.launchIO
|
||||
import tachiyomi.core.common.util.lang.launchNonCancellable
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.lang.withUIContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
@@ -111,13 +107,13 @@ class MangaScreenModel(
|
||||
private val setReadStatus: SetReadStatus = Injekt.get(),
|
||||
private val updateChapter: UpdateChapter = Injekt.get(),
|
||||
private val updateManga: UpdateManga = Injekt.get(),
|
||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get(),
|
||||
private val getCategories: GetCategories = Injekt.get(),
|
||||
private val getTracks: GetTracks = Injekt.get(),
|
||||
private val addTracks: AddTracks = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
private val mangaRepository: MangaRepository = Injekt.get(),
|
||||
private val filterChaptersForDownload: FilterChaptersForDownload = Injekt.get(),
|
||||
private val updateMangaFromRemote: UpdateMangaFromRemote = Injekt.get(),
|
||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||
) : StateScreenModel<MangaScreenModel.State>(State.Loading) {
|
||||
|
||||
@@ -237,11 +233,11 @@ class MangaScreenModel(
|
||||
|
||||
// Fetch info-chapters when needed
|
||||
if (screenModelScope.isActive) {
|
||||
val fetchFromSourceTasks = listOf(
|
||||
async { if (needRefreshInfo) fetchMangaFromSource() },
|
||||
async { if (needRefreshChapter) fetchChaptersFromSource() },
|
||||
fetchAllFromSource(
|
||||
manualFetch = false,
|
||||
fetchDetails = needRefreshInfo,
|
||||
fetchChapters = needRefreshChapter,
|
||||
)
|
||||
fetchFromSourceTasks.awaitAll()
|
||||
}
|
||||
|
||||
// Initial loading finished
|
||||
@@ -252,38 +248,54 @@ class MangaScreenModel(
|
||||
fun fetchAllFromSource(manualFetch: Boolean = true) {
|
||||
screenModelScope.launch {
|
||||
updateSuccessState { it.copy(isRefreshingData = true) }
|
||||
val fetchFromSourceTasks = listOf(
|
||||
async { fetchMangaFromSource(manualFetch) },
|
||||
async { fetchChaptersFromSource(manualFetch) },
|
||||
fetchAllFromSource(
|
||||
manualFetch = manualFetch,
|
||||
fetchDetails = true,
|
||||
fetchChapters = true,
|
||||
)
|
||||
fetchFromSourceTasks.awaitAll()
|
||||
updateSuccessState { it.copy(isRefreshingData = false) }
|
||||
}
|
||||
}
|
||||
|
||||
// Manga info - start
|
||||
|
||||
/**
|
||||
* Fetch manga information from source.
|
||||
*/
|
||||
private suspend fun fetchMangaFromSource(manualFetch: Boolean = false) {
|
||||
private suspend fun fetchAllFromSource(
|
||||
manualFetch: Boolean,
|
||||
fetchDetails: Boolean,
|
||||
fetchChapters: Boolean,
|
||||
) {
|
||||
val state = successState ?: return
|
||||
try {
|
||||
withIOContext {
|
||||
val networkManga = state.source.getMangaDetails(state.manga.toSManga())
|
||||
updateManga.awaitUpdateFromSource(state.manga, networkManga, manualFetch)
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// Ignore early hints "errors" that aren't handled by OkHttp
|
||||
if (e is HttpException && e.code == 103) return
|
||||
withUIContext {
|
||||
val update = updateMangaFromRemote(
|
||||
source = state.source,
|
||||
manga = state.manga,
|
||||
fetchDetails = fetchDetails,
|
||||
fetchChapters = fetchChapters,
|
||||
manualFetch = manualFetch,
|
||||
)
|
||||
.getOrThrow()
|
||||
|
||||
if (manualFetch) {
|
||||
downloadNewChapters(update.newChapters)
|
||||
}
|
||||
}
|
||||
} catch (_: CancellationException) {
|
||||
// ignore
|
||||
} catch (e: Exception) {
|
||||
val message = if (e is NoChaptersException) {
|
||||
context.stringResource(MR.strings.no_chapters_error)
|
||||
} else {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
with(context) { e.formattedMessage }
|
||||
}
|
||||
|
||||
logcat(LogPriority.ERROR, e)
|
||||
screenModelScope.launch {
|
||||
snackbarHostState.showSnackbar(message = with(context) { e.formattedMessage })
|
||||
snackbarHostState.showSnackbar(message = message)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Manga info - start
|
||||
|
||||
fun toggleFavorite() {
|
||||
toggleFavorite(
|
||||
onRemoved = {
|
||||
@@ -548,42 +560,6 @@ class MangaScreenModel(
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests an updated list of chapters from the source.
|
||||
*/
|
||||
private suspend fun fetchChaptersFromSource(manualFetch: Boolean = false) {
|
||||
val state = successState ?: return
|
||||
try {
|
||||
withIOContext {
|
||||
val chapters = state.source.getChapterList(state.manga.toSManga())
|
||||
|
||||
val newChapters = syncChaptersWithSource.await(
|
||||
chapters,
|
||||
state.manga,
|
||||
state.source,
|
||||
manualFetch,
|
||||
)
|
||||
|
||||
if (manualFetch) {
|
||||
downloadNewChapters(newChapters)
|
||||
}
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
val message = if (e is NoChaptersException) {
|
||||
context.stringResource(MR.strings.no_chapters_error)
|
||||
} else {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
with(context) { e.formattedMessage }
|
||||
}
|
||||
|
||||
screenModelScope.launch {
|
||||
snackbarHostState.showSnackbar(message = message)
|
||||
}
|
||||
val newManga = mangaRepository.getMangaById(mangaId)
|
||||
updateSuccessState { it.copy(manga = newManga, isRefreshingData = false) }
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws IllegalStateException if the swipe action is [LibraryPreferences.ChapterSwipeAction.Disabled]
|
||||
*/
|
||||
|
||||
@@ -13,33 +13,6 @@ import uy.kohesive.injekt.api.get
|
||||
import java.io.InputStream
|
||||
import java.time.Instant
|
||||
|
||||
/**
|
||||
* Call before updating [Manga.thumbnail_url] to ensure old cover can be cleared from cache
|
||||
*/
|
||||
fun Manga.prepUpdateCover(coverCache: CoverCache, remoteManga: SManga, refreshSameUrl: Boolean): Manga {
|
||||
// Never refresh covers if the new url is null, as the current url has possibly become invalid
|
||||
val newUrl = remoteManga.thumbnail_url ?: return this
|
||||
|
||||
// Never refresh covers if the url is empty to avoid "losing" existing covers
|
||||
if (newUrl.isEmpty()) return this
|
||||
|
||||
if (!refreshSameUrl && thumbnailUrl == newUrl) return this
|
||||
|
||||
return when {
|
||||
isLocal() -> {
|
||||
this.copy(coverLastModified = Instant.now().toEpochMilli())
|
||||
}
|
||||
hasCustomCover(coverCache) -> {
|
||||
coverCache.deleteFromCache(this, false)
|
||||
this
|
||||
}
|
||||
else -> {
|
||||
coverCache.deleteFromCache(this, false)
|
||||
this.copy(coverLastModified = Instant.now().toEpochMilli())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun Manga.removeCovers(coverCache: CoverCache = Injekt.get()): Manga {
|
||||
if (isLocal()) return this
|
||||
return if (coverCache.deleteFromCache(this, true) > 0) {
|
||||
|
||||
Reference in New Issue
Block a user