mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-24 01:51:48 +01:00
Show notification with error log on update failures + Move notification logic out of LibraryUpdateService
This commit is contained in:
parent
e76805160c
commit
12b2da9058
@ -0,0 +1,242 @@
|
|||||||
|
package eu.kanade.tachiyomi.data.library
|
||||||
|
|
||||||
|
import android.app.Notification
|
||||||
|
import android.app.PendingIntent
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.graphics.BitmapFactory
|
||||||
|
import android.graphics.drawable.BitmapDrawable
|
||||||
|
import android.net.Uri
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
|
import androidx.core.content.ContextCompat
|
||||||
|
import coil.Coil
|
||||||
|
import coil.request.CachePolicy
|
||||||
|
import coil.request.GetRequest
|
||||||
|
import coil.transform.CircleCropTransformation
|
||||||
|
import eu.kanade.tachiyomi.R
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.LibraryManga
|
||||||
|
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||||
|
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
||||||
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
|
import eu.kanade.tachiyomi.ui.main.MainActivity
|
||||||
|
import eu.kanade.tachiyomi.util.lang.chop
|
||||||
|
import eu.kanade.tachiyomi.util.system.notification
|
||||||
|
import eu.kanade.tachiyomi.util.system.notificationBuilder
|
||||||
|
import eu.kanade.tachiyomi.util.system.notificationManager
|
||||||
|
import uy.kohesive.injekt.injectLazy
|
||||||
|
import java.util.ArrayList
|
||||||
|
|
||||||
|
class LibraryUpdateNotifier(private val context: Context) {
|
||||||
|
|
||||||
|
private val preferences: PreferencesHelper by injectLazy()
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Pending intent of action that cancels the library update
|
||||||
|
*/
|
||||||
|
private val cancelIntent by lazy {
|
||||||
|
NotificationReceiver.cancelLibraryUpdatePendingBroadcast(context)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Bitmap of the app for notifications.
|
||||||
|
*/
|
||||||
|
private val notificationBitmap by lazy {
|
||||||
|
BitmapFactory.decodeResource(context.resources, R.mipmap.ic_launcher)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cached progress notification to avoid creating a lot.
|
||||||
|
*/
|
||||||
|
val progressNotificationBuilder by lazy {
|
||||||
|
context.notificationBuilder(Notifications.CHANNEL_LIBRARY) {
|
||||||
|
setContentTitle(context.getString(R.string.app_name))
|
||||||
|
setSmallIcon(R.drawable.ic_refresh_24dp)
|
||||||
|
setLargeIcon(notificationBitmap)
|
||||||
|
setOngoing(true)
|
||||||
|
setOnlyAlertOnce(true)
|
||||||
|
color = ContextCompat.getColor(context, R.color.colorAccent)
|
||||||
|
addAction(R.drawable.ic_close_24dp, context.getString(android.R.string.cancel), cancelIntent)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the notification containing the currently updating manga and the progress.
|
||||||
|
*
|
||||||
|
* @param manga the manga that's being updated.
|
||||||
|
* @param current the current progress.
|
||||||
|
* @param total the total progress.
|
||||||
|
*/
|
||||||
|
fun showProgressNotification(manga: Manga, current: Int, total: Int) {
|
||||||
|
val title = manga.title
|
||||||
|
|
||||||
|
context.notificationManager.notify(
|
||||||
|
Notifications.ID_LIBRARY_PROGRESS,
|
||||||
|
progressNotificationBuilder
|
||||||
|
.setContentTitle(title)
|
||||||
|
.setProgress(total, current, false)
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows notification containing update entries that failed with action to open full log.
|
||||||
|
*
|
||||||
|
* @param errors List of entry titles that failed to update.
|
||||||
|
* @param uri Uri for error log file containing all titles that failed.
|
||||||
|
*/
|
||||||
|
fun showUpdateErrorNotification(errors: List<String>, uri: Uri) {
|
||||||
|
if (errors.isEmpty()) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
context.notificationManager.notify(
|
||||||
|
Notifications.ID_LIBRARY_ERROR,
|
||||||
|
context.notificationBuilder(Notifications.CHANNEL_LIBRARY) {
|
||||||
|
setContentTitle(context.resources.getQuantityString(R.plurals.notification_update_failed, errors.size, errors.size))
|
||||||
|
setStyle(
|
||||||
|
NotificationCompat.BigTextStyle().bigText(
|
||||||
|
errors.joinToString("\n") {
|
||||||
|
it.chop(TITLE_MAX_LEN)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
addAction(
|
||||||
|
R.drawable.nnf_ic_file_folder,
|
||||||
|
context.getString(R.string.view_all_errors),
|
||||||
|
NotificationReceiver.openErrorLogPendingActivity(context, uri)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.build()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Shows the notification containing the result of the update done by the service.
|
||||||
|
*
|
||||||
|
* @param updates a list of manga with new updates.
|
||||||
|
*/
|
||||||
|
suspend fun showResultNotification(updates: Map<LibraryManga, Array<Chapter>>) {
|
||||||
|
val notifications = ArrayList<Pair<Notification, Int>>()
|
||||||
|
updates.forEach {
|
||||||
|
val manga = it.key
|
||||||
|
val chapters = it.value
|
||||||
|
val chapterNames = chapters.map { chapter -> chapter.name }
|
||||||
|
notifications.add(Pair(context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||||
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
try {
|
||||||
|
val request = GetRequest.Builder(context).data(manga)
|
||||||
|
.networkCachePolicy(CachePolicy.DISABLED)
|
||||||
|
.transformations(CircleCropTransformation()).size(width = ICON_SIZE, height = ICON_SIZE)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
Coil.imageLoader(context)
|
||||||
|
.execute(request).drawable?.let { drawable ->
|
||||||
|
setLargeIcon((drawable as BitmapDrawable).bitmap)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) { }
|
||||||
|
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||||
|
setContentTitle(manga.title)
|
||||||
|
color = ContextCompat.getColor(context, R.color.colorAccent)
|
||||||
|
val chaptersNames = if (chapterNames.size > MAX_CHAPTERS) {
|
||||||
|
"${chapterNames.take(MAX_CHAPTERS - 1)
|
||||||
|
.joinToString(", ")}, " + context.resources.getQuantityString(
|
||||||
|
R.plurals.notification_and_n_more,
|
||||||
|
(chapterNames.size - (MAX_CHAPTERS - 1)),
|
||||||
|
(chapterNames.size - (MAX_CHAPTERS - 1))
|
||||||
|
)
|
||||||
|
} else chapterNames.joinToString(", ")
|
||||||
|
setContentText(chaptersNames)
|
||||||
|
setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames))
|
||||||
|
priority = NotificationCompat.PRIORITY_HIGH
|
||||||
|
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
||||||
|
setContentIntent(
|
||||||
|
NotificationReceiver.openChapterPendingActivity(
|
||||||
|
context, manga, chapters.first()
|
||||||
|
)
|
||||||
|
)
|
||||||
|
addAction(
|
||||||
|
R.drawable.ic_glasses_black_24dp, context.getString(R.string.mark_as_read),
|
||||||
|
NotificationReceiver.markAsReadPendingBroadcast(
|
||||||
|
context,
|
||||||
|
manga, chapters, Notifications.ID_NEW_CHAPTERS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
addAction(
|
||||||
|
R.drawable.ic_book_white_24dp, context.getString(R.string.view_chapters),
|
||||||
|
NotificationReceiver.openChapterPendingActivity(
|
||||||
|
context,
|
||||||
|
manga, Notifications.ID_NEW_CHAPTERS
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setAutoCancel(true)
|
||||||
|
}, manga.id.hashCode()))
|
||||||
|
}
|
||||||
|
|
||||||
|
NotificationManagerCompat.from(context).apply {
|
||||||
|
|
||||||
|
notify(
|
||||||
|
Notifications.ID_NEW_CHAPTERS,
|
||||||
|
context.notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
||||||
|
setSmallIcon(R.drawable.ic_tachi)
|
||||||
|
setLargeIcon(notificationBitmap)
|
||||||
|
setContentTitle(context.getString(R.string.new_chapters_found))
|
||||||
|
color = ContextCompat.getColor(context, R.color.colorAccent)
|
||||||
|
if (updates.size > 1) {
|
||||||
|
setContentText(
|
||||||
|
context.resources.getQuantityString(
|
||||||
|
R.plurals
|
||||||
|
.for_n_titles,
|
||||||
|
updates.size, updates.size
|
||||||
|
)
|
||||||
|
)
|
||||||
|
setStyle(
|
||||||
|
NotificationCompat.BigTextStyle()
|
||||||
|
.bigText(updates.keys.joinToString("\n") {
|
||||||
|
it.title.chop(45)
|
||||||
|
})
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
setContentText(updates.keys.first().title.chop(45))
|
||||||
|
}
|
||||||
|
priority = NotificationCompat.PRIORITY_HIGH
|
||||||
|
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
||||||
|
setGroupAlertBehavior(NotificationCompat.GROUP_ALERT_SUMMARY)
|
||||||
|
setGroupSummary(true)
|
||||||
|
setContentIntent(getNotificationIntent())
|
||||||
|
setAutoCancel(true)
|
||||||
|
})
|
||||||
|
|
||||||
|
notifications.forEach {
|
||||||
|
notify(it.second, it.first)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cancels the progress notification.
|
||||||
|
*/
|
||||||
|
fun cancelProgressNotification() {
|
||||||
|
context.notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns an intent to open the main activity.
|
||||||
|
*/
|
||||||
|
private fun getNotificationIntent(): PendingIntent {
|
||||||
|
val intent = Intent(context, MainActivity::class.java).apply {
|
||||||
|
flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
||||||
|
action = MainActivity.SHORTCUT_RECENTLY_UPDATED
|
||||||
|
}
|
||||||
|
return PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val MAX_CHAPTERS = 5
|
||||||
|
private const val TITLE_MAX_LEN = 45
|
||||||
|
private const val ICON_SIZE = 192
|
||||||
|
}
|
||||||
|
}
|
@ -1,25 +1,14 @@
|
|||||||
package eu.kanade.tachiyomi.data.library
|
package eu.kanade.tachiyomi.data.library
|
||||||
|
|
||||||
import android.app.Notification
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.app.Service
|
import android.app.Service
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.graphics.BitmapFactory
|
|
||||||
import android.graphics.drawable.BitmapDrawable
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.IBinder
|
import android.os.IBinder
|
||||||
import android.os.PowerManager
|
import android.os.PowerManager
|
||||||
import androidx.core.app.NotificationCompat
|
|
||||||
import androidx.core.app.NotificationCompat.GROUP_ALERT_SUMMARY
|
|
||||||
import androidx.core.app.NotificationManagerCompat
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import coil.Coil
|
import coil.Coil
|
||||||
import coil.request.CachePolicy
|
import coil.request.CachePolicy
|
||||||
import coil.request.GetRequest
|
|
||||||
import coil.request.LoadRequest
|
import coil.request.LoadRequest
|
||||||
import coil.transform.CircleCropTransformation
|
|
||||||
import eu.kanade.tachiyomi.R
|
|
||||||
import eu.kanade.tachiyomi.data.cache.CoverCache
|
import eu.kanade.tachiyomi.data.cache.CoverCache
|
||||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||||
import eu.kanade.tachiyomi.data.database.models.Category
|
import eu.kanade.tachiyomi.data.database.models.Category
|
||||||
@ -30,7 +19,6 @@ import eu.kanade.tachiyomi.data.download.DownloadManager
|
|||||||
import eu.kanade.tachiyomi.data.download.DownloadService
|
import eu.kanade.tachiyomi.data.download.DownloadService
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateRanker.rankingScheme
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateService.Companion.start
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationReceiver
|
|
||||||
import eu.kanade.tachiyomi.data.notification.Notifications
|
import eu.kanade.tachiyomi.data.notification.Notifications
|
||||||
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
import eu.kanade.tachiyomi.data.preference.PreferencesHelper
|
||||||
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||||
@ -39,12 +27,9 @@ import eu.kanade.tachiyomi.source.SourceManager
|
|||||||
import eu.kanade.tachiyomi.source.fetchMangaDetailsAsync
|
import eu.kanade.tachiyomi.source.fetchMangaDetailsAsync
|
||||||
import eu.kanade.tachiyomi.source.model.SManga
|
import eu.kanade.tachiyomi.source.model.SManga
|
||||||
import eu.kanade.tachiyomi.source.online.HttpSource
|
import eu.kanade.tachiyomi.source.online.HttpSource
|
||||||
import eu.kanade.tachiyomi.ui.main.MainActivity
|
|
||||||
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
import eu.kanade.tachiyomi.util.chapter.syncChaptersWithSource
|
||||||
import eu.kanade.tachiyomi.util.lang.chop
|
import eu.kanade.tachiyomi.util.storage.getUriCompat
|
||||||
import eu.kanade.tachiyomi.util.system.executeOnIO
|
import eu.kanade.tachiyomi.util.system.executeOnIO
|
||||||
import eu.kanade.tachiyomi.util.system.notification
|
|
||||||
import eu.kanade.tachiyomi.util.system.notificationManager
|
|
||||||
import kotlinx.coroutines.CancellationException
|
import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.CoroutineExceptionHandler
|
import kotlinx.coroutines.CoroutineExceptionHandler
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
@ -60,7 +45,7 @@ import kotlinx.coroutines.withContext
|
|||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import uy.kohesive.injekt.Injekt
|
import uy.kohesive.injekt.Injekt
|
||||||
import uy.kohesive.injekt.api.get
|
import uy.kohesive.injekt.api.get
|
||||||
import java.util.ArrayList
|
import java.io.File
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
import java.util.concurrent.atomic.AtomicInteger
|
import java.util.concurrent.atomic.AtomicInteger
|
||||||
|
|
||||||
@ -86,19 +71,7 @@ class LibraryUpdateService(
|
|||||||
*/
|
*/
|
||||||
private lateinit var wakeLock: PowerManager.WakeLock
|
private lateinit var wakeLock: PowerManager.WakeLock
|
||||||
|
|
||||||
/**
|
private lateinit var notifier: LibraryUpdateNotifier
|
||||||
* Pending intent of action that cancels the library update
|
|
||||||
*/
|
|
||||||
private val cancelIntent by lazy {
|
|
||||||
NotificationReceiver.cancelLibraryUpdatePendingBroadcast(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Bitmap of the app for notifications.
|
|
||||||
*/
|
|
||||||
private val notificationBitmap by lazy {
|
|
||||||
BitmapFactory.decodeResource(resources, R.mipmap.ic_launcher)
|
|
||||||
}
|
|
||||||
|
|
||||||
private var job: Job? = null
|
private var job: Job? = null
|
||||||
|
|
||||||
@ -111,6 +84,9 @@ class LibraryUpdateService(
|
|||||||
// List containing new updates
|
// List containing new updates
|
||||||
private val newUpdates = mutableMapOf<LibraryManga, Array<Chapter>>()
|
private val newUpdates = mutableMapOf<LibraryManga, Array<Chapter>>()
|
||||||
|
|
||||||
|
// List containing failed updates
|
||||||
|
private val failedUpdates = mutableMapOf<Manga, String?>()
|
||||||
|
|
||||||
val count = AtomicInteger(0)
|
val count = AtomicInteger(0)
|
||||||
val jobCount = AtomicInteger(0)
|
val jobCount = AtomicInteger(0)
|
||||||
|
|
||||||
@ -131,19 +107,6 @@ class LibraryUpdateService(
|
|||||||
preferences.deleteRemovedChapters().get() != 1
|
preferences.deleteRemovedChapters().get() != 1
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Cached progress notification to avoid creating a lot.
|
|
||||||
*/
|
|
||||||
private val progressNotification by lazy {
|
|
||||||
NotificationCompat.Builder(this, Notifications.CHANNEL_LIBRARY)
|
|
||||||
.setContentTitle(getString(R.string.app_name))
|
|
||||||
.setSmallIcon(R.drawable.ic_refresh_white_24dp_img).setLargeIcon(notificationBitmap)
|
|
||||||
.setOngoing(true).setOnlyAlertOnce(true)
|
|
||||||
.setColor(ContextCompat.getColor(this, R.color.colorAccent)).addAction(
|
|
||||||
R.drawable.ic_clear_grey_24dp_img, getString(android.R.string.cancel), cancelIntent
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines what should be updated within a service execution.
|
* Defines what should be updated within a service execution.
|
||||||
*/
|
*/
|
||||||
@ -315,11 +278,12 @@ class LibraryUpdateService(
|
|||||||
*/
|
*/
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
super.onCreate()
|
super.onCreate()
|
||||||
startForeground(Notifications.ID_LIBRARY_PROGRESS, progressNotification.build())
|
notifier = LibraryUpdateNotifier(this)
|
||||||
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).newWakeLock(
|
||||||
PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock"
|
PowerManager.PARTIAL_WAKE_LOCK, "LibraryUpdateService:WakeLock"
|
||||||
)
|
)
|
||||||
wakeLock.acquire(TimeUnit.MINUTES.toMillis(30))
|
wakeLock.acquire(TimeUnit.MINUTES.toMillis(30))
|
||||||
|
startForeground(Notifications.ID_LIBRARY_PROGRESS, notifier.progressNotificationBuilder.build())
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -407,11 +371,11 @@ class LibraryUpdateService(
|
|||||||
private suspend fun finishUpdates() {
|
private suspend fun finishUpdates() {
|
||||||
if (jobCount.get() != 0) return
|
if (jobCount.get() != 0) return
|
||||||
if (newUpdates.isNotEmpty()) {
|
if (newUpdates.isNotEmpty()) {
|
||||||
showResultNotification(newUpdates)
|
notifier.showResultNotification(newUpdates)
|
||||||
|
|
||||||
if (preferences.refreshCoversToo().getOrDefault() && job?.isCancelled == false) {
|
if (preferences.refreshCoversToo().getOrDefault() && job?.isCancelled == false) {
|
||||||
updateDetails(newUpdates.keys.toList())
|
updateDetails(newUpdates.keys.toList())
|
||||||
cancelProgressNotification()
|
notifier.cancelProgressNotification()
|
||||||
if (downloadNew && hasDownloads) {
|
if (downloadNew && hasDownloads) {
|
||||||
DownloadService.start(this)
|
DownloadService.start(this)
|
||||||
}
|
}
|
||||||
@ -420,7 +384,15 @@ class LibraryUpdateService(
|
|||||||
}
|
}
|
||||||
newUpdates.clear()
|
newUpdates.clear()
|
||||||
}
|
}
|
||||||
cancelProgressNotification()
|
if (preferences.showLibraryUpdateErrors() && failedUpdates.isNotEmpty()) {
|
||||||
|
val errorFile = writeErrorFile(failedUpdates)
|
||||||
|
notifier.showUpdateErrorNotification(
|
||||||
|
failedUpdates.map { it.key.title },
|
||||||
|
errorFile.getUriCompat(this)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
failedUpdates.clear()
|
||||||
|
notifier.cancelProgressNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun updateMangaInSource(
|
private suspend fun updateMangaInSource(
|
||||||
@ -459,7 +431,7 @@ class LibraryUpdateService(
|
|||||||
if (job?.isCancelled == true) {
|
if (job?.isCancelled == true) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
showProgressNotification(manga, progress, mangaToUpdate.size)
|
notifier.showProgressNotification(manga, progress, mangaToUpdate.size)
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource ?: return false
|
val source = sourceManager.get(manga.source) as? HttpSource ?: return false
|
||||||
val fetchedChapters = withContext(Dispatchers.IO) {
|
val fetchedChapters = withContext(Dispatchers.IO) {
|
||||||
source.fetchChapterList(manga).toBlocking().single()
|
source.fetchChapterList(manga).toBlocking().single()
|
||||||
@ -489,6 +461,7 @@ class LibraryUpdateService(
|
|||||||
return hasDownloads
|
return hasDownloads
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
if (e !is CancellationException) {
|
if (e !is CancellationException) {
|
||||||
|
failedUpdates[manga] = e.message
|
||||||
Timber.e("Failed updating: ${manga.title}: $e")
|
Timber.e("Failed updating: ${manga.title}: $e")
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
@ -496,14 +469,9 @@ class LibraryUpdateService(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
|
private fun downloadChapters(manga: Manga, chapters: List<Chapter>) {
|
||||||
// we need to get the chapters from the db so we have chapter ids
|
|
||||||
val mangaChapters = db.getChapters(manga).executeAsBlocking()
|
|
||||||
val dbChapters = chapters.map {
|
|
||||||
mangaChapters.find { mangaChapter -> mangaChapter.url == it.url }!!
|
|
||||||
}
|
|
||||||
// We don't want to start downloading while the library is updating, because websites
|
// We don't want to start downloading while the library is updating, because websites
|
||||||
// may don't like it and they could ban the user.
|
// may don't like it and they could ban the user.
|
||||||
downloadManager.downloadChapters(manga, dbChapters, false)
|
downloadManager.downloadChapters(manga, chapters, false)
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -523,7 +491,11 @@ class LibraryUpdateService(
|
|||||||
return@async
|
return@async
|
||||||
}
|
}
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource ?: return@async
|
val source = sourceManager.get(manga.source) as? HttpSource ?: return@async
|
||||||
showProgressNotification(manga, count.andIncrement, mangaToUpdate.size)
|
notifier.showProgressNotification(
|
||||||
|
manga,
|
||||||
|
count.andIncrement,
|
||||||
|
mangaToUpdate.size
|
||||||
|
)
|
||||||
|
|
||||||
val networkManga = try {
|
val networkManga = try {
|
||||||
source.fetchMangaDetailsAsync(manga)
|
source.fetchMangaDetailsAsync(manga)
|
||||||
@ -550,7 +522,7 @@ class LibraryUpdateService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
asyncList.awaitAll()
|
asyncList.awaitAll()
|
||||||
cancelProgressNotification()
|
notifier.cancelProgressNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -565,7 +537,7 @@ class LibraryUpdateService(
|
|||||||
val loggedServices = trackManager.services.filter { it.isLogged }
|
val loggedServices = trackManager.services.filter { it.isLogged }
|
||||||
|
|
||||||
mangaToUpdate.forEach { manga ->
|
mangaToUpdate.forEach { manga ->
|
||||||
showProgressNotification(manga, count++, mangaToUpdate.size)
|
notifier.showProgressNotification(manga, count++, mangaToUpdate.size)
|
||||||
|
|
||||||
val tracks = db.getTracks(manga).executeAsBlocking()
|
val tracks = db.getTracks(manga).executeAsBlocking()
|
||||||
|
|
||||||
@ -581,143 +553,28 @@ class LibraryUpdateService(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
cancelProgressNotification()
|
notifier.cancelProgressNotification()
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Shows the notification containing the currently updating manga and the progress.
|
* Writes basic file of update errors to cache dir.
|
||||||
*
|
|
||||||
* @param manga the manga that's being updated.
|
|
||||||
* @param current the current progress.
|
|
||||||
* @param total the total progress.
|
|
||||||
*/
|
*/
|
||||||
private fun showProgressNotification(manga: Manga, current: Int, total: Int) {
|
private fun writeErrorFile(errors: Map<Manga, String?>): File {
|
||||||
notificationManager.notify(
|
try {
|
||||||
Notifications.ID_LIBRARY_PROGRESS, progressNotification
|
if (errors.isNotEmpty()) {
|
||||||
.setContentTitle(manga.title)
|
val destFile = File(externalCacheDir, "tachiyomi_update_errors.txt")
|
||||||
.setProgress(total, current, false)
|
|
||||||
.build()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
destFile.bufferedWriter().use { out ->
|
||||||
* Shows the notification containing the result of the update done by the service.
|
errors.forEach { (manga, error) ->
|
||||||
*
|
out.write("${manga.title}: $error\n")
|
||||||
* @param updates a list of manga with new updates.
|
|
||||||
*/
|
|
||||||
private suspend fun showResultNotification(updates: Map<LibraryManga, Array<Chapter>>) {
|
|
||||||
val notifications = ArrayList<Pair<Notification, Int>>()
|
|
||||||
updates.forEach {
|
|
||||||
val manga = it.key
|
|
||||||
val chapters = it.value
|
|
||||||
val chapterNames = chapters.map { chapter -> chapter.name }
|
|
||||||
notifications.add(Pair(notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
|
||||||
try {
|
|
||||||
|
|
||||||
val request = GetRequest.Builder(this@LibraryUpdateService).data(manga)
|
|
||||||
.networkCachePolicy(CachePolicy.DISABLED)
|
|
||||||
.transformations(CircleCropTransformation()).size(width = 256, height = 256)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
Coil.imageLoader(this@LibraryUpdateService)
|
|
||||||
.execute(request).drawable?.let { drawable ->
|
|
||||||
setLargeIcon((drawable as BitmapDrawable).bitmap)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
}
|
|
||||||
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
|
||||||
setContentTitle(manga.title)
|
|
||||||
color = ContextCompat.getColor(this@LibraryUpdateService, R.color.colorAccent)
|
|
||||||
val chaptersNames = if (chapterNames.size > 5) {
|
|
||||||
"${chapterNames.take(4).joinToString(", ")}, " +
|
|
||||||
resources.getQuantityString(
|
|
||||||
R.plurals.notification_and_n_more,
|
|
||||||
(chapterNames.size - 4), (chapterNames.size - 4)
|
|
||||||
)
|
|
||||||
} else chapterNames.joinToString(", ")
|
|
||||||
setContentText(chaptersNames)
|
|
||||||
setStyle(NotificationCompat.BigTextStyle().bigText(chaptersNames))
|
|
||||||
priority = NotificationCompat.PRIORITY_HIGH
|
|
||||||
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
|
||||||
setContentIntent(
|
|
||||||
NotificationReceiver.openChapterPendingActivity(
|
|
||||||
this@LibraryUpdateService, manga, chapters.first()
|
|
||||||
)
|
|
||||||
)
|
|
||||||
addAction(
|
|
||||||
R.drawable.ic_glasses_black_24dp, getString(R.string.mark_as_read),
|
|
||||||
NotificationReceiver.markAsReadPendingBroadcast(
|
|
||||||
this@LibraryUpdateService,
|
|
||||||
manga, chapters, Notifications.ID_NEW_CHAPTERS
|
|
||||||
)
|
|
||||||
)
|
|
||||||
addAction(
|
|
||||||
R.drawable.ic_book_white_24dp, getString(R.string.view_chapters),
|
|
||||||
NotificationReceiver.openChapterPendingActivity(
|
|
||||||
this@LibraryUpdateService,
|
|
||||||
manga, Notifications.ID_NEW_CHAPTERS
|
|
||||||
)
|
|
||||||
)
|
|
||||||
setAutoCancel(true)
|
|
||||||
}, manga.id.hashCode()))
|
|
||||||
}
|
|
||||||
|
|
||||||
NotificationManagerCompat.from(this).apply {
|
|
||||||
|
|
||||||
notify(
|
|
||||||
Notifications.ID_NEW_CHAPTERS,
|
|
||||||
notification(Notifications.CHANNEL_NEW_CHAPTERS) {
|
|
||||||
setSmallIcon(R.drawable.ic_tachi)
|
|
||||||
setLargeIcon(notificationBitmap)
|
|
||||||
setContentTitle(getString(R.string.new_chapters_found))
|
|
||||||
color = ContextCompat.getColor(applicationContext, R.color.colorAccent)
|
|
||||||
if (updates.size > 1) {
|
|
||||||
setContentText(
|
|
||||||
resources.getQuantityString(
|
|
||||||
R.plurals
|
|
||||||
.for_n_titles,
|
|
||||||
updates.size, updates.size
|
|
||||||
)
|
|
||||||
)
|
|
||||||
setStyle(
|
|
||||||
NotificationCompat.BigTextStyle()
|
|
||||||
.bigText(updates.keys.joinToString("\n") {
|
|
||||||
it.title.chop(45)
|
|
||||||
})
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
setContentText(updates.keys.first().title.chop(45))
|
|
||||||
}
|
}
|
||||||
priority = NotificationCompat.PRIORITY_HIGH
|
}
|
||||||
setGroup(Notifications.GROUP_NEW_CHAPTERS)
|
return destFile
|
||||||
setGroupAlertBehavior(GROUP_ALERT_SUMMARY)
|
|
||||||
setGroupSummary(true)
|
|
||||||
setContentIntent(getNotificationIntent())
|
|
||||||
setAutoCancel(true)
|
|
||||||
})
|
|
||||||
|
|
||||||
notifications.forEach {
|
|
||||||
notify(it.second, it.first)
|
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// Empty
|
||||||
}
|
}
|
||||||
}
|
return File("")
|
||||||
|
|
||||||
/**
|
|
||||||
* Cancels the progress notification.
|
|
||||||
*/
|
|
||||||
private fun cancelProgressNotification() {
|
|
||||||
notificationManager.cancel(Notifications.ID_LIBRARY_PROGRESS)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns an intent to open the main activity.
|
|
||||||
*/
|
|
||||||
private fun getNotificationIntent(): PendingIntent {
|
|
||||||
val intent = Intent(this, MainActivity::class.java)
|
|
||||||
intent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP
|
|
||||||
intent.action = MainActivity.SHORTCUT_RECENTLY_UPDATED
|
|
||||||
return PendingIntent.getActivity(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,6 +435,22 @@ class NotificationReceiver : BroadcastReceiver() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns [PendingIntent] that opens the error log file in an external viewer
|
||||||
|
*
|
||||||
|
* @param context context of application
|
||||||
|
* @param uri uri of error log file
|
||||||
|
* @return [PendingIntent]
|
||||||
|
*/
|
||||||
|
internal fun openErrorLogPendingActivity(context: Context, uri: Uri): PendingIntent {
|
||||||
|
val intent = Intent().apply {
|
||||||
|
action = Intent.ACTION_VIEW
|
||||||
|
setDataAndType(uri, "text/plain")
|
||||||
|
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION
|
||||||
|
}
|
||||||
|
return PendingIntent.getActivity(context, 0, intent, 0)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns [PendingIntent] that opens the extensions controller,
|
* Returns [PendingIntent] that opens the extensions controller,
|
||||||
*
|
*
|
||||||
|
@ -24,6 +24,7 @@ object Notifications {
|
|||||||
*/
|
*/
|
||||||
const val CHANNEL_LIBRARY = "library_channel"
|
const val CHANNEL_LIBRARY = "library_channel"
|
||||||
const val ID_LIBRARY_PROGRESS = -101
|
const val ID_LIBRARY_PROGRESS = -101
|
||||||
|
const val ID_LIBRARY_ERROR = -102
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notification channel and ids used by the downloader.
|
* Notification channel and ids used by the downloader.
|
||||||
|
@ -121,8 +121,6 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val uniformGrid = "uniform_grid"
|
const val uniformGrid = "uniform_grid"
|
||||||
|
|
||||||
const val libraryAsSingleList = "library_as_single_list"
|
|
||||||
|
|
||||||
const val lang = "app_language"
|
const val lang = "app_language"
|
||||||
|
|
||||||
const val dateFormat = "app_date_format"
|
const val dateFormat = "app_date_format"
|
||||||
@ -149,6 +147,8 @@ object PreferenceKeys {
|
|||||||
|
|
||||||
const val updateOnRefresh = "update_on_refresh"
|
const val updateOnRefresh = "update_on_refresh"
|
||||||
|
|
||||||
|
const val showLibraryUpdateErrors = "show_library_update_errors"
|
||||||
|
|
||||||
const val alwaysShowChapterTransition = "always_show_chapter_transition"
|
const val alwaysShowChapterTransition = "always_show_chapter_transition"
|
||||||
|
|
||||||
@Deprecated("Use the preferences of the source")
|
@Deprecated("Use the preferences of the source")
|
||||||
|
@ -278,6 +278,8 @@ class PreferencesHelper(val context: Context) {
|
|||||||
|
|
||||||
fun onlySearchPinned() = flowPrefs.getBoolean(Keys.onlySearchPinned, true)
|
fun onlySearchPinned() = flowPrefs.getBoolean(Keys.onlySearchPinned, true)
|
||||||
|
|
||||||
|
fun showLibraryUpdateErrors() = prefs.getBoolean(Keys.showLibraryUpdateErrors, false)
|
||||||
|
|
||||||
// Tutorial preferences
|
// Tutorial preferences
|
||||||
fun shownFilterTutorial() = flowPrefs.getBoolean("shown_filter_tutorial", false)
|
fun shownFilterTutorial() = flowPrefs.getBoolean("shown_filter_tutorial", false)
|
||||||
|
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
package eu.kanade.tachiyomi.network
|
package eu.kanade.tachiyomi.network
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.chuckerteam.chucker.api.ChuckerInterceptor
|
|
||||||
import okhttp3.Cache
|
import okhttp3.Cache
|
||||||
import okhttp3.OkHttpClient
|
import okhttp3.OkHttpClient
|
||||||
import java.io.File
|
import java.io.File
|
||||||
@ -17,7 +16,7 @@ class NetworkHelper(context: Context) {
|
|||||||
val client = OkHttpClient.Builder()
|
val client = OkHttpClient.Builder()
|
||||||
.cookieJar(cookieManager)
|
.cookieJar(cookieManager)
|
||||||
.cache(Cache(cacheDir, cacheSize))
|
.cache(Cache(cacheDir, cacheSize))
|
||||||
.addInterceptor(ChuckerInterceptor(context))
|
// .addInterceptor(ChuckerInterceptor(context))
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
val cloudflareClient = client.newBuilder()
|
val cloudflareClient = client.newBuilder()
|
||||||
|
@ -159,6 +159,12 @@ class SettingsLibraryController : SettingsController() {
|
|||||||
summaryRes = R.string.auto_refresh_covers_summary
|
summaryRes = R.string.auto_refresh_covers_summary
|
||||||
defaultValue = true
|
defaultValue = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
switchPreference {
|
||||||
|
key = Keys.showLibraryUpdateErrors
|
||||||
|
titleRes = R.string.show_notification_error
|
||||||
|
defaultValue = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -170,6 +170,10 @@
|
|||||||
<item quantity="one">and %1$d more chapter</item>
|
<item quantity="one">and %1$d more chapter</item>
|
||||||
<item quantity="other">and %1$d more chapters</item>
|
<item quantity="other">and %1$d more chapters</item>
|
||||||
</plurals>
|
</plurals>
|
||||||
|
<plurals name="notification_update_failed">
|
||||||
|
<item quantity="one">1 update failed</item>
|
||||||
|
<item quantity="other">%1$d updates failed</item>
|
||||||
|
</plurals>
|
||||||
|
|
||||||
<!-- Library settings -->
|
<!-- Library settings -->
|
||||||
<string name="library_update_frequency">Library update frequency</string>
|
<string name="library_update_frequency">Library update frequency</string>
|
||||||
@ -186,6 +190,7 @@
|
|||||||
<string name="auto_refresh_covers">Automatically refresh covers</string>
|
<string name="auto_refresh_covers">Automatically refresh covers</string>
|
||||||
<string name="auto_refresh_covers_summary">Refresh covers in library as well
|
<string name="auto_refresh_covers_summary">Refresh covers in library as well
|
||||||
when updating library</string>
|
when updating library</string>
|
||||||
|
<string name="show_notification_error">Show a notification for errors</string>
|
||||||
|
|
||||||
<!-- Recents -->
|
<!-- Recents -->
|
||||||
<string name="recents">Recents</string>
|
<string name="recents">Recents</string>
|
||||||
|
Loading…
Reference in New Issue
Block a user