Add option to skip downloading duplicate read chapters (mihonapp/mihon#1125)
* Add query to get chapter count by manga and chapter number * Add functions to get chapter count by manga and chapter number * Only count read chapters * Add interactor * Savepoint * Extract new chapter logic to separate function * Update javadocs * Add preference to toggle new functionality * Add todo * Add debug logcat * Use string resource instead of hardcoding title * Add temporary logcat for debugging * Fix detekt issues * Update javadocs * Update download unread chapters preference * Remove debug logcat calls * Update javadocs * Resolve issue where read chapters were still being downloaded during manual manga fetch * Apply code review changes * Apply code review changes * Revert "Apply code review changes" This reverts commit 1a2dce78acc66a7c529ce5b572bdaf94804b1a30. * Revert "Apply code review changes" This reverts commit ac2a77829313967ad39ce3cb0c0231083b9d640d. * Group download chapter logic inside the interactor GetChaptersToDownload * Update javadocs * Apply code review * Apply code review * Apply code review * Update CHANGELOG.md to include the new feature * Run spotless * Update domain/src/main/java/mihon/domain/chapter/interactor/FilterChaptersForDownload.kt --------- Co-authored-by: AntsyLich <59261191+AntsyLich@users.noreply.github.com> (cherry picked from commit ca968f162ef7a61a9036b7ab9bea407a6334801d)
This commit is contained in:
parent
266f449614
commit
94acbb08d7
10 changed files with 119 additions and 61 deletions
|
|
@ -21,6 +21,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
- Copy Tracker URL option to tracker sheet ([@mm12](https://github.com/mm12)) ([#1101](https://github.com/mihonapp/mihon/pull/1101))
|
||||
- A button to exclude all scanlators in exclude scanlators dialog ([@AntsyLich](https://github.com/AntsyLich)) ([`84b2164`](https://github.com/mihonapp/mihon/commit/84b2164787a795f3fd757c325cbfb6ef660ac3a3))
|
||||
- Open in browser option to reader menu ([@mm12](https://github.com/mm12)) ([#1110](https://github.com/mihonapp/mihon/pull/1110))
|
||||
- Option to skip downloading duplicate read chapters ([@shabnix](https://github.com/shabnix)) ([#1125](https://github.com/mihonapp/mihon/pull/1125))
|
||||
|
||||
### Changed
|
||||
- Read archive files from memory instead of extracting files to internal storage ([@FooIbar](https://github.com/FooIbar)) ([#326](https://github.com/mihonapp/mihon/pull/326))
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ import eu.kanade.domain.track.interactor.RefreshTracks
|
|||
import eu.kanade.domain.track.interactor.SyncChapterProgressWithTrack
|
||||
import eu.kanade.domain.track.interactor.TrackChapter
|
||||
import mihon.data.repository.ExtensionRepoRepositoryImpl
|
||||
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||
import mihon.domain.extensionrepo.interactor.CreateExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.DeleteExtensionRepo
|
||||
import mihon.domain.extensionrepo.interactor.GetExtensionRepo
|
||||
|
|
@ -152,6 +153,7 @@ class DomainModule : InjektModule {
|
|||
addFactory { ShouldUpdateDbChapter() }
|
||||
addFactory { SyncChaptersWithSource(get(), get(), get(), get(), get(), get(), get(), get()) }
|
||||
addFactory { GetAvailableScanlators(get()) }
|
||||
addFactory { FilterChaptersForDownload(get(), get(), get()) }
|
||||
|
||||
addSingletonFactory<HistoryRepository> { HistoryRepositoryImpl(get()) }
|
||||
addFactory { GetHistory(get()) }
|
||||
|
|
|
|||
|
|
@ -28,6 +28,7 @@ import uy.kohesive.injekt.Injekt
|
|||
import uy.kohesive.injekt.api.get
|
||||
|
||||
object SettingsDownloadScreen : SearchableSettings {
|
||||
private fun readResolve(): Any = SettingsDownloadScreen
|
||||
|
||||
@ReadOnlyComposable
|
||||
@Composable
|
||||
|
|
@ -124,6 +125,7 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||
allCategories: List<Category>,
|
||||
): Preference.PreferenceGroup {
|
||||
val downloadNewChaptersPref = downloadPreferences.downloadNewChapters()
|
||||
val downloadNewUnreadChaptersOnlyPref = downloadPreferences.downloadNewUnreadChaptersOnly()
|
||||
val downloadNewChapterCategoriesPref = downloadPreferences.downloadNewChapterCategories()
|
||||
val downloadNewChapterCategoriesExcludePref = downloadPreferences.downloadNewChapterCategoriesExclude()
|
||||
|
||||
|
|
@ -156,6 +158,11 @@ object SettingsDownloadScreen : SearchableSettings {
|
|||
pref = downloadNewChaptersPref,
|
||||
title = stringResource(MR.strings.pref_download_new),
|
||||
),
|
||||
Preference.PreferenceItem.SwitchPreference(
|
||||
pref = downloadNewUnreadChaptersOnlyPref,
|
||||
title = stringResource(MR.strings.pref_download_new_unread_chapters_only),
|
||||
enabled = downloadNewChapters,
|
||||
),
|
||||
Preference.PreferenceItem.TextPreference(
|
||||
title = stringResource(MR.strings.categories),
|
||||
subtitle = getCategoriesLabel(
|
||||
|
|
|
|||
|
|
@ -35,7 +35,6 @@ import eu.kanade.tachiyomi.source.model.SManga
|
|||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
import eu.kanade.tachiyomi.source.online.all.MergedSource
|
||||
import eu.kanade.tachiyomi.util.prepUpdateCover
|
||||
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
|
||||
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||
import eu.kanade.tachiyomi.util.system.createFileInCacheDir
|
||||
import eu.kanade.tachiyomi.util.system.isConnectedToWifi
|
||||
|
|
@ -59,17 +58,16 @@ import kotlinx.coroutines.runBlocking
|
|||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import logcat.LogPriority
|
||||
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.preference.getAndSet
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.core.common.util.system.logcat
|
||||
import tachiyomi.domain.UnsortedPreferences
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.category.model.Category
|
||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.chapter.model.NoChaptersException
|
||||
import tachiyomi.domain.download.service.DownloadPreferences
|
||||
import tachiyomi.domain.library.model.GroupLibraryMode
|
||||
import tachiyomi.domain.library.model.LibraryGroup
|
||||
import tachiyomi.domain.library.model.LibraryManga
|
||||
|
|
@ -109,16 +107,15 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||
CoroutineWorker(context, workerParams) {
|
||||
|
||||
private val sourceManager: SourceManager = Injekt.get()
|
||||
private val downloadPreferences: DownloadPreferences = 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 getCategories: GetCategories = Injekt.get()
|
||||
private val syncChaptersWithSource: SyncChaptersWithSource = Injekt.get()
|
||||
private val fetchInterval: FetchInterval = Injekt.get()
|
||||
private val filterChaptersForDownload: FilterChaptersForDownload = Injekt.get()
|
||||
|
||||
// SY -->
|
||||
private val getFavorites: GetFavorites = Injekt.get()
|
||||
|
|
@ -444,9 +441,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
|||
// SY <--
|
||||
|
||||
if (newChapters.isNotEmpty()) {
|
||||
val categoryIds = getCategories.await(manga.id).map { it.id }
|
||||
if (manga.shouldDownloadNewChapters(categoryIds, downloadPreferences)) {
|
||||
downloadChapters(manga, newChapters)
|
||||
val chaptersToDownload = filterChaptersForDownload.await(manga, newChapters)
|
||||
|
||||
if (chaptersToDownload.isNotEmpty()) {
|
||||
downloadChapters(manga, chaptersToDownload)
|
||||
hasDownloads.set(true)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.source.model.SChapter
|
|||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.copy
|
||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||
import eu.kanade.tachiyomi.util.shouldDownloadNewChapters
|
||||
import exh.source.MERGED_SOURCE_ID
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.async
|
||||
|
|
@ -19,11 +18,10 @@ import kotlinx.coroutines.awaitAll
|
|||
import kotlinx.coroutines.supervisorScope
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||
import okhttp3.Response
|
||||
import tachiyomi.core.common.util.lang.withIOContext
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.download.service.DownloadPreferences
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.interactor.GetMergedReferencesById
|
||||
import tachiyomi.domain.manga.interactor.NetworkToLocalManga
|
||||
|
|
@ -38,10 +36,12 @@ class MergedSource : HttpSource() {
|
|||
private val syncChaptersWithSource: SyncChaptersWithSource by injectLazy()
|
||||
private val networkToLocalManga: NetworkToLocalManga by injectLazy()
|
||||
private val updateManga: UpdateManga by injectLazy()
|
||||
private val getCategories: GetCategories by injectLazy()
|
||||
private val sourceManager: SourceManager by injectLazy()
|
||||
private val downloadManager: DownloadManager by injectLazy()
|
||||
private val downloadPreferences: DownloadPreferences by injectLazy()
|
||||
|
||||
// KMK -->
|
||||
private val filterChaptersForDownload: FilterChaptersForDownload by injectLazy()
|
||||
// KMK <--
|
||||
|
||||
override val id: Long = MERGED_SOURCE_ID
|
||||
|
||||
|
|
@ -119,13 +119,6 @@ class MergedSource : HttpSource() {
|
|||
"Manga references are empty, chapters unavailable, merge is likely corrupted"
|
||||
}
|
||||
|
||||
val ifDownloadNewChapters = downloadChapters &&
|
||||
manga.shouldDownloadNewChapters(
|
||||
getCategories.await(manga.id).map {
|
||||
it.id
|
||||
},
|
||||
downloadPreferences,
|
||||
)
|
||||
val semaphore = Semaphore(5)
|
||||
var exception: Exception? = null
|
||||
return supervisorScope {
|
||||
|
|
@ -140,9 +133,16 @@ class MergedSource : HttpSource() {
|
|||
val (source, loadedManga, reference) = it.load()
|
||||
if (loadedManga != null && reference.getChapterUpdates) {
|
||||
val chapterList = source.getChapterList(loadedManga.toSManga())
|
||||
val results =
|
||||
val chapters =
|
||||
syncChaptersWithSource.await(chapterList, loadedManga, source)
|
||||
if (ifDownloadNewChapters && reference.downloadChapters) {
|
||||
// KMK -->
|
||||
// KMK: this should check if user preferences & manga's categories allowed to download
|
||||
// KMK: the checking for skip duplicated-read chapters might not work for merged mangas but won't affect the download
|
||||
val results = filterChaptersForDownload.await(manga, chapters)
|
||||
if (downloadChapters &&
|
||||
// KMK <--
|
||||
reference.downloadChapters
|
||||
) {
|
||||
downloadManager.downloadChapters(
|
||||
loadedManga,
|
||||
results,
|
||||
|
|
|
|||
|
|
@ -65,7 +65,6 @@ import eu.kanade.tachiyomi.ui.manga.RelatedManga.Companion.sorted
|
|||
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.shouldDownloadNewChapters
|
||||
import eu.kanade.tachiyomi.util.system.getBitmapOrNull
|
||||
import eu.kanade.tachiyomi.util.system.toast
|
||||
import exh.debug.DebugToggles
|
||||
|
|
@ -102,6 +101,7 @@ import kotlinx.coroutines.flow.update
|
|||
import kotlinx.coroutines.isActive
|
||||
import kotlinx.coroutines.launch
|
||||
import logcat.LogPriority
|
||||
import mihon.domain.chapter.interactor.FilterChaptersForDownload
|
||||
import tachiyomi.core.common.i18n.stringResource
|
||||
import tachiyomi.core.common.preference.CheckboxState
|
||||
import tachiyomi.core.common.preference.TriState
|
||||
|
|
@ -123,7 +123,6 @@ import tachiyomi.domain.chapter.model.ChapterUpdate
|
|||
import tachiyomi.domain.chapter.model.NoChaptersException
|
||||
import tachiyomi.domain.chapter.service.calculateChapterGap
|
||||
import tachiyomi.domain.chapter.service.getChapterSort
|
||||
import tachiyomi.domain.download.service.DownloadPreferences
|
||||
import tachiyomi.domain.library.service.LibraryPreferences
|
||||
import tachiyomi.domain.manga.interactor.DeleteByMergeId
|
||||
import tachiyomi.domain.manga.interactor.DeleteMangaById
|
||||
|
|
@ -170,7 +169,6 @@ class MangaScreenModel(
|
|||
private val isFromSource: Boolean,
|
||||
val smartSearched: Boolean,
|
||||
// SY <--
|
||||
private val downloadPreferences: DownloadPreferences = Injekt.get(),
|
||||
private val libraryPreferences: LibraryPreferences = Injekt.get(),
|
||||
readerPreferences: ReaderPreferences = Injekt.get(),
|
||||
private val uiPreferences: UiPreferences = Injekt.get(),
|
||||
|
|
@ -215,6 +213,7 @@ class MangaScreenModel(
|
|||
private val addTracks: AddTracks = Injekt.get(),
|
||||
private val setMangaCategories: SetMangaCategories = Injekt.get(),
|
||||
private val mangaRepository: MangaRepository = Injekt.get(),
|
||||
private val filterChaptersForDownload: FilterChaptersForDownload = Injekt.get(),
|
||||
val snackbarHostState: SnackbarHostState = SnackbarHostState(),
|
||||
) : StateScreenModel<MangaScreenModel.State>(State.Loading) {
|
||||
|
||||
|
|
@ -1559,17 +1558,14 @@ class MangaScreenModel(
|
|||
private fun downloadNewChapters(chapters: List<Chapter>) {
|
||||
screenModelScope.launchNonCancellable {
|
||||
val manga = successState?.manga ?: return@launchNonCancellable
|
||||
val categories = getCategories.await(manga.id).map { it.id }
|
||||
if (
|
||||
chapters.isEmpty() ||
|
||||
!manga.shouldDownloadNewChapters(categories, downloadPreferences) ||
|
||||
// EXH -->
|
||||
manga.isEhBasedManga()
|
||||
// EXH <--
|
||||
) {
|
||||
return@launchNonCancellable
|
||||
// EXH -->
|
||||
if (manga.isEhBasedManga()) return@launchNonCancellable
|
||||
// EXH <--
|
||||
val chaptersToDownload = filterChaptersForDownload.await(manga, chapters)
|
||||
|
||||
if (chaptersToDownload.isNotEmpty()) {
|
||||
downloadChapters(chaptersToDownload)
|
||||
}
|
||||
downloadChapters(chapters)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import eu.kanade.domain.manga.model.hasCustomCover
|
|||
import eu.kanade.domain.manga.model.toSManga
|
||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import tachiyomi.domain.download.service.DownloadPreferences
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.source.local.image.LocalCoverManager
|
||||
import tachiyomi.source.local.isLocal
|
||||
|
|
@ -50,31 +49,6 @@ fun Manga.removeCovers(coverCache: CoverCache = Injekt.get()): Manga {
|
|||
}
|
||||
}
|
||||
|
||||
fun Manga.shouldDownloadNewChapters(dbCategories: List<Long>, preferences: DownloadPreferences): Boolean {
|
||||
if (!favorite) return false
|
||||
|
||||
val categories = dbCategories.ifEmpty { listOf(0L) }
|
||||
|
||||
// Boolean to determine if user wants to automatically download new chapters.
|
||||
val downloadNewChapters = preferences.downloadNewChapters().get()
|
||||
if (!downloadNewChapters) return false
|
||||
|
||||
val includedCategories = preferences.downloadNewChapterCategories().get().map { it.toLong() }
|
||||
val excludedCategories = preferences.downloadNewChapterCategoriesExclude().get().map { it.toLong() }
|
||||
|
||||
// Default: Download from all categories
|
||||
if (includedCategories.isEmpty() && excludedCategories.isEmpty()) return true
|
||||
|
||||
// In excluded category
|
||||
if (categories.any { it in excludedCategories }) return false
|
||||
|
||||
// Included category not selected
|
||||
if (includedCategories.isEmpty()) return true
|
||||
|
||||
// In included category
|
||||
return categories.any { it in includedCategories }
|
||||
}
|
||||
|
||||
suspend fun Manga.editCover(
|
||||
coverManager: LocalCoverManager,
|
||||
stream: InputStream,
|
||||
|
|
|
|||
|
|
@ -0,0 +1,77 @@
|
|||
package mihon.domain.chapter.interactor
|
||||
|
||||
import tachiyomi.domain.category.interactor.GetCategories
|
||||
import tachiyomi.domain.chapter.interactor.GetChaptersByMangaId
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.download.service.DownloadPreferences
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
|
||||
/**
|
||||
* Interactor responsible for determining which chapters of a manga should be downloaded.
|
||||
*
|
||||
* @property getChaptersByMangaId Interactor for retrieving chapters by manga ID.
|
||||
* @property downloadPreferences User preferences related to chapter downloads.
|
||||
* @property getCategories Interactor for retrieving categories associated with a manga.
|
||||
*/
|
||||
class FilterChaptersForDownload(
|
||||
private val getChaptersByMangaId: GetChaptersByMangaId,
|
||||
private val downloadPreferences: DownloadPreferences,
|
||||
private val getCategories: GetCategories,
|
||||
) {
|
||||
|
||||
/**
|
||||
* Determines which chapters of a manga should be downloaded based on user preferences.
|
||||
*
|
||||
* @param manga The manga for which chapters may be downloaded.
|
||||
* @param newChapters The list of new chapters available for the manga.
|
||||
* @return A list of chapters that should be downloaded
|
||||
*/
|
||||
suspend fun await(manga: Manga, newChapters: List<Chapter>): List<Chapter> {
|
||||
if (
|
||||
newChapters.isEmpty() ||
|
||||
!downloadPreferences.downloadNewChapters().get() ||
|
||||
!manga.shouldDownloadNewChapters()
|
||||
) {
|
||||
return emptyList()
|
||||
}
|
||||
|
||||
if (!downloadPreferences.downloadNewUnreadChaptersOnly().get()) return newChapters
|
||||
|
||||
val readChapterNumbers = getChaptersByMangaId.await(manga.id)
|
||||
.asSequence()
|
||||
.filter { it.read && it.isRecognizedNumber }
|
||||
.map { it.chapterNumber }
|
||||
.toSet()
|
||||
|
||||
return newChapters.filterNot { it.chapterNumber in readChapterNumbers }
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether new chapters should be downloaded for the manga based on user preferences and the
|
||||
* categories to which the manga belongs.
|
||||
*
|
||||
* @return `true` if chapters of the manga should be downloaded
|
||||
*/
|
||||
private suspend fun Manga.shouldDownloadNewChapters(): Boolean {
|
||||
if (!favorite) return false
|
||||
|
||||
val categories = getCategories.await(id).map { it.id }.ifEmpty { listOf(DEFAULT_CATEGORY_ID) }
|
||||
val includedCategories = downloadPreferences.downloadNewChapterCategories().get().map { it.toLong() }
|
||||
val excludedCategories = downloadPreferences.downloadNewChapterCategoriesExclude().get().map { it.toLong() }
|
||||
|
||||
return when {
|
||||
// Default Download from all categories
|
||||
includedCategories.isEmpty() && excludedCategories.isEmpty() -> true
|
||||
// In excluded category
|
||||
categories.any { it in excludedCategories } -> false
|
||||
// Included category not selected
|
||||
includedCategories.isEmpty() -> true
|
||||
// In included category
|
||||
else -> categories.any { it in includedCategories }
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DEFAULT_CATEGORY_ID = 0L
|
||||
}
|
||||
}
|
||||
|
|
@ -43,6 +43,8 @@ class DownloadPreferences(
|
|||
emptySet(),
|
||||
)
|
||||
|
||||
fun downloadNewUnreadChaptersOnly() = preferenceStore.getBoolean("download_new_unread_chapters_only", false)
|
||||
|
||||
// KMK -->
|
||||
fun downloadCacheRenewInterval() = preferenceStore.getInt("download_cache_renew_interval", 1)
|
||||
// KMK <--
|
||||
|
|
|
|||
|
|
@ -486,6 +486,7 @@
|
|||
<string name="fifth_to_last">Fifth to last read chapter</string>
|
||||
<string name="pref_category_auto_download">Auto-download</string>
|
||||
<string name="pref_download_new">Download new chapters</string>
|
||||
<string name="pref_download_new_unread_chapters_only">Skip downloading duplicate read chapters</string>
|
||||
<string name="pref_download_new_categories_details">Entries in excluded categories will not be downloaded even if they are also in included categories.</string>
|
||||
<string name="download_ahead">Download ahead</string>
|
||||
<string name="auto_download_while_reading">Auto download while reading</string>
|
||||
|
|
|
|||
Loading…
Reference in a new issue