From 4abd2d709f8a838bd56b2ca204f803f3b94a3d8f Mon Sep 17 00:00:00 2001 From: len Date: Fri, 13 Oct 2017 00:12:29 +0200 Subject: [PATCH] Introduce coroutines. Fix #1027. Lower notification channels' importance --- app/build.gradle | 12 ++- .../tachiyomi/data/download/Downloader.kt | 88 +++++++++---------- .../data/notification/Notifications.kt | 6 +- .../ui/manga/chapter/ChaptersPresenter.kt | 4 - .../recent_updates/RecentChaptersPresenter.kt | 3 - .../tachiyomi/util/CoroutinesExtensions.kt | 13 +++ 6 files changed, 70 insertions(+), 56 deletions(-) create mode 100644 app/src/main/java/eu/kanade/tachiyomi/util/CoroutinesExtensions.kt diff --git a/app/build.gradle b/app/build.gradle index f682e58d0a..d2200e0a33 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -218,10 +218,14 @@ dependencies { testCompile "org.robolectric:shadows-play-services:$robolectric_version" compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + + final coroutines_version = '0.19.1' + compile "org.jetbrains.kotlinx:kotlinx-coroutines-core:$coroutines_version" + compile "org.jetbrains.kotlinx:kotlinx-coroutines-android:$coroutines_version" } buildscript { - ext.kotlin_version = '1.1.50' + ext.kotlin_version = '1.1.51' repositories { mavenCentral() } @@ -234,6 +238,12 @@ repositories { mavenCentral() } +kotlin { + experimental { + coroutines 'enable' + } +} + // add support for placeholders in resource files //https://code.google.com/p/android/issues/detail?id=69224 def replacePlaceholdersInFile(basePath, fileName, placeholders) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 64941401d1..da232f869f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -16,6 +16,7 @@ import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.source.online.HttpSource import eu.kanade.tachiyomi.source.online.fetchAllImageUrlsFromPageList import eu.kanade.tachiyomi.util.* +import kotlinx.coroutines.experimental.async import okhttp3.Response import rx.Observable import rx.android.schedulers.AndroidSchedulers @@ -90,11 +91,10 @@ class Downloader(private val context: Context, private val provider: DownloadPro @Volatile private var isRunning: Boolean = false init { - Observable.fromCallable { store.restore() } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - .subscribe({ downloads -> queue.addAll(downloads) - }, { error -> Timber.e(error) }) + launchNow { + val chapters = async { store.restore() } + queue.addAll(chapters.await()) + } } /** @@ -217,51 +217,49 @@ class Downloader(private val context: Context, private val provider: DownloadPro * @param manga the manga of the chapters to download. * @param chapters the list of chapters to download. */ - fun queueChapters(manga: Manga, chapters: List) { - val source = sourceManager.get(manga.source) as? HttpSource ?: return + fun queueChapters(manga: Manga, chapters: List) = launchUI { + val source = sourceManager.get(manga.source) as? HttpSource ?: return@launchUI - Observable - // Background, long running checks - .fromCallable { - val mangaDir = provider.findMangaDir(source, manga) + // Called in background thread, the operation can be slow with SAF. + val chaptersWithoutDir = async { + val mangaDir = provider.findMangaDir(source, manga) - chapters - // Avoid downloading chapters with the same name. - .distinctBy { it.name } - // Filter out those already downloaded. - .filter { mangaDir?.findFile(provider.getChapterDirName(it)) == null } - // Add chapters to queue from the start. - .sortedByDescending { it.source_order } - } - .subscribeOn(Schedulers.io()) - .observeOn(AndroidSchedulers.mainThread()) - // Main thread, quick checks - .map { chaptersToQueue -> - chaptersToQueue - // Filter out those already enqueued. - .filter { chapter -> queue.none { it.chapter.id == chapter.id } } - // Create a download for each one. - .map { Download(source, manga, it) } - } - .subscribe { chaptersToQueue -> - if (chaptersToQueue.isNotEmpty()) { - queue.addAll(chaptersToQueue) + chapters + // Avoid downloading chapters with the same name. + .distinctBy { it.name } + // Filter out those already downloaded. + .filter { mangaDir?.findFile(provider.getChapterDirName(it)) == null } + // Add chapters to queue from the start. + .sortedByDescending { it.source_order } + } - // Initialize queue size. - notifier.initialQueueSize = queue.size + // Runs in main thread (synchronization needed). + val chaptersToQueue = chaptersWithoutDir.await() + // Filter out those already enqueued. + .filter { chapter -> queue.none { it.chapter.id == chapter.id } } + // Create a download for each one. + .map { Download(source, manga, it) } - // Initial multi-thread - notifier.multipleDownloadThreads = preferences.downloadThreads().getOrDefault() > 1 + if (chaptersToQueue.isNotEmpty()) { + queue.addAll(chaptersToQueue) - if (isRunning) { - // Send the list of downloads to the downloader. - downloadsRelay.call(chaptersToQueue) - } else { - // Show initial notification. - notifier.onProgressChange(queue) - } - } - } + // Initialize queue size. + notifier.initialQueueSize = queue.size + + // Initial multi-thread + notifier.multipleDownloadThreads = preferences.downloadThreads().getOrDefault() > 1 + + if (isRunning) { + // Send the list of downloads to the downloader. + downloadsRelay.call(chaptersToQueue) + } else { + // Show initial notification. + notifier.onProgressChange(queue) + } + + // Start downloader if needed + DownloadService.start(this@Downloader.context) + } } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt index 079c859dfe..be537dd14f 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/Notifications.kt @@ -43,11 +43,11 @@ object Notifications { val channels = listOf( NotificationChannel(CHANNEL_COMMON, context.getString(R.string.channel_common), - NotificationManager.IMPORTANCE_DEFAULT), + NotificationManager.IMPORTANCE_LOW), NotificationChannel(CHANNEL_LIBRARY, context.getString(R.string.channel_library), - NotificationManager.IMPORTANCE_DEFAULT), + NotificationManager.IMPORTANCE_LOW), NotificationChannel(CHANNEL_DOWNLOADER, context.getString(R.string.channel_downloader), - NotificationManager.IMPORTANCE_DEFAULT) + NotificationManager.IMPORTANCE_LOW) ) context.notificationManager.createNotificationChannels(channels) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt index 71283739d4..17ca3e055c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/manga/chapter/ChaptersPresenter.kt @@ -7,7 +7,6 @@ import eu.kanade.tachiyomi.data.database.DatabaseHelper import eu.kanade.tachiyomi.data.database.models.Chapter import eu.kanade.tachiyomi.data.database.models.Manga import eu.kanade.tachiyomi.data.download.DownloadManager -import eu.kanade.tachiyomi.data.download.DownloadService import eu.kanade.tachiyomi.data.download.model.Download import eu.kanade.tachiyomi.data.preference.PreferencesHelper import eu.kanade.tachiyomi.source.Source @@ -35,8 +34,6 @@ class ChaptersPresenter( private val downloadManager: DownloadManager = Injekt.get() ) : BasePresenter() { - private val context = preferences.context - /** * List of chapters of the manga. It's always unfiltered and unsorted. */ @@ -246,7 +243,6 @@ class ChaptersPresenter( * @param chapters the list of chapters to download. */ fun downloadChapters(chapters: List) { - DownloadService.start(context) downloadManager.downloadChapters(manga, chapters) } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt index fe7cebfb53..b9496a72bd 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/recent_updates/RecentChaptersPresenter.kt @@ -25,8 +25,6 @@ class RecentChaptersPresenter( private val sourceManager: SourceManager = Injekt.get() ) : BasePresenter() { - private val context = preferences.context - /** * List containing chapter and manga information */ @@ -207,7 +205,6 @@ class RecentChaptersPresenter( */ fun downloadChapters(items: List) { items.forEach { downloadManager.downloadChapters(it.manga, listOf(it.chapter)) } - DownloadService.start(context) } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/util/CoroutinesExtensions.kt b/app/src/main/java/eu/kanade/tachiyomi/util/CoroutinesExtensions.kt new file mode 100644 index 0000000000..8959794a1d --- /dev/null +++ b/app/src/main/java/eu/kanade/tachiyomi/util/CoroutinesExtensions.kt @@ -0,0 +1,13 @@ +package eu.kanade.tachiyomi.util + +import kotlinx.coroutines.experimental.CoroutineScope +import kotlinx.coroutines.experimental.CoroutineStart +import kotlinx.coroutines.experimental.Job +import kotlinx.coroutines.experimental.android.UI +import kotlinx.coroutines.experimental.launch + +fun launchUI(block: suspend CoroutineScope.() -> Unit): Job = + launch(UI, CoroutineStart.DEFAULT, block) + +fun launchNow(block: suspend CoroutineScope.() -> Unit): Job = + launch(UI, CoroutineStart.UNDISPATCHED, block) \ No newline at end of file