mirror of
https://github.com/tachiyomiorg/tachiyomi.git
synced 2024-12-22 22:41:48 +01:00
parent
8f144316a6
commit
ed77c60283
8
app/src/main/java/eu/kanade/tachiyomi/Constants.kt
Normal file
8
app/src/main/java/eu/kanade/tachiyomi/Constants.kt
Normal file
@ -0,0 +1,8 @@
|
||||
package eu.kanade.tachiyomi
|
||||
|
||||
object Constants {
|
||||
const val NOTIFICATION_LIBRARY_ID = 1
|
||||
const val NOTIFICATION_UPDATER_ID = 2
|
||||
const val NOTIFICATION_DOWNLOAD_CHAPTER_ID = 3
|
||||
const val NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID = 4
|
||||
}
|
@ -5,6 +5,7 @@ import android.net.Uri
|
||||
import com.google.gson.Gson
|
||||
import com.google.gson.reflect.TypeToken
|
||||
import com.google.gson.stream.JsonReader
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.models.Chapter
|
||||
import eu.kanade.tachiyomi.data.database.models.Manga
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
@ -14,7 +15,10 @@ import eu.kanade.tachiyomi.data.preference.getOrDefault
|
||||
import eu.kanade.tachiyomi.data.source.SourceManager
|
||||
import eu.kanade.tachiyomi.data.source.base.Source
|
||||
import eu.kanade.tachiyomi.data.source.model.Page
|
||||
import eu.kanade.tachiyomi.util.*
|
||||
import eu.kanade.tachiyomi.util.DiskUtils
|
||||
import eu.kanade.tachiyomi.util.DynamicConcurrentMergeOperator
|
||||
import eu.kanade.tachiyomi.util.UrlUtil
|
||||
import eu.kanade.tachiyomi.util.saveImageTo
|
||||
import rx.Observable
|
||||
import rx.Subscription
|
||||
import rx.android.schedulers.AndroidSchedulers
|
||||
@ -35,6 +39,8 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
||||
val runningSubject = BehaviorSubject.create<Boolean>()
|
||||
private var downloadsSubscription: Subscription? = null
|
||||
|
||||
val downloadNotifier by lazy { DownloadNotifier(context) }
|
||||
|
||||
private val threadsSubject = BehaviorSubject.create<Int>()
|
||||
private var threadsSubscription: Subscription? = null
|
||||
|
||||
@ -48,10 +54,14 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
||||
private set
|
||||
|
||||
private fun initializeSubscriptions() {
|
||||
|
||||
downloadsSubscription?.unsubscribe()
|
||||
|
||||
threadsSubscription = preferences.downloadThreads().asObservable()
|
||||
.subscribe { threadsSubject.onNext(it) }
|
||||
.subscribe {
|
||||
threadsSubject.onNext(it)
|
||||
downloadNotifier.multipleDownloadThreads = it > 1
|
||||
}
|
||||
|
||||
downloadsSubscription = downloadsQueueSubject.flatMap { Observable.from(it) }
|
||||
.lift(DynamicConcurrentMergeOperator<Download, Download>({ downloadChapter(it) }, threadsSubject))
|
||||
@ -60,7 +70,9 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
||||
.subscribe({
|
||||
// Delete successful downloads from queue
|
||||
if (it.status == Download.DOWNLOADED) {
|
||||
// remove downloaded chapter from queue
|
||||
queue.del(it)
|
||||
downloadNotifier.onProgressChange(queue)
|
||||
}
|
||||
if (areAllDownloadsFinished()) {
|
||||
DownloadService.stop(context)
|
||||
@ -68,7 +80,7 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
||||
}, { e ->
|
||||
DownloadService.stop(context)
|
||||
Timber.e(e, e.message)
|
||||
context.toast(e.message)
|
||||
downloadNotifier.onError(e.message)
|
||||
})
|
||||
|
||||
if (!isRunning) {
|
||||
@ -114,6 +126,12 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
||||
pending.add(download)
|
||||
}
|
||||
}
|
||||
|
||||
// Initialize queue size
|
||||
downloadNotifier.initialQueueSize = queue.size
|
||||
// Show notification
|
||||
downloadNotifier.onProgressChange(queue)
|
||||
|
||||
if (isRunning) downloadsQueueSubject.onNext(pending)
|
||||
}
|
||||
|
||||
@ -164,34 +182,40 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
||||
DiskUtils.createDirectory(download.directory)
|
||||
|
||||
val pageListObservable = if (download.pages == null)
|
||||
// Pull page list from network and add them to download object
|
||||
// Pull page list from network and add them to download object
|
||||
download.source.pullPageListFromNetwork(download.chapter.url)
|
||||
.doOnNext { pages ->
|
||||
download.pages = pages
|
||||
savePageList(download)
|
||||
}
|
||||
else
|
||||
// Or if the page list already exists, start from the file
|
||||
// Or if the page list already exists, start from the file
|
||||
Observable.just(download.pages)
|
||||
|
||||
return Observable.defer { pageListObservable
|
||||
.doOnNext { pages ->
|
||||
download.downloadedImages = 0
|
||||
download.status = Download.DOWNLOADING
|
||||
}
|
||||
// Get all the URLs to the source images, fetch pages if necessary
|
||||
.flatMap { download.source.getAllImageUrlsFromPageList(it) }
|
||||
// Start downloading images, consider we can have downloaded images already
|
||||
.concatMap { page -> getOrDownloadImage(page, download) }
|
||||
// Do after download completes
|
||||
.doOnCompleted { onDownloadCompleted(download) }
|
||||
.toList()
|
||||
.map { pages -> download }
|
||||
// If the page list threw, it will resume here
|
||||
.onErrorResumeNext { error ->
|
||||
download.status = Download.ERROR
|
||||
Observable.just(download)
|
||||
}
|
||||
return Observable.defer {
|
||||
pageListObservable
|
||||
.doOnNext { pages ->
|
||||
download.downloadedImages = 0
|
||||
download.status = Download.DOWNLOADING
|
||||
}
|
||||
// Get all the URLs to the source images, fetch pages if necessary
|
||||
.flatMap { download.source.getAllImageUrlsFromPageList(it) }
|
||||
// Start downloading images, consider we can have downloaded images already
|
||||
.concatMap { page -> getOrDownloadImage(page, download) }
|
||||
// Do when page is downloaded.
|
||||
.doOnNext {
|
||||
downloadNotifier.onProgressChange(download, queue)
|
||||
}
|
||||
// Do after download completes
|
||||
.doOnCompleted { onDownloadCompleted(download) }
|
||||
.toList()
|
||||
.map { pages -> download }
|
||||
// If the page list threw, it will resume here
|
||||
.onErrorResumeNext { error ->
|
||||
download.status = Download.ERROR
|
||||
downloadNotifier.onError(error.message, download.chapter.name)
|
||||
Observable.just(download)
|
||||
}
|
||||
}.subscribeOn(Schedulers.io())
|
||||
}
|
||||
|
||||
@ -297,11 +321,15 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
||||
// If any page has an error, the download result will be error
|
||||
for (page in download.pages) {
|
||||
actualProgress += page.progress
|
||||
if (page.status != Page.READY) status = Download.ERROR
|
||||
if (page.status != Page.READY) {
|
||||
status = Download.ERROR
|
||||
downloadNotifier.onError(context.getString(R.string.download_notifier_page_ready_error), download.chapter.name)
|
||||
}
|
||||
}
|
||||
// Ensure that the chapter folder has all the images
|
||||
if (!isChapterDownloaded(download.directory, download.pages)) {
|
||||
status = Download.ERROR
|
||||
downloadNotifier.onError(context.getString(R.string.download_notifier_page_error), download.chapter.name)
|
||||
}
|
||||
download.totalProgress = actualProgress
|
||||
download.status = status
|
||||
@ -399,13 +427,19 @@ class DownloadManager(private val context: Context, private val sourceManager: S
|
||||
return !pending.isEmpty()
|
||||
}
|
||||
|
||||
fun stopDownloads() {
|
||||
fun stopDownloads(error: String = "") {
|
||||
destroySubscriptions()
|
||||
for (download in queue) {
|
||||
if (download.status == Download.DOWNLOADING) {
|
||||
download.status = Download.ERROR
|
||||
}
|
||||
}
|
||||
downloadNotifier.onError(error)
|
||||
}
|
||||
|
||||
fun clearQueue() {
|
||||
queue.clear()
|
||||
downloadNotifier.onClear()
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,180 @@
|
||||
package eu.kanade.tachiyomi.data.download
|
||||
|
||||
import android.content.Context
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import eu.kanade.tachiyomi.Constants
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.download.model.Download
|
||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
||||
import eu.kanade.tachiyomi.util.notificationManager
|
||||
|
||||
/**
|
||||
* DownloadNotifier is used to show notifications when downloading one or multiple chapters.
|
||||
* @param context context of application
|
||||
*/
|
||||
class DownloadNotifier(private val context: Context) {
|
||||
/**
|
||||
* Notification builder.
|
||||
*/
|
||||
private val notificationBuilder = NotificationCompat.Builder(context)
|
||||
|
||||
/**
|
||||
* Id of the notification.
|
||||
*/
|
||||
private val notificationId = Constants.NOTIFICATION_DOWNLOAD_CHAPTER_ID
|
||||
|
||||
/**
|
||||
* Status of download. Used for correct notification icon.
|
||||
*/
|
||||
private var isDownloading = false
|
||||
|
||||
/**
|
||||
* The size of queue on start download.
|
||||
*/
|
||||
internal var initialQueueSize = 0
|
||||
|
||||
/**
|
||||
* Simultaneous download setting > 1.
|
||||
*/
|
||||
internal var multipleDownloadThreads = false
|
||||
|
||||
/**
|
||||
* Called when download progress changes.
|
||||
* Note: Only accepted when multi download active.
|
||||
* @param queue the queue containing downloads.
|
||||
*/
|
||||
internal fun onProgressChange(queue: DownloadQueue) {
|
||||
// If single download mode return.
|
||||
if (!multipleDownloadThreads)
|
||||
return
|
||||
// Update progress.
|
||||
doOnProgressChange(null, queue)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when download progress changes
|
||||
* Note: Only accepted when single download active
|
||||
* @param download download object containing download information
|
||||
* @param queue the queue containing downloads
|
||||
*/
|
||||
internal fun onProgressChange(download: Download, queue: DownloadQueue) {
|
||||
// If multi download mode return.
|
||||
if (multipleDownloadThreads)
|
||||
return
|
||||
// Update progress.
|
||||
doOnProgressChange(download, queue)
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Show notification progress of chapter
|
||||
* @param download download object containing download information
|
||||
* @param queue the queue containing downloads
|
||||
*/
|
||||
private fun doOnProgressChange(download: Download?, queue: DownloadQueue) {
|
||||
// Check if download is completed
|
||||
if (multipleDownloadThreads) {
|
||||
if (queue.isEmpty()) {
|
||||
onComplete(null)
|
||||
return
|
||||
}
|
||||
} else {
|
||||
if (download != null && download.pages.size == download.downloadedImages) {
|
||||
onComplete(download)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Create notification
|
||||
with (notificationBuilder)
|
||||
{
|
||||
// Check if icon needs refresh
|
||||
if (!isDownloading) {
|
||||
setSmallIcon(android.R.drawable.stat_sys_download)
|
||||
isDownloading = true
|
||||
}
|
||||
|
||||
if (multipleDownloadThreads) {
|
||||
setContentTitle(context.getString(R.string.app_name))
|
||||
|
||||
setContentText(context.getString(R.string.chapter_downloading_progress)
|
||||
.format(initialQueueSize - queue.size, initialQueueSize))
|
||||
setProgress(initialQueueSize, initialQueueSize - queue.size, false)
|
||||
} else {
|
||||
download?.let {
|
||||
if (it.chapter.name.length >= 33)
|
||||
setContentTitle(it.chapter.name.slice(IntRange(0, 30)).plus("..."))
|
||||
else
|
||||
setContentTitle(it.chapter.name)
|
||||
|
||||
setContentText(context.getString(R.string.chapter_downloading_progress)
|
||||
.format(it.downloadedImages, it.pages.size))
|
||||
setProgress(it.pages.size, it.downloadedImages, false)
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
// Displays the progress bar on notification
|
||||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
||||
}
|
||||
|
||||
/**
|
||||
* Called when chapter is downloaded
|
||||
* @param download download object containing download information
|
||||
*/
|
||||
private fun onComplete(download: Download?) {
|
||||
//Create notification.
|
||||
with(notificationBuilder) {
|
||||
// Set notification title
|
||||
if (download != null)
|
||||
setContentTitle(download.chapter?.name)
|
||||
else
|
||||
setContentTitle(context.getString(R.string.app_name))
|
||||
|
||||
// Set content information and progress.
|
||||
setContentText(context.getString(R.string.update_check_notification_download_complete))
|
||||
setSmallIcon(android.R.drawable.stat_sys_download_done)
|
||||
setProgress(0, 0, false)
|
||||
}
|
||||
|
||||
// Show notification.
|
||||
context.notificationManager.notify(notificationId, notificationBuilder.build())
|
||||
|
||||
// Reset initial values
|
||||
isDownloading = false
|
||||
initialQueueSize = 0
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears the notification message
|
||||
*/
|
||||
internal fun onClear() {
|
||||
context.notificationManager.cancel(notificationId)
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on error while downloading chapter
|
||||
* @param error string containing error information
|
||||
* @param chapter string containing chapter title
|
||||
*/
|
||||
internal fun onError(error: String? = "", chapter: String = "") {
|
||||
// Create notification
|
||||
with(notificationBuilder) {
|
||||
if (chapter.isNullOrEmpty()) {
|
||||
setContentTitle(context.getString(R.string.download_notifier_title_error))
|
||||
} else {
|
||||
setContentTitle(chapter)
|
||||
}
|
||||
|
||||
if (error.isNullOrEmpty())
|
||||
setContentText(context.getString(R.string.download_notifier_unkown_error))
|
||||
else
|
||||
setContentText(error)
|
||||
|
||||
setSmallIcon(android.R.drawable.stat_sys_warning)
|
||||
setProgress(0, 0, false)
|
||||
}
|
||||
context.notificationManager.notify(Constants.NOTIFICATION_DOWNLOAD_CHAPTER_ERROR_ID, notificationBuilder.build())
|
||||
isDownloading = false
|
||||
}
|
||||
}
|
@ -82,12 +82,12 @@ class DownloadService : Service() {
|
||||
stopSelf()
|
||||
}
|
||||
} else if (isRunning) {
|
||||
downloadManager.stopDownloads()
|
||||
downloadManager.stopDownloads(baseContext.getString(R.string.download_notifier_text_only_wifi))
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
if (isRunning) {
|
||||
downloadManager.stopDownloads()
|
||||
downloadManager.stopDownloads(baseContext.getString(R.string.download_notifier_text_only_wifi))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import android.util.Pair
|
||||
import com.github.pwittchen.reactivenetwork.library.ConnectivityStatus
|
||||
import com.github.pwittchen.reactivenetwork.library.ReactiveNetwork
|
||||
import eu.kanade.tachiyomi.App
|
||||
import eu.kanade.tachiyomi.Constants
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.database.DatabaseHelper
|
||||
import eu.kanade.tachiyomi.data.database.models.Category
|
||||
@ -67,7 +68,7 @@ class LibraryUpdateService : Service() {
|
||||
/**
|
||||
* Id of the library update notification.
|
||||
*/
|
||||
const val UPDATE_NOTIFICATION_ID = 1
|
||||
const val UPDATE_NOTIFICATION_ID = Constants.NOTIFICATION_LIBRARY_ID
|
||||
|
||||
/**
|
||||
* Key for manual library update.
|
||||
@ -206,8 +207,8 @@ class LibraryUpdateService : Service() {
|
||||
showNotification(getString(R.string.notification_update_error), "")
|
||||
stopSelf(startId)
|
||||
}, {
|
||||
stopSelf(startId)
|
||||
})
|
||||
stopSelf(startId)
|
||||
})
|
||||
|
||||
return Service.START_STICKY
|
||||
}
|
||||
@ -451,7 +452,6 @@ class LibraryUpdateService : Service() {
|
||||
class CancelUpdateReceiver : BroadcastReceiver() {
|
||||
/**
|
||||
* Method called when user wants a library update.
|
||||
*
|
||||
* @param context the application context.
|
||||
* @param intent the intent received.
|
||||
*/
|
||||
@ -460,5 +460,4 @@ class LibraryUpdateService : Service() {
|
||||
context.notificationManager.cancel(UPDATE_NOTIFICATION_ID)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import android.net.Uri
|
||||
import android.os.AsyncTask
|
||||
import android.support.v4.app.NotificationCompat
|
||||
import eu.kanade.tachiyomi.App
|
||||
import eu.kanade.tachiyomi.Constants
|
||||
import eu.kanade.tachiyomi.R
|
||||
import eu.kanade.tachiyomi.data.network.NetworkHelper
|
||||
import eu.kanade.tachiyomi.data.network.ProgressListener
|
||||
@ -181,7 +182,7 @@ class UpdateDownloader(private val context: Context) :
|
||||
val FILE_LOCATION = "file_location"
|
||||
|
||||
// Id of the notification
|
||||
val notificationId = 2
|
||||
val notificationId = Constants.NOTIFICATION_UPDATER_ID
|
||||
}
|
||||
|
||||
override fun onReceive(context: Context, intent: Intent) {
|
||||
|
@ -59,7 +59,7 @@ class DownloadPresenter : BasePresenter<DownloadFragment>() {
|
||||
* Clears the download queue.
|
||||
*/
|
||||
fun clearQueue() {
|
||||
downloadQueue.clear()
|
||||
downloadManager.clearQueue()
|
||||
start(GET_DOWNLOAD_QUEUE)
|
||||
}
|
||||
|
||||
|
@ -303,4 +303,10 @@
|
||||
<string name="information_no_recent">No recent chapters</string>
|
||||
<string name="information_empty_library">Empty library</string>
|
||||
|
||||
<!-- Download Notification -->
|
||||
<string name="download_notifier_title_error">Error</string>
|
||||
<string name="download_notifier_unkown_error">An unexpected error occurred while downloading chapter</string>
|
||||
<string name="download_notifier_page_error">A page is missing in directory</string>
|
||||
<string name="download_notifier_page_ready_error">A page is not loaded</string>
|
||||
<string name="download_notifier_text_only_wifi">No wifi connection available</string>
|
||||
</resources>
|
||||
|
Loading…
Reference in New Issue
Block a user