Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add option to skip downloading duplicate read chapters #1125

Merged
merged 30 commits into from
Aug 23, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
0941527
Add query to get chapter count by manga and chapter number
shabnix Aug 12, 2024
8ed386f
Add functions to get chapter count by manga and chapter number
shabnix Aug 12, 2024
e758c7a
Only count read chapters
shabnix Aug 13, 2024
374b7b0
Add interactor
shabnix Aug 13, 2024
8925463
Savepoint
shabnix Aug 13, 2024
a203209
Extract new chapter logic to separate function
shabnix Aug 14, 2024
cd0d67c
Update javadocs
shabnix Aug 14, 2024
fe14a68
Add preference to toggle new functionality
shabnix Aug 14, 2024
b84b58b
Add todo
shabnix Aug 14, 2024
62894e3
Add debug logcat
shabnix Aug 14, 2024
c5d024b
Use string resource instead of hardcoding title
shabnix Aug 14, 2024
70a1d25
Add temporary logcat for debugging
shabnix Aug 14, 2024
96ce079
Fix detekt issues
shabnix Aug 14, 2024
8e9dfac
Update javadocs
shabnix Aug 14, 2024
5263366
Update download unread chapters preference
shabnix Aug 14, 2024
b057dfb
Remove debug logcat calls
shabnix Aug 14, 2024
770d7e4
Update javadocs
shabnix Aug 14, 2024
ff884f0
Resolve issue where read chapters were still being downloaded during …
shabnix Aug 16, 2024
83a6580
Apply code review changes
shabnix Aug 20, 2024
4892ca7
Apply code review changes
shabnix Aug 20, 2024
8fff3e0
Revert "Apply code review changes"
shabnix Aug 20, 2024
1db1610
Revert "Apply code review changes"
shabnix Aug 20, 2024
2825fec
Group download chapter logic inside the interactor GetChaptersToDownload
shabnix Aug 20, 2024
29a805e
Update javadocs
shabnix Aug 20, 2024
f901e52
Apply code review
shabnix Aug 21, 2024
91e6779
Apply code review
shabnix Aug 21, 2024
c57eca8
Apply code review
shabnix Aug 22, 2024
5e3b91d
Update CHANGELOG.md to include the new feature
shabnix Aug 22, 2024
21bb71d
Run spotless
shabnix Aug 23, 2024
cfff184
Update domain/src/main/java/mihon/domain/chapter/interactor/FilterCha…
AntsyLich Aug 23, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/java/eu/kanade/domain/DomainModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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()) }
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,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()

Expand Down Expand Up @@ -152,6 +153,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(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.source.model.SManga
import eu.kanade.tachiyomi.source.model.UpdateStrategy
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
Expand All @@ -39,15 +38,14 @@ import kotlinx.coroutines.ensureActive
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.category.interactor.GetCategories
import tachiyomi.domain.category.model.Category
import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.chapter.model.NoChaptersException
import tachiyomi.domain.download.service.DownloadPreferences
import tachiyomi.domain.library.model.LibraryManga
import tachiyomi.domain.library.service.LibraryPreferences
import tachiyomi.domain.library.service.LibraryPreferences.Companion.DEVICE_CHARGING
Expand Down Expand Up @@ -78,16 +76,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()

private val notifier = LibraryUpdateNotifier(context)

Expand Down Expand Up @@ -270,9 +267,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
.sortedByDescending { it.sourceOrder }

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

Expand Down
17 changes: 6 additions & 11 deletions app/src/main/java/eu/kanade/tachiyomi/ui/manga/MangaScreenModel.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ 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.shouldDownloadNewChapters
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.toImmutableList
import kotlinx.coroutines.async
Expand All @@ -48,6 +47,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
Expand All @@ -67,7 +67,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.GetDuplicateLibraryManga
import tachiyomi.domain.manga.interactor.GetMangaWithChapters
Expand All @@ -87,7 +86,6 @@ class MangaScreenModel(
val context: Context,
val mangaId: Long,
private val isFromSource: Boolean,
private val downloadPreferences: DownloadPreferences = Injekt.get(),
private val libraryPreferences: LibraryPreferences = Injekt.get(),
readerPreferences: ReaderPreferences = Injekt.get(),
private val trackerManager: TrackerManager = Injekt.get(),
Expand All @@ -109,6 +107,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) {

Expand Down Expand Up @@ -771,15 +770,11 @@ 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)
) {
return@launchNonCancellable
}
val chaptersToDownload = filterChaptersForDownload.await(manga, chapters)

downloadChapters(chapters)
if (chaptersToDownload.isNotEmpty()) {
downloadChapters(chaptersToDownload)
}
}
}

Expand Down
26 changes: 0 additions & 26 deletions app/src/main/java/eu/kanade/tachiyomi/util/MangaExtensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand Down
Original file line number Diff line number Diff line change
@@ -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
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,4 +42,6 @@ class DownloadPreferences(
"download_new_categories_exclude",
emptySet(),
)

fun downloadNewUnreadChaptersOnly() = preferenceStore.getBoolean("download_new_unread_chapters_only", false)
}
1 change: 1 addition & 0 deletions i18n/src/commonMain/moko-resources/base/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -484,6 +484,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>
Expand Down