Inline DownloadQueue into Downloader (#9159)
* Move statusFlow and progressFlow to DownloadManager * Inline DownloadQueue into Downloader * Move reorderQueue implementation to Downloader
This commit is contained in:
parent
f03a834136
commit
b41565f879
7 changed files with 134 additions and 151 deletions
|
@ -4,10 +4,17 @@ import android.content.Context
|
||||||
import eu.kanade.domain.download.service.DownloadPreferences
|
import eu.kanade.domain.download.service.DownloadPreferences
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
|
||||||
import eu.kanade.tachiyomi.source.Source
|
import eu.kanade.tachiyomi.source.Source
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
import eu.kanade.tachiyomi.source.model.Page
|
import eu.kanade.tachiyomi.source.model.Page
|
||||||
|
import kotlinx.coroutines.flow.Flow
|
||||||
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
import kotlinx.coroutines.flow.drop
|
||||||
|
import kotlinx.coroutines.flow.emitAll
|
||||||
|
import kotlinx.coroutines.flow.flatMapLatest
|
||||||
|
import kotlinx.coroutines.flow.map
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
|
import kotlinx.coroutines.flow.onStart
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import tachiyomi.core.util.lang.launchIO
|
import tachiyomi.core.util.lang.launchIO
|
||||||
|
@ -42,11 +49,8 @@ class DownloadManager(
|
||||||
*/
|
*/
|
||||||
private val pendingDeleter = DownloadPendingDeleter(context)
|
private val pendingDeleter = DownloadPendingDeleter(context)
|
||||||
|
|
||||||
/**
|
val queueState
|
||||||
* Downloads queue, where the pending chapters are stored.
|
get() = downloader.queueState
|
||||||
*/
|
|
||||||
val queue: DownloadQueue
|
|
||||||
get() = downloader.queue
|
|
||||||
|
|
||||||
// For use by DownloadService only
|
// For use by DownloadService only
|
||||||
fun downloaderStart() = downloader.start()
|
fun downloaderStart() = downloader.start()
|
||||||
|
@ -85,7 +89,7 @@ class DownloadManager(
|
||||||
* @param chapterId the chapter to check.
|
* @param chapterId the chapter to check.
|
||||||
*/
|
*/
|
||||||
fun getQueuedDownloadOrNull(chapterId: Long): Download? {
|
fun getQueuedDownloadOrNull(chapterId: Long): Download? {
|
||||||
return queue.find { it.chapter.id == chapterId }
|
return queueState.value.find { it: Download -> it.chapter.id == chapterId }
|
||||||
}
|
}
|
||||||
|
|
||||||
fun startDownloadNow(chapterId: Long?) {
|
fun startDownloadNow(chapterId: Long?) {
|
||||||
|
@ -93,7 +97,7 @@ class DownloadManager(
|
||||||
val download = getQueuedDownloadOrNull(chapterId)
|
val download = getQueuedDownloadOrNull(chapterId)
|
||||||
// If not in queue try to start a new download
|
// If not in queue try to start a new download
|
||||||
val toAdd = download ?: runBlocking { Download.fromChapterId(chapterId) } ?: return
|
val toAdd = download ?: runBlocking { Download.fromChapterId(chapterId) } ?: return
|
||||||
val queue = queue.toMutableList()
|
val queue = queueState.value.toMutableList()
|
||||||
download?.let { queue.remove(it) }
|
download?.let { queue.remove(it) }
|
||||||
queue.add(0, toAdd)
|
queue.add(0, toAdd)
|
||||||
reorderQueue(queue)
|
reorderQueue(queue)
|
||||||
|
@ -112,21 +116,7 @@ class DownloadManager(
|
||||||
* @param downloads value to set the download queue to
|
* @param downloads value to set the download queue to
|
||||||
*/
|
*/
|
||||||
fun reorderQueue(downloads: List<Download>) {
|
fun reorderQueue(downloads: List<Download>) {
|
||||||
val wasRunning = downloader.isRunning
|
downloader.updateQueue(downloads)
|
||||||
|
|
||||||
if (downloads.isEmpty()) {
|
|
||||||
downloader.clearQueue()
|
|
||||||
downloader.stop()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
downloader.pause()
|
|
||||||
queue.clear()
|
|
||||||
queue.addAll(downloads)
|
|
||||||
|
|
||||||
if (wasRunning) {
|
|
||||||
downloader.start()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -147,7 +137,7 @@ class DownloadManager(
|
||||||
*/
|
*/
|
||||||
fun addDownloadsToStartOfQueue(downloads: List<Download>) {
|
fun addDownloadsToStartOfQueue(downloads: List<Download>) {
|
||||||
if (downloads.isEmpty()) return
|
if (downloads.isEmpty()) return
|
||||||
queue.toMutableList().apply {
|
queueState.value.toMutableList().apply {
|
||||||
addAll(0, downloads)
|
addAll(0, downloads)
|
||||||
reorderQueue(this)
|
reorderQueue(this)
|
||||||
}
|
}
|
||||||
|
@ -251,7 +241,7 @@ class DownloadManager(
|
||||||
fun deleteManga(manga: Manga, source: Source, removeQueued: Boolean = true) {
|
fun deleteManga(manga: Manga, source: Source, removeQueued: Boolean = true) {
|
||||||
launchIO {
|
launchIO {
|
||||||
if (removeQueued) {
|
if (removeQueued) {
|
||||||
queue.remove(manga)
|
downloader.removeFromQueue(manga)
|
||||||
}
|
}
|
||||||
provider.findMangaDir(manga.title, source)?.delete()
|
provider.findMangaDir(manga.title, source)?.delete()
|
||||||
cache.removeManga(manga)
|
cache.removeManga(manga)
|
||||||
|
@ -271,12 +261,12 @@ class DownloadManager(
|
||||||
downloader.pause()
|
downloader.pause()
|
||||||
}
|
}
|
||||||
|
|
||||||
queue.remove(chapters)
|
downloader.removeFromQueue(chapters)
|
||||||
|
|
||||||
if (wasRunning) {
|
if (wasRunning) {
|
||||||
if (queue.isEmpty()) {
|
if (queueState.value.isEmpty()) {
|
||||||
downloader.stop()
|
downloader.stop()
|
||||||
} else if (queue.isNotEmpty()) {
|
} else if (queueState.value.isNotEmpty()) {
|
||||||
downloader.start()
|
downloader.start()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -374,4 +364,33 @@ class DownloadManager(
|
||||||
chapters
|
chapters
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun statusFlow(): Flow<Download> = queueState
|
||||||
|
.flatMapLatest { downloads ->
|
||||||
|
downloads
|
||||||
|
.map { download ->
|
||||||
|
download.statusFlow.drop(1).map { download }
|
||||||
|
}
|
||||||
|
.merge()
|
||||||
|
}
|
||||||
|
.onStart {
|
||||||
|
emitAll(
|
||||||
|
queueState.value.filter { download -> download.status == Download.State.DOWNLOADING }.asFlow(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun progressFlow(): Flow<Download> = queueState
|
||||||
|
.flatMapLatest { downloads ->
|
||||||
|
downloads
|
||||||
|
.map { download ->
|
||||||
|
download.progressFlow.drop(1).map { download }
|
||||||
|
}
|
||||||
|
.merge()
|
||||||
|
}
|
||||||
|
.onStart {
|
||||||
|
emitAll(
|
||||||
|
queueState.value.filter { download -> download.status == Download.State.DOWNLOADING }
|
||||||
|
.asFlow(),
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -11,7 +11,6 @@ import eu.kanade.domain.manga.model.getComicInfo
|
||||||
import eu.kanade.tachiyomi.R
|
import eu.kanade.tachiyomi.R
|
||||||
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
import eu.kanade.tachiyomi.data.cache.ChapterCache
|
||||||
import eu.kanade.tachiyomi.data.download.model.Download
|
import eu.kanade.tachiyomi.data.download.model.Download
|
||||||
import eu.kanade.tachiyomi.data.download.model.DownloadQueue
|
|
||||||
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
|
import eu.kanade.tachiyomi.data.library.LibraryUpdateNotifier
|
||||||
import eu.kanade.tachiyomi.data.notification.NotificationHandler
|
import eu.kanade.tachiyomi.data.notification.NotificationHandler
|
||||||
import eu.kanade.tachiyomi.source.SourceManager
|
import eu.kanade.tachiyomi.source.SourceManager
|
||||||
|
@ -25,12 +24,15 @@ import kotlinx.coroutines.CancellationException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.async
|
import kotlinx.coroutines.async
|
||||||
import kotlinx.coroutines.delay
|
import kotlinx.coroutines.delay
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.asFlow
|
import kotlinx.coroutines.flow.asFlow
|
||||||
|
import kotlinx.coroutines.flow.asStateFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.flatMapMerge
|
import kotlinx.coroutines.flow.flatMapMerge
|
||||||
import kotlinx.coroutines.flow.flow
|
import kotlinx.coroutines.flow.flow
|
||||||
import kotlinx.coroutines.flow.flowOn
|
import kotlinx.coroutines.flow.flowOn
|
||||||
import kotlinx.coroutines.flow.retryWhen
|
import kotlinx.coroutines.flow.retryWhen
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.runBlocking
|
import kotlinx.coroutines.runBlocking
|
||||||
import logcat.LogPriority
|
import logcat.LogPriority
|
||||||
import nl.adaptivity.xmlutil.serialization.XML
|
import nl.adaptivity.xmlutil.serialization.XML
|
||||||
|
@ -59,7 +61,7 @@ import java.util.zip.ZipOutputStream
|
||||||
/**
|
/**
|
||||||
* This class is the one in charge of downloading chapters.
|
* This class is the one in charge of downloading chapters.
|
||||||
*
|
*
|
||||||
* Its [queue] contains the list of chapters to download. In order to download them, the downloader
|
* Its queue contains the list of chapters to download. In order to download them, the downloader
|
||||||
* subscription must be running and the list of chapters must be sent to them by [downloadsRelay].
|
* subscription must be running and the list of chapters must be sent to them by [downloadsRelay].
|
||||||
*
|
*
|
||||||
* The queue manipulation must be done in one thread (currently the main thread) to avoid unexpected
|
* The queue manipulation must be done in one thread (currently the main thread) to avoid unexpected
|
||||||
|
@ -88,7 +90,8 @@ class Downloader(
|
||||||
/**
|
/**
|
||||||
* Queue where active downloads are kept.
|
* Queue where active downloads are kept.
|
||||||
*/
|
*/
|
||||||
val queue = DownloadQueue(store)
|
val _queueState = MutableStateFlow<List<Download>>(emptyList())
|
||||||
|
val queueState = _queueState.asStateFlow()
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Notifier for the downloader state and progress.
|
* Notifier for the downloader state and progress.
|
||||||
|
@ -120,7 +123,7 @@ class Downloader(
|
||||||
init {
|
init {
|
||||||
launchNow {
|
launchNow {
|
||||||
val chapters = async { store.restore() }
|
val chapters = async { store.restore() }
|
||||||
queue.addAll(chapters.await())
|
addAllToQueue(chapters.await())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -131,13 +134,13 @@ class Downloader(
|
||||||
* @return true if the downloader is started, false otherwise.
|
* @return true if the downloader is started, false otherwise.
|
||||||
*/
|
*/
|
||||||
fun start(): Boolean {
|
fun start(): Boolean {
|
||||||
if (subscription != null || queue.isEmpty()) {
|
if (subscription != null || queueState.value.isEmpty()) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
initializeSubscription()
|
initializeSubscription()
|
||||||
|
|
||||||
val pending = queue.filter { it.status != Download.State.DOWNLOADED }
|
val pending = queueState.value.filter { it: Download -> it.status != Download.State.DOWNLOADED }
|
||||||
pending.forEach { if (it.status != Download.State.QUEUE) it.status = Download.State.QUEUE }
|
pending.forEach { if (it.status != Download.State.QUEUE) it.status = Download.State.QUEUE }
|
||||||
|
|
||||||
isPaused = false
|
isPaused = false
|
||||||
|
@ -151,7 +154,7 @@ class Downloader(
|
||||||
*/
|
*/
|
||||||
fun stop(reason: String? = null) {
|
fun stop(reason: String? = null) {
|
||||||
destroySubscription()
|
destroySubscription()
|
||||||
queue
|
queueState.value
|
||||||
.filter { it.status == Download.State.DOWNLOADING }
|
.filter { it.status == Download.State.DOWNLOADING }
|
||||||
.forEach { it.status = Download.State.ERROR }
|
.forEach { it.status = Download.State.ERROR }
|
||||||
|
|
||||||
|
@ -160,7 +163,7 @@ class Downloader(
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
if (isPaused && queue.isNotEmpty()) {
|
if (isPaused && queueState.value.isNotEmpty()) {
|
||||||
notifier.onPaused()
|
notifier.onPaused()
|
||||||
} else {
|
} else {
|
||||||
notifier.onComplete()
|
notifier.onComplete()
|
||||||
|
@ -179,7 +182,7 @@ class Downloader(
|
||||||
*/
|
*/
|
||||||
fun pause() {
|
fun pause() {
|
||||||
destroySubscription()
|
destroySubscription()
|
||||||
queue
|
queueState.value
|
||||||
.filter { it.status == Download.State.DOWNLOADING }
|
.filter { it.status == Download.State.DOWNLOADING }
|
||||||
.forEach { it.status = Download.State.QUEUE }
|
.forEach { it.status = Download.State.QUEUE }
|
||||||
isPaused = true
|
isPaused = true
|
||||||
|
@ -191,7 +194,7 @@ class Downloader(
|
||||||
fun clearQueue() {
|
fun clearQueue() {
|
||||||
destroySubscription()
|
destroySubscription()
|
||||||
|
|
||||||
queue.clear()
|
_clearQueue()
|
||||||
notifier.dismissProgress()
|
notifier.dismissProgress()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -250,7 +253,7 @@ class Downloader(
|
||||||
}
|
}
|
||||||
|
|
||||||
val source = sourceManager.get(manga.source) as? HttpSource ?: return@launchIO
|
val source = sourceManager.get(manga.source) as? HttpSource ?: return@launchIO
|
||||||
val wasEmpty = queue.isEmpty()
|
val wasEmpty = queueState.value.isEmpty()
|
||||||
// Called in background thread, the operation can be slow with SAF.
|
// Called in background thread, the operation can be slow with SAF.
|
||||||
val chaptersWithoutDir = async {
|
val chaptersWithoutDir = async {
|
||||||
chapters
|
chapters
|
||||||
|
@ -263,12 +266,12 @@ class Downloader(
|
||||||
// Runs in main thread (synchronization needed).
|
// Runs in main thread (synchronization needed).
|
||||||
val chaptersToQueue = chaptersWithoutDir.await()
|
val chaptersToQueue = chaptersWithoutDir.await()
|
||||||
// Filter out those already enqueued.
|
// Filter out those already enqueued.
|
||||||
.filter { chapter -> queue.none { it.chapter.id == chapter.id } }
|
.filter { chapter -> queueState.value.none { it: Download -> it.chapter.id == chapter.id } }
|
||||||
// Create a download for each one.
|
// Create a download for each one.
|
||||||
.map { Download(source, manga, it) }
|
.map { Download(source, manga, it) }
|
||||||
|
|
||||||
if (chaptersToQueue.isNotEmpty()) {
|
if (chaptersToQueue.isNotEmpty()) {
|
||||||
queue.addAll(chaptersToQueue)
|
addAllToQueue(chaptersToQueue)
|
||||||
|
|
||||||
if (isRunning) {
|
if (isRunning) {
|
||||||
// Send the list of downloads to the downloader.
|
// Send the list of downloads to the downloader.
|
||||||
|
@ -277,8 +280,8 @@ class Downloader(
|
||||||
|
|
||||||
// Start downloader if needed
|
// Start downloader if needed
|
||||||
if (autoStart && wasEmpty) {
|
if (autoStart && wasEmpty) {
|
||||||
val queuedDownloads = queue.count { it.source !is UnmeteredSource }
|
val queuedDownloads = queueState.value.count { it: Download -> it.source !is UnmeteredSource }
|
||||||
val maxDownloadsFromSource = queue
|
val maxDownloadsFromSource = queueState.value
|
||||||
.groupBy { it.source }
|
.groupBy { it.source }
|
||||||
.filterKeys { it !is UnmeteredSource }
|
.filterKeys { it !is UnmeteredSource }
|
||||||
.maxOfOrNull { it.value.size }
|
.maxOfOrNull { it.value.size }
|
||||||
|
@ -636,7 +639,7 @@ class Downloader(
|
||||||
// Delete successful downloads from queue
|
// Delete successful downloads from queue
|
||||||
if (download.status == Download.State.DOWNLOADED) {
|
if (download.status == Download.State.DOWNLOADED) {
|
||||||
// Remove downloaded chapter from queue
|
// Remove downloaded chapter from queue
|
||||||
queue.remove(download)
|
removeFromQueue(download)
|
||||||
}
|
}
|
||||||
if (areAllDownloadsFinished()) {
|
if (areAllDownloadsFinished()) {
|
||||||
stop()
|
stop()
|
||||||
|
@ -647,7 +650,67 @@ class Downloader(
|
||||||
* Returns true if all the queued downloads are in DOWNLOADED or ERROR state.
|
* Returns true if all the queued downloads are in DOWNLOADED or ERROR state.
|
||||||
*/
|
*/
|
||||||
private fun areAllDownloadsFinished(): Boolean {
|
private fun areAllDownloadsFinished(): Boolean {
|
||||||
return queue.none { it.status.value <= Download.State.DOWNLOADING.value }
|
return queueState.value.none { it: Download -> it.status.value <= Download.State.DOWNLOADING.value }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun addAllToQueue(downloads: List<Download>) {
|
||||||
|
_queueState.update {
|
||||||
|
downloads.forEach { download ->
|
||||||
|
download.status = Download.State.QUEUE
|
||||||
|
}
|
||||||
|
store.addAll(downloads)
|
||||||
|
it + downloads
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeFromQueue(download: Download) {
|
||||||
|
_queueState.update {
|
||||||
|
store.remove(download)
|
||||||
|
if (download.status == Download.State.DOWNLOADING || download.status == Download.State.QUEUE) {
|
||||||
|
download.status = Download.State.NOT_DOWNLOADED
|
||||||
|
}
|
||||||
|
it - download
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeFromQueue(chapters: List<Chapter>) {
|
||||||
|
chapters.forEach { chapter ->
|
||||||
|
queueState.value.find { it.chapter.id == chapter.id }?.let { removeFromQueue(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun removeFromQueue(manga: Manga) {
|
||||||
|
queueState.value.filter { it.manga.id == manga.id }.forEach { removeFromQueue(it) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun _clearQueue() {
|
||||||
|
_queueState.update {
|
||||||
|
it.forEach { download ->
|
||||||
|
if (download.status == Download.State.DOWNLOADING || download.status == Download.State.QUEUE) {
|
||||||
|
download.status = Download.State.NOT_DOWNLOADED
|
||||||
|
}
|
||||||
|
}
|
||||||
|
store.clear()
|
||||||
|
emptyList()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateQueue(downloads: List<Download>) {
|
||||||
|
val wasRunning = isRunning
|
||||||
|
|
||||||
|
if (downloads.isEmpty()) {
|
||||||
|
clearQueue()
|
||||||
|
stop()
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
pause()
|
||||||
|
_clearQueue()
|
||||||
|
addAllToQueue(downloads)
|
||||||
|
|
||||||
|
if (wasRunning) {
|
||||||
|
start()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
|
|
|
@ -1,99 +0,0 @@
|
||||||
package eu.kanade.tachiyomi.data.download.model
|
|
||||||
|
|
||||||
import eu.kanade.tachiyomi.data.download.DownloadStore
|
|
||||||
import kotlinx.coroutines.flow.Flow
|
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
|
||||||
import kotlinx.coroutines.flow.asFlow
|
|
||||||
import kotlinx.coroutines.flow.asStateFlow
|
|
||||||
import kotlinx.coroutines.flow.drop
|
|
||||||
import kotlinx.coroutines.flow.emitAll
|
|
||||||
import kotlinx.coroutines.flow.flatMapLatest
|
|
||||||
import kotlinx.coroutines.flow.map
|
|
||||||
import kotlinx.coroutines.flow.merge
|
|
||||||
import kotlinx.coroutines.flow.onStart
|
|
||||||
import kotlinx.coroutines.flow.update
|
|
||||||
import tachiyomi.domain.chapter.model.Chapter
|
|
||||||
import tachiyomi.domain.manga.model.Manga
|
|
||||||
|
|
||||||
class DownloadQueue(
|
|
||||||
private val store: DownloadStore,
|
|
||||||
) {
|
|
||||||
private val _state = MutableStateFlow<List<Download>>(emptyList())
|
|
||||||
val state = _state.asStateFlow()
|
|
||||||
|
|
||||||
fun addAll(downloads: List<Download>) {
|
|
||||||
_state.update {
|
|
||||||
downloads.forEach { download ->
|
|
||||||
download.status = Download.State.QUEUE
|
|
||||||
}
|
|
||||||
store.addAll(downloads)
|
|
||||||
it + downloads
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove(download: Download) {
|
|
||||||
_state.update {
|
|
||||||
store.remove(download)
|
|
||||||
if (download.status == Download.State.DOWNLOADING || download.status == Download.State.QUEUE) {
|
|
||||||
download.status = Download.State.NOT_DOWNLOADED
|
|
||||||
}
|
|
||||||
it - download
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove(chapter: Chapter) {
|
|
||||||
_state.value.find { it.chapter.id == chapter.id }?.let { remove(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove(chapters: List<Chapter>) {
|
|
||||||
chapters.forEach(::remove)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun remove(manga: Manga) {
|
|
||||||
_state.value.filter { it.manga.id == manga.id }.forEach { remove(it) }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun clear() {
|
|
||||||
_state.update {
|
|
||||||
it.forEach { download ->
|
|
||||||
if (download.status == Download.State.DOWNLOADING || download.status == Download.State.QUEUE) {
|
|
||||||
download.status = Download.State.NOT_DOWNLOADED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
store.clear()
|
|
||||||
emptyList()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun statusFlow(): Flow<Download> = state
|
|
||||||
.flatMapLatest { downloads ->
|
|
||||||
downloads
|
|
||||||
.map { download ->
|
|
||||||
download.statusFlow.drop(1).map { download }
|
|
||||||
}
|
|
||||||
.merge()
|
|
||||||
}
|
|
||||||
.onStart { emitAll(getActiveDownloads()) }
|
|
||||||
|
|
||||||
fun progressFlow(): Flow<Download> = state
|
|
||||||
.flatMapLatest { downloads ->
|
|
||||||
downloads
|
|
||||||
.map { download ->
|
|
||||||
download.progressFlow.drop(1).map { download }
|
|
||||||
}
|
|
||||||
.merge()
|
|
||||||
}
|
|
||||||
.onStart { emitAll(getActiveDownloads()) }
|
|
||||||
|
|
||||||
private fun getActiveDownloads(): Flow<Download> =
|
|
||||||
_state.value.filter { download -> download.status == Download.State.DOWNLOADING }.asFlow()
|
|
||||||
|
|
||||||
fun count(predicate: (Download) -> Boolean) = _state.value.count(predicate)
|
|
||||||
fun filter(predicate: (Download) -> Boolean) = _state.value.filter(predicate)
|
|
||||||
fun find(predicate: (Download) -> Boolean) = _state.value.find(predicate)
|
|
||||||
fun <K> groupBy(keySelector: (Download) -> K) = _state.value.groupBy(keySelector)
|
|
||||||
fun isEmpty() = _state.value.isEmpty()
|
|
||||||
fun isNotEmpty() = _state.value.isNotEmpty()
|
|
||||||
fun none(predicate: (Download) -> Boolean) = _state.value.none(predicate)
|
|
||||||
fun toMutableList() = _state.value.toMutableList()
|
|
||||||
}
|
|
|
@ -111,7 +111,7 @@ class DownloadQueueScreenModel(
|
||||||
|
|
||||||
init {
|
init {
|
||||||
coroutineScope.launch {
|
coroutineScope.launch {
|
||||||
downloadManager.queue.state
|
downloadManager.queueState
|
||||||
.map { downloads ->
|
.map { downloads ->
|
||||||
downloads
|
downloads
|
||||||
.groupBy { it.source }
|
.groupBy { it.source }
|
||||||
|
@ -136,8 +136,8 @@ class DownloadQueueScreenModel(
|
||||||
val isDownloaderRunning
|
val isDownloaderRunning
|
||||||
get() = downloadManager.isDownloaderRunning
|
get() = downloadManager.isDownloaderRunning
|
||||||
|
|
||||||
fun getDownloadStatusFlow() = downloadManager.queue.statusFlow()
|
fun getDownloadStatusFlow() = downloadManager.statusFlow()
|
||||||
fun getDownloadProgressFlow() = downloadManager.queue.progressFlow()
|
fun getDownloadProgressFlow() = downloadManager.progressFlow()
|
||||||
|
|
||||||
fun startDownloads() {
|
fun startDownloads() {
|
||||||
downloadManager.startDownloads()
|
downloadManager.startDownloads()
|
||||||
|
|
|
@ -427,7 +427,7 @@ class MangaInfoScreenModel(
|
||||||
|
|
||||||
private fun observeDownloads() {
|
private fun observeDownloads() {
|
||||||
coroutineScope.launchIO {
|
coroutineScope.launchIO {
|
||||||
downloadManager.queue.statusFlow()
|
downloadManager.statusFlow()
|
||||||
.filter { it.manga.id == successState?.manga?.id }
|
.filter { it.manga.id == successState?.manga?.id }
|
||||||
.catch { error -> logcat(LogPriority.ERROR, error) }
|
.catch { error -> logcat(LogPriority.ERROR, error) }
|
||||||
.collect {
|
.collect {
|
||||||
|
@ -438,7 +438,7 @@ class MangaInfoScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
coroutineScope.launchIO {
|
coroutineScope.launchIO {
|
||||||
downloadManager.queue.progressFlow()
|
downloadManager.progressFlow()
|
||||||
.filter { it.manga.id == successState?.manga?.id }
|
.filter { it.manga.id == successState?.manga?.id }
|
||||||
.catch { error -> logcat(LogPriority.ERROR, error) }
|
.catch { error -> logcat(LogPriority.ERROR, error) }
|
||||||
.collect {
|
.collect {
|
||||||
|
|
|
@ -94,7 +94,7 @@ private class MoreScreenModel(
|
||||||
coroutineScope.launchIO {
|
coroutineScope.launchIO {
|
||||||
combine(
|
combine(
|
||||||
downloadManager.isDownloaderRunning,
|
downloadManager.isDownloaderRunning,
|
||||||
downloadManager.queue.state,
|
downloadManager.queueState,
|
||||||
) { isRunning, downloadQueue -> Pair(isRunning, downloadQueue.size) }
|
) { isRunning, downloadQueue -> Pair(isRunning, downloadQueue.size) }
|
||||||
.collectLatest { (isDownloading, downloadQueueSize) ->
|
.collectLatest { (isDownloading, downloadQueueSize) ->
|
||||||
val pendingDownloadExists = downloadQueueSize != 0
|
val pendingDownloadExists = downloadQueueSize != 0
|
||||||
|
|
|
@ -99,7 +99,7 @@ class UpdatesScreenModel(
|
||||||
}
|
}
|
||||||
|
|
||||||
coroutineScope.launchIO {
|
coroutineScope.launchIO {
|
||||||
merge(downloadManager.queue.statusFlow(), downloadManager.queue.progressFlow())
|
merge(downloadManager.statusFlow(), downloadManager.progressFlow())
|
||||||
.catch { logcat(LogPriority.ERROR, it) }
|
.catch { logcat(LogPriority.ERROR, it) }
|
||||||
.collect(this@UpdatesScreenModel::updateDownloadState)
|
.collect(this@UpdatesScreenModel::updateDownloadState)
|
||||||
}
|
}
|
||||||
|
|
Reference in a new issue