diff --git a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt index 630335be43..83c0be1b5c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/Migrations.kt @@ -5,7 +5,7 @@ import androidx.core.content.edit import androidx.preference.PreferenceManager import eu.kanade.tachiyomi.data.backup.BackupCreatorJob import eu.kanade.tachiyomi.data.library.LibraryUpdateJob -import eu.kanade.tachiyomi.data.preference.MANGA_ONGOING +import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED import eu.kanade.tachiyomi.data.preference.PreferenceKeys import eu.kanade.tachiyomi.data.preference.PreferenceValues import eu.kanade.tachiyomi.data.preference.PreferencesHelper @@ -244,7 +244,7 @@ object Migrations { if (oldVersion < 72) { val oldUpdateOngoingOnly = prefs.getBoolean("pref_update_only_non_completed_key", true) if (!oldUpdateOngoingOnly) { - preferences.libraryUpdateMangaRestriction() -= MANGA_ONGOING + preferences.libraryUpdateMangaRestriction() -= MANGA_NON_COMPLETED } } if (oldVersion < 75) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt index 1ba931e97e..b18e162761 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateNotifier.kt @@ -92,18 +92,19 @@ class LibraryUpdateNotifier(private val context: Context) { /** * Shows notification containing update entries that failed with action to open full log. * - * @param errors List of entry titles that failed to update. + * @param skipped Number of entries that were skipped during the update. + * @param failed Number of entries that failed to update. * @param uri Uri for error log file containing all titles that failed. */ - fun showUpdateErrorNotification(errors: List, uri: Uri) { - if (errors.isEmpty()) { + fun showUpdateErrorNotification(skipped: Int, failed: Int, uri: Uri) { + if (skipped == 0 && failed == 0) { return } context.notificationManager.notify( Notifications.ID_LIBRARY_ERROR, context.notificationBuilder(Notifications.CHANNEL_LIBRARY_ERROR) { - setContentTitle(context.resources.getQuantityString(R.plurals.notification_update_error, errors.size, errors.size)) + setContentTitle(context.resources.getString(R.string.notification_update_skipped_error, skipped, failed)) setContentText(context.getString(R.string.action_show_errors)) setSmallIcon(R.drawable.ic_tachi) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt index 209323527d..e556a6f124 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/library/LibraryUpdateService.kt @@ -19,9 +19,9 @@ import eu.kanade.tachiyomi.data.download.DownloadManager import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start import eu.kanade.tachiyomi.data.notification.Notifications -import eu.kanade.tachiyomi.data.preference.MANGA_FULLY_READ -import eu.kanade.tachiyomi.data.preference.MANGA_ONGOING -import eu.kanade.tachiyomi.data.preference.MANGA_STARTED +import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD +import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED +import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.track.EnhancedTrackService import eu.kanade.tachiyomi.data.track.TrackManager @@ -141,7 +141,7 @@ class LibraryUpdateService( true } else { - instance?.addMangaToQueue(category?.id ?: -1, target) + instance?.addMangaToQueue(category?.id ?: -1) false } } @@ -213,7 +213,7 @@ class LibraryUpdateService( // Update favorite manga val categoryId = intent.getIntExtra(KEY_CATEGORY, -1) - addMangaToQueue(categoryId, target) + addMangaToQueue(categoryId) // Destroy service when completed or in case of an error. val handler = CoroutineExceptionHandler { _, exception -> @@ -238,10 +238,10 @@ class LibraryUpdateService( * @param category the ID of the category to update, or -1 if no category specified. * @param target the target to update. */ - fun addMangaToQueue(categoryId: Int, target: Target) { + fun addMangaToQueue(categoryId: Int) { val libraryManga = db.getLibraryMangas().executeAsBlocking() - var listToUpdate = if (categoryId != -1) { + val listToUpdate = if (categoryId != -1) { libraryManga.filter { it.category == categoryId } } else { val categoriesToUpdate = preferences.libraryUpdateCategories().get().map(String::toInt) @@ -261,23 +261,6 @@ class LibraryUpdateService( listToInclude.minus(listToExclude) } - if (target == Target.CHAPTERS) { - val restrictions = preferences.libraryUpdateMangaRestriction().get() - if (MANGA_ONGOING in restrictions) { - listToUpdate = listToUpdate.filterNot { it.status == SManga.COMPLETED } - } - if (MANGA_FULLY_READ in restrictions) { - listToUpdate = listToUpdate.filter { it.unreadCount == 0 } - } - if (MANGA_STARTED in restrictions) { - listToUpdate = listToUpdate.filter { manga -> - // If the manga has 0 chapters you can't actually start reading it - if (manga.totalChapters == 0) true - else manga.hasStarted - } - } - } - mangaToUpdate = listToUpdate .distinctBy { it.id } .sortedBy { it.title } @@ -306,10 +289,12 @@ class LibraryUpdateService( val progressCount = AtomicInteger(0) val currentlyUpdatingManga = CopyOnWriteArrayList() val newUpdates = CopyOnWriteArrayList>>() + val skippedUpdates = CopyOnWriteArrayList>() val failedUpdates = CopyOnWriteArrayList>() val hasDownloads = AtomicBoolean(false) val loggedServices by lazy { trackManager.services.filter { it.isLogged } } val currentUnreadUpdatesCount = preferences.unreadUpdatesCount().get() + val restrictions = preferences.libraryUpdateMangaRestriction().get() withIOContext { mangaToUpdate.groupBy { it.source } @@ -328,6 +313,16 @@ class LibraryUpdateService( manga, ) { manga -> try { + if (MANGA_NON_COMPLETED in restrictions && manga.status == SManga.COMPLETED) { + throw SkipUpdateException(getString(R.string.skipped_reason_completed)) + } + if (MANGA_HAS_UNREAD in restrictions && manga.unreadCount != 0) { + throw SkipUpdateException(getString(R.string.skipped_reason_not_caught_up)) + } + if (MANGA_NON_READ in restrictions && manga.totalChapters > 0 && !manga.hasStarted) { + throw SkipUpdateException(getString(R.string.skipped_reason_not_started)) + } + val (newChapters, _) = updateManga(manga) if (newChapters.isNotEmpty()) { @@ -342,6 +337,8 @@ class LibraryUpdateService( .toTypedArray() ) } + } catch (e: SkipUpdateException) { + skippedUpdates.add(manga to e.message) } catch (e: Throwable) { val errorMessage = when (e) { is NoChaptersException -> { @@ -380,11 +377,12 @@ class LibraryUpdateService( } } - if (failedUpdates.isNotEmpty()) { - val errorFile = writeErrorFile(failedUpdates) + if (skippedUpdates.isNotEmpty() || failedUpdates.isNotEmpty()) { + val errorFile = writeErrorFile(skippedUpdates + failedUpdates) notifier.showUpdateErrorNotification( - failedUpdates.map { it.first.title }, - errorFile.getUriCompat(this) + skippedUpdates.size, + failedUpdates.size, + errorFile.getUriCompat(this), ) } } @@ -587,3 +585,5 @@ class LibraryUpdateService( private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60 private const val ERROR_LOG_HELP_URL = "https://tachiyomi.org/help/guides/troubleshooting" + +private class SkipUpdateException(override val message: String) : RuntimeException() diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt index 56a0180021..9ec0005695 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferenceValues.kt @@ -5,9 +5,9 @@ import eu.kanade.tachiyomi.R const val DEVICE_ONLY_ON_WIFI = "wifi" const val DEVICE_CHARGING = "ac" -const val MANGA_ONGOING = "manga_ongoing" -const val MANGA_FULLY_READ = "manga_fully_read" -const val MANGA_STARTED = "manga_started" +const val MANGA_NON_COMPLETED = "manga_ongoing" +const val MANGA_HAS_UNREAD = "manga_fully_read" +const val MANGA_NON_READ = "manga_started" /** * This class stores the values for the preferences in the application. diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt index 14d2cca0d1..f723c3e426 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/preference/PreferencesHelper.kt @@ -224,7 +224,7 @@ class PreferencesHelper(val context: Context) { fun libraryUpdateInterval() = flowPrefs.getInt("pref_library_update_interval_key", 24) fun libraryUpdateDeviceRestriction() = flowPrefs.getStringSet("library_update_restriction", setOf(DEVICE_ONLY_ON_WIFI)) - fun libraryUpdateMangaRestriction() = flowPrefs.getStringSet("library_update_manga_restriction", setOf(MANGA_FULLY_READ, MANGA_ONGOING, MANGA_STARTED)) + fun libraryUpdateMangaRestriction() = flowPrefs.getStringSet("library_update_manga_restriction", setOf(MANGA_HAS_UNREAD, MANGA_NON_COMPLETED, MANGA_NON_READ)) fun showUpdatesNavBadge() = flowPrefs.getBoolean("library_update_show_tab_badge", false) fun unreadUpdatesCount() = flowPrefs.getInt("library_unread_updates_count", 0) diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt index d594b8f924..956ec8853e 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/setting/SettingsLibraryController.kt @@ -13,9 +13,9 @@ import eu.kanade.tachiyomi.data.database.models.Category import eu.kanade.tachiyomi.data.library.LibraryUpdateJob import eu.kanade.tachiyomi.data.preference.DEVICE_CHARGING import eu.kanade.tachiyomi.data.preference.DEVICE_ONLY_ON_WIFI -import eu.kanade.tachiyomi.data.preference.MANGA_FULLY_READ -import eu.kanade.tachiyomi.data.preference.MANGA_ONGOING -import eu.kanade.tachiyomi.data.preference.MANGA_STARTED +import eu.kanade.tachiyomi.data.preference.MANGA_HAS_UNREAD +import eu.kanade.tachiyomi.data.preference.MANGA_NON_COMPLETED +import eu.kanade.tachiyomi.data.preference.MANGA_NON_READ import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.data.track.TrackManager import eu.kanade.tachiyomi.databinding.PrefLibraryColumnsBinding @@ -196,16 +196,16 @@ class SettingsLibraryController : SettingsController() { multiSelectListPreference { bindTo(preferences.libraryUpdateMangaRestriction()) titleRes = R.string.pref_library_update_manga_restriction - entriesRes = arrayOf(R.string.pref_update_only_completely_read, R.string.pref_update_only_non_completed, R.string.pref_update_only_started) - entryValues = arrayOf(MANGA_FULLY_READ, MANGA_ONGOING, MANGA_STARTED) + entriesRes = arrayOf(R.string.pref_update_only_completely_read, R.string.pref_update_only_started, R.string.pref_update_only_non_completed) + entryValues = arrayOf(MANGA_HAS_UNREAD, MANGA_NON_READ, MANGA_NON_COMPLETED) fun updateSummary() { val restrictions = preferences.libraryUpdateMangaRestriction().get().sorted() .map { when (it) { - MANGA_ONGOING -> context.getString(R.string.pref_update_only_non_completed) - MANGA_FULLY_READ -> context.getString(R.string.pref_update_only_completely_read) - MANGA_STARTED -> context.getString(R.string.pref_update_only_started) + MANGA_NON_READ -> context.getString(R.string.pref_update_only_started) + MANGA_HAS_UNREAD -> context.getString(R.string.pref_update_only_completely_read) + MANGA_NON_COMPLETED -> context.getString(R.string.pref_update_only_non_completed) else -> it } } @@ -215,7 +215,7 @@ class SettingsLibraryController : SettingsController() { restrictions.joinToString() } - summary = context.getString(R.string.only_update_restrictions, restrictionsText) + summary = restrictionsText } preferences.libraryUpdateMangaRestriction().asFlow() @@ -234,23 +234,24 @@ class SettingsLibraryController : SettingsController() { val includedCategories = preferences.libraryUpdateCategories().get() .mapNotNull { id -> categories.find { it.id == id.toInt() } } .sortedBy { it.order } - val excludedCategories = preferences.libraryUpdateCategoriesExclude().get() .mapNotNull { id -> categories.find { it.id == id.toInt() } } .sortedBy { it.order } - val includedItemsText = if (includedCategories.isEmpty()) { - context.getString(R.string.none) - } else { - if (includedCategories.size == categories.size) context.getString(R.string.all) - else includedCategories.joinToString { it.name } - } + val allExcluded = excludedCategories.size == categories.size - val excludedItemsText = if (excludedCategories.isEmpty()) { - context.getString(R.string.none) - } else { - if (excludedCategories.size == categories.size) context.getString(R.string.all) - else excludedCategories.joinToString { it.name } + val includedItemsText = when { + // Some selected, but not all + includedCategories.isNotEmpty() && includedCategories.size != categories.size -> includedCategories.joinToString { it.name } + // All explicitly selected + includedCategories.size == categories.size -> context.getString(R.string.all) + allExcluded -> context.getString(R.string.none) + else -> context.getString(R.string.all) + } + val excludedItemsText = when { + excludedCategories.isEmpty() -> context.getString(R.string.none) + allExcluded -> context.getString(R.string.all) + else -> excludedCategories.joinToString { it.name } } summary = buildSpannedString { @@ -340,8 +341,10 @@ class SettingsLibraryController : SettingsController() { var selected = categories .map { when (it.id.toString()) { - in preferences.libraryUpdateCategories().get() -> QuadStateTextView.State.CHECKED.ordinal - in preferences.libraryUpdateCategoriesExclude().get() -> QuadStateTextView.State.INVERSED.ordinal + in preferences.libraryUpdateCategories() + .get() -> QuadStateTextView.State.CHECKED.ordinal + in preferences.libraryUpdateCategoriesExclude() + .get() -> QuadStateTextView.State.INVERSED.ordinal else -> QuadStateTextView.State.UNCHECKED.ordinal } } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index df5b6f704d..3af6e18ef1 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -121,7 +121,7 @@ Reset Undo Open log - Tap to see error details + Tap to see details Create Restore Back @@ -224,11 +224,11 @@ Charging Restrictions: %s - Library update restrictions + Skip updating Only update: %s - Completely read series - Ongoing series - Started series + Has unread chapters + Is completed series + No read chapters Show unread count on Updates icon Automatically refresh metadata Check for new cover and details when updating library @@ -737,13 +737,13 @@ Chapters %1$s and 1 more Chapters %1$s and %2$d more - - 1 update failed - %1$d updates failed - + %1$d update(s) skipped and %2$d update(s) failed Failed to update cover Please add the manga to your library before doing this For help on how to fix library update errors, see %1$s + Skipped because series is complete + Skipped because there are unread chapters + Skipped because no chapters are read Select cover image