Account for skipped entries when showing large updates warning

Closes #6159
This commit is contained in:
arkon 2023-10-08 16:40:17 -04:00
parent 94cba9324c
commit 7ed99fbbd6
3 changed files with 88 additions and 73 deletions

View file

@ -186,7 +186,40 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
.distinctBy { it.manga.id } .distinctBy { it.manga.id }
} }
val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get()
val skippedUpdates = mutableListOf<Pair<Manga, String?>>()
val fetchWindow = fetchInterval.getWindow(ZonedDateTime.now())
mangaToUpdate = listToUpdate mangaToUpdate = listToUpdate
.filter {
when {
it.manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE -> {
skippedUpdates.add(it.manga to context.getString(R.string.skipped_reason_not_always_update))
false
}
MANGA_NON_COMPLETED in restrictions && it.manga.status.toInt() == SManga.COMPLETED -> {
skippedUpdates.add(it.manga to context.getString(R.string.skipped_reason_completed))
false
}
MANGA_HAS_UNREAD in restrictions && it.unreadCount != 0L -> {
skippedUpdates.add(it.manga to context.getString(R.string.skipped_reason_not_caught_up))
false
}
MANGA_NON_READ in restrictions && it.totalChapters > 0L && !it.hasStarted -> {
skippedUpdates.add(it.manga to context.getString(R.string.skipped_reason_not_started))
false
}
MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && it.manga.nextUpdate > fetchWindow.second -> {
skippedUpdates.add(it.manga to context.getString(R.string.skipped_reason_not_in_release_period))
false
}
else -> true
}
}
.sortedBy { it.manga.title } .sortedBy { it.manga.title }
// Warn when excessively checking a single source // Warn when excessively checking a single source
@ -197,6 +230,17 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
if (maxUpdatesFromSource > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) { if (maxUpdatesFromSource > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
notifier.showQueueSizeWarningNotification() notifier.showQueueSizeWarningNotification()
} }
if (skippedUpdates.isNotEmpty()) {
// TODO: surface skipped reasons to user?
logcat {
skippedUpdates
.groupBy { it.second }
.map { (reason, entries) -> "$reason: [${entries.map { it.first.title }.sorted().joinToString()}]" }
.joinToString()
}
notifier.showUpdateSkippedNotification(skippedUpdates.size)
}
} }
/** /**
@ -212,10 +256,8 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
val progressCount = AtomicInteger(0) val progressCount = AtomicInteger(0)
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>() val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
val newUpdates = CopyOnWriteArrayList<Pair<Manga, Array<Chapter>>>() val newUpdates = CopyOnWriteArrayList<Pair<Manga, Array<Chapter>>>()
val skippedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>() val failedUpdates = CopyOnWriteArrayList<Pair<Manga, String?>>()
val hasDownloads = AtomicBoolean(false) val hasDownloads = AtomicBoolean(false)
val restrictions = libraryPreferences.autoUpdateMangaRestrictions().get()
val fetchWindow = fetchInterval.getWindow(ZonedDateTime.now()) val fetchWindow = fetchInterval.getWindow(ZonedDateTime.now())
coroutineScope { coroutineScope {
@ -237,49 +279,30 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
progressCount, progressCount,
manga, manga,
) { ) {
when { try {
manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE -> val newChapters = updateManga(manga, fetchWindow)
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_always_update)) .sortedByDescending { it.sourceOrder }
MANGA_NON_COMPLETED in restrictions && manga.status.toInt() == SManga.COMPLETED -> if (newChapters.isNotEmpty()) {
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_completed)) val categoryIds = getCategories.await(manga.id).map { it.id }
if (manga.shouldDownloadNewChapters(categoryIds, downloadPreferences)) {
MANGA_HAS_UNREAD in restrictions && libraryManga.unreadCount != 0L -> downloadChapters(manga, newChapters)
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_caught_up)) hasDownloads.set(true)
MANGA_NON_READ in restrictions && libraryManga.totalChapters > 0L && !libraryManga.hasStarted ->
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_started))
MANGA_OUTSIDE_RELEASE_PERIOD in restrictions && manga.nextUpdate > fetchWindow.second ->
skippedUpdates.add(manga to context.getString(R.string.skipped_reason_not_in_release_period))
else -> {
try {
val newChapters = updateManga(manga, fetchWindow)
.sortedByDescending { it.sourceOrder }
if (newChapters.isNotEmpty()) {
val categoryIds = getCategories.await(manga.id).map { it.id }
if (manga.shouldDownloadNewChapters(categoryIds, downloadPreferences)) {
downloadChapters(manga, newChapters)
hasDownloads.set(true)
}
libraryPreferences.newUpdatesCount().getAndSet { it + newChapters.size }
// Convert to the manga that contains new chapters
newUpdates.add(manga to newChapters.toTypedArray())
}
} catch (e: Throwable) {
val errorMessage = when (e) {
is NoChaptersException -> context.getString(R.string.no_chapters_error)
// failedUpdates will already have the source, don't need to copy it into the message
is SourceNotInstalledException -> context.getString(R.string.loader_not_implemented_error)
else -> e.message
}
failedUpdates.add(manga to errorMessage)
} }
libraryPreferences.newUpdatesCount().getAndSet { it + newChapters.size }
// Convert to the manga that contains new chapters
newUpdates.add(manga to newChapters.toTypedArray())
} }
} catch (e: Throwable) {
val errorMessage = when (e) {
is NoChaptersException -> context.getString(R.string.no_chapters_error)
// failedUpdates will already have the source, don't need to copy it into the message
is SourceNotInstalledException -> context.getString(R.string.loader_not_implemented_error)
else -> e.message
}
failedUpdates.add(manga to errorMessage)
} }
if (libraryPreferences.autoUpdateTrackers().get()) { if (libraryPreferences.autoUpdateTrackers().get()) {
@ -309,16 +332,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
errorFile.getUriCompat(context), errorFile.getUriCompat(context),
) )
} }
if (skippedUpdates.isNotEmpty()) {
// TODO: surface skipped reasons to user
logcat {
skippedUpdates
.groupBy { it.second }
.map { (reason, entries) -> "$reason: [${entries.map { it.first.title }.sorted().joinToString()}]" }
.joinToString()
}
notifier.showUpdateSkippedNotification(skippedUpdates.size)
}
} }
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) { private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
@ -428,29 +441,27 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
completed: AtomicInteger, completed: AtomicInteger,
manga: Manga, manga: Manga,
block: suspend () -> Unit, block: suspend () -> Unit,
) { ) = coroutineScope {
coroutineScope { ensureActive()
ensureActive()
updatingManga.add(manga) updatingManga.add(manga)
notifier.showProgressNotification( notifier.showProgressNotification(
updatingManga, updatingManga,
completed.get(), completed.get(),
mangaToUpdate.size, mangaToUpdate.size,
) )
block() block()
ensureActive() ensureActive()
updatingManga.remove(manga) updatingManga.remove(manga)
completed.getAndIncrement() completed.getAndIncrement()
notifier.showProgressNotification( notifier.showProgressNotification(
updatingManga, updatingManga,
completed.get(), completed.get(),
mangaToUpdate.size, mangaToUpdate.size,
) )
}
} }
/** /**

View file

@ -30,10 +30,14 @@ import tachiyomi.core.util.lang.launchUI
import tachiyomi.domain.chapter.model.Chapter import tachiyomi.domain.chapter.model.Chapter
import tachiyomi.domain.manga.model.Manga import tachiyomi.domain.manga.model.Manga
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
import java.text.NumberFormat
class LibraryUpdateNotifier(private val context: Context) { class LibraryUpdateNotifier(private val context: Context) {
private val preferences: SecurityPreferences by injectLazy() private val preferences: SecurityPreferences by injectLazy()
private val percentFormatter = NumberFormat.getPercentInstance().apply {
maximumFractionDigits = 0
}
/** /**
* Pending intent of action that cancels the library update * Pending intent of action that cancels the library update
@ -78,7 +82,7 @@ class LibraryUpdateNotifier(private val context: Context) {
} else { } else {
val updatingText = manga.joinToString("\n") { it.title.chop(40) } val updatingText = manga.joinToString("\n") { it.title.chop(40) }
progressNotificationBuilder progressNotificationBuilder
.setContentTitle(context.getString(R.string.notification_updating, current, total)) .setContentTitle(context.getString(R.string.notification_updating_progress, percentFormatter.format(current.toFloat() / total)))
.setStyle(NotificationCompat.BigTextStyle().bigText(updatingText)) .setStyle(NotificationCompat.BigTextStyle().bigText(updatingText))
} }

View file

@ -837,7 +837,7 @@
<!-- Library update service notifications --> <!-- Library update service notifications -->
<string name="notification_check_updates">Checking for new chapters</string> <string name="notification_check_updates">Checking for new chapters</string>
<string name="notification_updating">Updating library… (%1$d/%2$d)</string> <string name="notification_updating_progress">Updating library… (%s)</string>
<string name="notification_size_warning">Large updates harm sources and may lead to slower updates and also increased battery usage. Tap to learn more.</string> <string name="notification_size_warning">Large updates harm sources and may lead to slower updates and also increased battery usage. Tap to learn more.</string>
<string name="notification_new_chapters">New chapters found</string> <string name="notification_new_chapters">New chapters found</string>
<plurals name="notification_new_chapters_summary"> <plurals name="notification_new_chapters_summary">