Migrate to multiplatform string resources (#10147)
* Migrate to multiplatform string resources * Move plurals translations into separate files * Fix lint check on generated files
This commit is contained in:
@@ -16,16 +16,13 @@ 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.copyFrom
|
||||
import eu.kanade.domain.manga.model.toSManga
|
||||
import eu.kanade.tachiyomi.R
|
||||
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.UnmeteredSource
|
||||
import eu.kanade.tachiyomi.source.model.SManga
|
||||
import eu.kanade.tachiyomi.source.model.UpdateStrategy
|
||||
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
|
||||
@@ -41,6 +38,7 @@ import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.i18n.localize
|
||||
import tachiyomi.core.preference.getAndSet
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.util.system.logcat
|
||||
@@ -62,9 +60,9 @@ import tachiyomi.domain.manga.interactor.FetchInterval
|
||||
import tachiyomi.domain.manga.interactor.GetLibraryManga
|
||||
import tachiyomi.domain.manga.interactor.GetManga
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.domain.manga.model.toMangaUpdate
|
||||
import tachiyomi.domain.source.model.SourceNotInstalledException
|
||||
import tachiyomi.domain.source.service.SourceManager
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.Injekt
|
||||
import uy.kohesive.injekt.api.get
|
||||
import java.io.File
|
||||
@@ -114,22 +112,14 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
logcat(LogPriority.ERROR, e) { "Not allowed to set foreground job" }
|
||||
}
|
||||
|
||||
val target = inputData.getString(KEY_TARGET)?.let { Target.valueOf(it) } ?: Target.CHAPTERS
|
||||
|
||||
// If this is a chapter update, set the last update time to now
|
||||
if (target == Target.CHAPTERS) {
|
||||
libraryPreferences.lastUpdatedTimestamp().set(Date().time)
|
||||
}
|
||||
libraryPreferences.lastUpdatedTimestamp().set(Date().time)
|
||||
|
||||
val categoryId = inputData.getLong(KEY_CATEGORY, -1L)
|
||||
addMangaToQueue(categoryId)
|
||||
|
||||
return withIOContext {
|
||||
try {
|
||||
when (target) {
|
||||
Target.CHAPTERS -> updateChapterList()
|
||||
Target.COVERS -> updateCovers()
|
||||
}
|
||||
updateChapterList()
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) {
|
||||
@@ -191,29 +181,32 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
.filter {
|
||||
when {
|
||||
it.manga.updateStrategy != UpdateStrategy.ALWAYS_UPDATE -> {
|
||||
skippedUpdates.add(it.manga to context.getString(R.string.skipped_reason_not_always_update))
|
||||
skippedUpdates.add(it.manga to context.localize(MR.strings.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))
|
||||
skippedUpdates.add(it.manga to context.localize(MR.strings.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))
|
||||
skippedUpdates.add(it.manga to context.localize(MR.strings.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))
|
||||
skippedUpdates.add(it.manga to context.localize(MR.strings.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))
|
||||
skippedUpdates.add(
|
||||
it.manga to context.localize(MR.strings.skipped_reason_not_in_release_period),
|
||||
)
|
||||
false
|
||||
}
|
||||
|
||||
else -> true
|
||||
}
|
||||
}
|
||||
@@ -294,10 +287,10 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
val errorMessage = when (e) {
|
||||
is NoChaptersException -> context.getString(R.string.no_chapters_error)
|
||||
is NoChaptersException -> context.localize(MR.strings.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,
|
||||
is SourceNotInstalledException -> context.localize(
|
||||
MR.strings.loader_not_implemented_error,
|
||||
)
|
||||
else -> e.message
|
||||
}
|
||||
@@ -359,51 +352,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
return syncChaptersWithSource.await(chapters, dbManga, source, false, fetchWindow)
|
||||
}
|
||||
|
||||
private suspend fun updateCovers() {
|
||||
val semaphore = Semaphore(5)
|
||||
val progressCount = AtomicInteger(0)
|
||||
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
|
||||
|
||||
coroutineScope {
|
||||
mangaToUpdate.groupBy { it.manga.source }
|
||||
.values
|
||||
.map { mangaInSource ->
|
||||
async {
|
||||
semaphore.withPermit {
|
||||
mangaInSource.forEach { libraryManga ->
|
||||
val manga = libraryManga.manga
|
||||
ensureActive()
|
||||
|
||||
withUpdateNotification(
|
||||
currentlyUpdatingManga,
|
||||
progressCount,
|
||||
manga,
|
||||
) {
|
||||
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" }
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// Ignore errors and continue
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
}
|
||||
|
||||
notifier.cancelProgressNotification()
|
||||
}
|
||||
|
||||
private suspend fun withUpdateNotification(
|
||||
updatingManga: CopyOnWriteArrayList<Manga>,
|
||||
completed: AtomicInteger,
|
||||
@@ -440,7 +388,7 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
if (errors.isNotEmpty()) {
|
||||
val file = context.createFileInCacheDir("tachiyomi_update_errors.txt")
|
||||
file.bufferedWriter().use { out ->
|
||||
out.write(context.getString(R.string.library_errors_help, ERROR_LOG_HELP_URL) + "\n\n")
|
||||
out.write(context.localize(MR.strings.library_errors_help, ERROR_LOG_HELP_URL) + "\n\n")
|
||||
// Error file format:
|
||||
// ! Error
|
||||
// # Source
|
||||
@@ -462,14 +410,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
return File("")
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines what should be updated within a service execution.
|
||||
*/
|
||||
enum class Target {
|
||||
CHAPTERS, // Manga chapters
|
||||
COVERS, // Manga covers
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "LibraryUpdate"
|
||||
private const val WORK_NAME_AUTO = "LibraryUpdate-auto"
|
||||
@@ -484,11 +424,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
*/
|
||||
private const val KEY_CATEGORY = "category"
|
||||
|
||||
/**
|
||||
* Key that defines what should be updated.
|
||||
*/
|
||||
private const val KEY_TARGET = "target"
|
||||
|
||||
fun cancelAllWorks(context: Context) {
|
||||
context.workManager.cancelAllWorkByTag(TAG)
|
||||
}
|
||||
@@ -534,7 +469,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
fun startNow(
|
||||
context: Context,
|
||||
category: Category? = null,
|
||||
target: Target = Target.CHAPTERS,
|
||||
): Boolean {
|
||||
val wm = context.workManager
|
||||
if (wm.isRunning(TAG)) {
|
||||
@@ -544,7 +478,6 @@ class LibraryUpdateJob(private val context: Context, workerParams: WorkerParamet
|
||||
|
||||
val inputData = workDataOf(
|
||||
KEY_CATEGORY to category?.id,
|
||||
KEY_TARGET to target.name,
|
||||
)
|
||||
val request = OneTimeWorkRequestBuilder<LibraryUpdateJob>()
|
||||
.addTag(TAG)
|
||||
|
||||
@@ -26,9 +26,12 @@ import eu.kanade.tachiyomi.util.system.getBitmapOrNull
|
||||
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||
import eu.kanade.tachiyomi.util.system.notify
|
||||
import tachiyomi.core.Constants
|
||||
import tachiyomi.core.i18n.localize
|
||||
import tachiyomi.core.i18n.localizePlural
|
||||
import tachiyomi.core.util.lang.launchUI
|
||||
import tachiyomi.domain.chapter.model.Chapter
|
||||
import tachiyomi.domain.manga.model.Manga
|
||||
import tachiyomi.i18n.MR
|
||||
import uy.kohesive.injekt.injectLazy
|
||||
import java.text.NumberFormat
|
||||
|
||||
@@ -58,12 +61,12 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
*/
|
||||
val progressNotificationBuilder by lazy {
|
||||
context.notificationBuilder(Notifications.CHANNEL_LIBRARY_PROGRESS) {
|
||||
setContentTitle(context.getString(R.string.app_name))
|
||||
setContentTitle(context.localize(MR.strings.app_name))
|
||||
setSmallIcon(R.drawable.ic_refresh_24dp)
|
||||
setLargeIcon(notificationBitmap)
|
||||
setOngoing(true)
|
||||
setOnlyAlertOnce(true)
|
||||
addAction(R.drawable.ic_close_24dp, context.getString(R.string.action_cancel), cancelIntent)
|
||||
addAction(R.drawable.ic_close_24dp, context.localize(MR.strings.action_cancel), cancelIntent)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,14 +80,14 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
fun showProgressNotification(manga: List<Manga>, current: Int, total: Int) {
|
||||
if (preferences.hideNotificationContent().get()) {
|
||||
progressNotificationBuilder
|
||||
.setContentTitle(context.getString(R.string.notification_check_updates))
|
||||
.setContentTitle(context.localize(MR.strings.notification_check_updates))
|
||||
.setContentText("($current/$total)")
|
||||
} else {
|
||||
val updatingText = manga.joinToString("\n") { it.title.chop(40) }
|
||||
progressNotificationBuilder
|
||||
.setContentTitle(
|
||||
context.getString(
|
||||
R.string.notification_updating_progress,
|
||||
context.localize(
|
||||
MR.strings.notification_updating_progress,
|
||||
percentFormatter.format(current.toFloat() / total),
|
||||
),
|
||||
)
|
||||
@@ -104,8 +107,8 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
Notifications.ID_LIBRARY_SIZE_WARNING,
|
||||
Notifications.CHANNEL_LIBRARY_PROGRESS,
|
||||
) {
|
||||
setContentTitle(context.getString(R.string.label_warning))
|
||||
setStyle(NotificationCompat.BigTextStyle().bigText(context.getString(R.string.notification_size_warning)))
|
||||
setContentTitle(context.localize(MR.strings.label_warning))
|
||||
setStyle(NotificationCompat.BigTextStyle().bigText(context.localize(MR.strings.notification_size_warning)))
|
||||
setSmallIcon(R.drawable.ic_warning_white_24dp)
|
||||
setTimeoutAfter(Downloader.WARNING_NOTIF_TIMEOUT_MS)
|
||||
setContentIntent(NotificationHandler.openUrl(context, HELP_WARNING_URL))
|
||||
@@ -127,8 +130,8 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
Notifications.ID_LIBRARY_ERROR,
|
||||
Notifications.CHANNEL_LIBRARY_ERROR,
|
||||
) {
|
||||
setContentTitle(context.resources.getString(R.string.notification_update_error, failed))
|
||||
setContentText(context.getString(R.string.action_show_errors))
|
||||
setContentTitle(context.localize(MR.strings.notification_update_error, failed))
|
||||
setContentText(context.localize(MR.strings.action_show_errors))
|
||||
setSmallIcon(R.drawable.ic_tachi)
|
||||
|
||||
setContentIntent(NotificationReceiver.openErrorLogPendingActivity(context, uri))
|
||||
@@ -149,8 +152,8 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
Notifications.ID_LIBRARY_SKIPPED,
|
||||
Notifications.CHANNEL_LIBRARY_SKIPPED,
|
||||
) {
|
||||
setContentTitle(context.resources.getString(R.string.notification_update_skipped, skipped))
|
||||
setContentText(context.getString(R.string.learn_more))
|
||||
setContentTitle(context.localize(MR.strings.notification_update_skipped, skipped))
|
||||
setContentText(context.localize(MR.strings.learn_more))
|
||||
setSmallIcon(R.drawable.ic_tachi)
|
||||
setContentIntent(NotificationHandler.openUrl(context, HELP_SKIPPED_URL))
|
||||
}
|
||||
@@ -167,13 +170,13 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
Notifications.ID_NEW_CHAPTERS,
|
||||
Notifications.CHANNEL_NEW_CHAPTERS,
|
||||
) {
|
||||
setContentTitle(context.getString(R.string.notification_new_chapters))
|
||||
setContentTitle(context.localize(MR.strings.notification_new_chapters))
|
||||
if (updates.size == 1 && !preferences.hideNotificationContent().get()) {
|
||||
setContentText(updates.first().first.title.chop(NOTIF_TITLE_MAX_LEN))
|
||||
} else {
|
||||
setContentText(
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.notification_new_chapters_summary,
|
||||
context.localizePlural(
|
||||
MR.plurals.notification_new_chapters_summary,
|
||||
updates.size,
|
||||
updates.size,
|
||||
),
|
||||
@@ -243,7 +246,7 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
// Mark chapters as read action
|
||||
addAction(
|
||||
R.drawable.ic_glasses_24dp,
|
||||
context.getString(R.string.action_mark_as_read),
|
||||
context.localize(MR.strings.action_mark_as_read),
|
||||
NotificationReceiver.markAsReadPendingBroadcast(
|
||||
context,
|
||||
manga,
|
||||
@@ -254,7 +257,7 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
// View chapters action
|
||||
addAction(
|
||||
R.drawable.ic_book_24dp,
|
||||
context.getString(R.string.action_view_chapters),
|
||||
context.localize(MR.strings.action_view_chapters),
|
||||
NotificationReceiver.openChapterPendingActivity(
|
||||
context,
|
||||
manga,
|
||||
@@ -266,7 +269,7 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
if (chapters.size <= Downloader.CHAPTERS_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
|
||||
addAction(
|
||||
android.R.drawable.stat_sys_download_done,
|
||||
context.getString(R.string.action_download),
|
||||
context.localize(MR.strings.action_download),
|
||||
NotificationReceiver.downloadChaptersPendingBroadcast(
|
||||
context,
|
||||
manga,
|
||||
@@ -306,8 +309,8 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
// No sensible chapter numbers to show (i.e. no chapters have parsed chapter number)
|
||||
0 -> {
|
||||
// "1 new chapter" or "5 new chapters"
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.notification_chapters_generic,
|
||||
context.localizePlural(
|
||||
MR.plurals.notification_chapters_generic,
|
||||
chapters.size,
|
||||
chapters.size,
|
||||
)
|
||||
@@ -317,14 +320,14 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
val remaining = chapters.size - displayableChapterNumbers.size
|
||||
if (remaining == 0) {
|
||||
// "Chapter 2.5"
|
||||
context.resources.getString(
|
||||
R.string.notification_chapters_single,
|
||||
context.localize(
|
||||
MR.strings.notification_chapters_single,
|
||||
displayableChapterNumbers.first(),
|
||||
)
|
||||
} else {
|
||||
// "Chapter 2.5 and 10 more"
|
||||
context.resources.getString(
|
||||
R.string.notification_chapters_single_and_more,
|
||||
context.localize(
|
||||
MR.strings.notification_chapters_single_and_more,
|
||||
displayableChapterNumbers.first(),
|
||||
remaining,
|
||||
)
|
||||
@@ -339,16 +342,16 @@ class LibraryUpdateNotifier(private val context: Context) {
|
||||
val joinedChapterNumbers = displayableChapterNumbers
|
||||
.take(NOTIF_MAX_CHAPTERS)
|
||||
.joinToString(", ")
|
||||
context.resources.getQuantityString(
|
||||
R.plurals.notification_chapters_multiple_and_more,
|
||||
context.localizePlural(
|
||||
MR.plurals.notification_chapters_multiple_and_more,
|
||||
remaining,
|
||||
joinedChapterNumbers,
|
||||
remaining,
|
||||
)
|
||||
} else {
|
||||
// "Chapters 1, 2.5, 3"
|
||||
context.resources.getString(
|
||||
R.string.notification_chapters_multiple,
|
||||
context.localize(
|
||||
MR.strings.notification_chapters_multiple,
|
||||
displayableChapterNumbers.joinToString(", "),
|
||||
)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,210 @@
|
||||
package eu.kanade.tachiyomi.data.library
|
||||
|
||||
import android.content.Context
|
||||
import androidx.work.CoroutineWorker
|
||||
import androidx.work.ExistingWorkPolicy
|
||||
import androidx.work.ForegroundInfo
|
||||
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.source.UnmeteredSource
|
||||
import eu.kanade.tachiyomi.util.prepUpdateCover
|
||||
import eu.kanade.tachiyomi.util.system.isRunning
|
||||
import eu.kanade.tachiyomi.util.system.workManager
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.ensureActive
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import kotlinx.coroutines.sync.Semaphore
|
||||
import kotlinx.coroutines.sync.withPermit
|
||||
import logcat.LogPriority
|
||||
import tachiyomi.core.util.lang.withIOContext
|
||||
import tachiyomi.core.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
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import java.util.concurrent.atomic.AtomicInteger
|
||||
|
||||
class MetadataUpdateJob(private val context: Context, workerParams: WorkerParameters) :
|
||||
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 notifier = LibraryUpdateNotifier(context)
|
||||
|
||||
private var mangaToUpdate: List<LibraryManga> = mutableListOf()
|
||||
|
||||
override suspend fun doWork(): Result {
|
||||
try {
|
||||
setForeground(getForegroundInfo())
|
||||
} catch (e: IllegalStateException) {
|
||||
logcat(LogPriority.ERROR, e) { "Not allowed to set foreground job" }
|
||||
}
|
||||
|
||||
addMangaToQueue()
|
||||
|
||||
return withIOContext {
|
||||
try {
|
||||
updateMetadata()
|
||||
Result.success()
|
||||
} catch (e: Exception) {
|
||||
if (e is CancellationException) {
|
||||
// Assume success although cancelled
|
||||
Result.success()
|
||||
} else {
|
||||
logcat(LogPriority.ERROR, e)
|
||||
Result.failure()
|
||||
}
|
||||
} finally {
|
||||
notifier.cancelProgressNotification()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun getForegroundInfo(): ForegroundInfo {
|
||||
val notifier = LibraryUpdateNotifier(context)
|
||||
return ForegroundInfo(
|
||||
Notifications.ID_LIBRARY_PROGRESS,
|
||||
notifier.progressNotificationBuilder.build(),
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Adds list of manga to be updated.
|
||||
*/
|
||||
private fun addMangaToQueue() {
|
||||
mangaToUpdate = runBlocking { getLibraryManga.await() }
|
||||
|
||||
// Warn when excessively checking a single source
|
||||
val maxUpdatesFromSource = mangaToUpdate
|
||||
.groupBy { it.manga.source }
|
||||
.filterKeys { sourceManager.get(it) !is UnmeteredSource }
|
||||
.maxOfOrNull { it.value.size } ?: 0
|
||||
if (maxUpdatesFromSource > MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD) {
|
||||
notifier.showQueueSizeWarningNotification()
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun updateMetadata() {
|
||||
val semaphore = Semaphore(5)
|
||||
val progressCount = AtomicInteger(0)
|
||||
val currentlyUpdatingManga = CopyOnWriteArrayList<Manga>()
|
||||
|
||||
coroutineScope {
|
||||
mangaToUpdate.groupBy { it.manga.source }
|
||||
.values
|
||||
.map { mangaInSource ->
|
||||
async {
|
||||
semaphore.withPermit {
|
||||
mangaInSource.forEach { libraryManga ->
|
||||
val manga = libraryManga.manga
|
||||
ensureActive()
|
||||
|
||||
withUpdateNotification(
|
||||
currentlyUpdatingManga,
|
||||
progressCount,
|
||||
manga,
|
||||
) {
|
||||
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" }
|
||||
}
|
||||
} catch (e: Throwable) {
|
||||
// Ignore errors and continue
|
||||
logcat(LogPriority.ERROR, e)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
.awaitAll()
|
||||
}
|
||||
|
||||
notifier.cancelProgressNotification()
|
||||
}
|
||||
|
||||
private suspend fun withUpdateNotification(
|
||||
updatingManga: CopyOnWriteArrayList<Manga>,
|
||||
completed: AtomicInteger,
|
||||
manga: Manga,
|
||||
block: suspend () -> Unit,
|
||||
) = coroutineScope {
|
||||
ensureActive()
|
||||
|
||||
updatingManga.add(manga)
|
||||
notifier.showProgressNotification(
|
||||
updatingManga,
|
||||
completed.get(),
|
||||
mangaToUpdate.size,
|
||||
)
|
||||
|
||||
block()
|
||||
|
||||
ensureActive()
|
||||
|
||||
updatingManga.remove(manga)
|
||||
completed.getAndIncrement()
|
||||
notifier.showProgressNotification(
|
||||
updatingManga,
|
||||
completed.get(),
|
||||
mangaToUpdate.size,
|
||||
)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "MetadataUpdate"
|
||||
private const val WORK_NAME_MANUAL = "MetadataUpdate"
|
||||
|
||||
private const val MANGA_PER_SOURCE_QUEUE_WARNING_THRESHOLD = 60
|
||||
|
||||
fun startNow(context: Context): Boolean {
|
||||
val wm = context.workManager
|
||||
if (wm.isRunning(TAG)) {
|
||||
// Already running either as a scheduled or manual job
|
||||
return false
|
||||
}
|
||||
val request = OneTimeWorkRequestBuilder<MetadataUpdateJob>()
|
||||
.addTag(TAG)
|
||||
.addTag(WORK_NAME_MANUAL)
|
||||
.build()
|
||||
wm.enqueueUniqueWork(WORK_NAME_MANUAL, ExistingWorkPolicy.KEEP, request)
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
fun stop(context: Context) {
|
||||
val wm = context.workManager
|
||||
val workQuery = WorkQuery.Builder.fromTags(listOf(TAG))
|
||||
.addStates(listOf(WorkInfo.State.RUNNING))
|
||||
.build()
|
||||
wm.getWorkInfos(workQuery).get()
|
||||
// Should only return one work but just in case
|
||||
.forEach {
|
||||
wm.cancelWorkById(it.id)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user