Clean up download state logic in MorePresenter

This commit is contained in:
arkon 2022-10-09 10:35:30 -04:00
parent bd9a08c73d
commit 3bfbd58402
7 changed files with 46 additions and 120 deletions

View File

@ -8,11 +8,9 @@ import android.os.IBinder
import android.os.PowerManager import android.os.PowerManager
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.core.content.ContextCompat import androidx.core.content.ContextCompat
import com.jakewharton.rxrelay.BehaviorRelay
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.notification.Notifications import eu.kanade.tachiyomi.data.notification.Notifications
import eu.kanade.tachiyomi.util.lang.plusAssign
import eu.kanade.tachiyomi.util.lang.withUIContext import eu.kanade.tachiyomi.util.lang.withUIContext
import eu.kanade.tachiyomi.util.system.acquireWakeLock import eu.kanade.tachiyomi.util.system.acquireWakeLock
import eu.kanade.tachiyomi.util.system.isConnectedToWifi import eu.kanade.tachiyomi.util.system.isConnectedToWifi
@ -32,7 +30,6 @@ import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import logcat.LogPriority import logcat.LogPriority
import ru.beryukhov.reactivenetwork.ReactiveNetwork import ru.beryukhov.reactivenetwork.ReactiveNetwork
import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.injectLazy import uy.kohesive.injekt.injectLazy
/** /**
@ -44,11 +41,6 @@ class DownloadService : Service() {
companion object { companion object {
/**
* Relay used to know when the service is running.
*/
val runningRelay: BehaviorRelay<Boolean> = BehaviorRelay.create(false)
private val _isRunning = MutableStateFlow(false) private val _isRunning = MutableStateFlow(false)
val isRunning = _isRunning.asStateFlow() val isRunning = _isRunning.asStateFlow()
@ -83,7 +75,6 @@ class DownloadService : Service() {
} }
private val downloadManager: DownloadManager by injectLazy() private val downloadManager: DownloadManager by injectLazy()
private val downloadPreferences: DownloadPreferences by injectLazy() private val downloadPreferences: DownloadPreferences by injectLazy()
/** /**
@ -91,62 +82,58 @@ class DownloadService : Service() {
*/ */
private lateinit var wakeLock: PowerManager.WakeLock private lateinit var wakeLock: PowerManager.WakeLock
private lateinit var subscriptions: CompositeSubscription
private lateinit var ioScope: CoroutineScope private lateinit var ioScope: CoroutineScope
/**
* Called when the service is created.
*/
override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) ioScope = CoroutineScope(SupervisorJob() + Dispatchers.IO)
startForeground(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS, getPlaceholderNotification()) startForeground(Notifications.ID_DOWNLOAD_CHAPTER_PROGRESS, getPlaceholderNotification())
wakeLock = acquireWakeLock(javaClass.name) wakeLock = acquireWakeLock(javaClass.name)
runningRelay.call(true)
_isRunning.value = true _isRunning.value = true
subscriptions = CompositeSubscription()
listenDownloaderState() listenDownloaderState()
listenNetworkChanges() listenNetworkChanges()
} }
/**
* Called when the service is destroyed.
*/
override fun onDestroy() { override fun onDestroy() {
ioScope?.cancel() ioScope?.cancel()
runningRelay.call(false)
_isRunning.value = false _isRunning.value = false
subscriptions.unsubscribe()
downloadManager.stopDownloads() downloadManager.stopDownloads()
wakeLock.releaseIfNeeded() wakeLock.releaseIfNeeded()
super.onDestroy() super.onDestroy()
} }
/** // Not used
* Not used.
*/
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_NOT_STICKY return START_NOT_STICKY
} }
/** // Not used
* Not used.
*/
override fun onBind(intent: Intent): IBinder? { override fun onBind(intent: Intent): IBinder? {
return null return null
} }
private fun stopDownloads(@StringRes string: Int) {
downloadManager.stopDownloads(getString(string))
}
/** /**
* Listens to network changes. * Listens to network changes.
*
* @see onNetworkStateChanged
*/ */
private fun listenNetworkChanges() { private fun listenNetworkChanges() {
ReactiveNetwork() ReactiveNetwork()
.observeNetworkConnectivity(applicationContext) .observeNetworkConnectivity(applicationContext)
.onEach { .onEach {
withUIContext { withUIContext {
onNetworkStateChanged() if (isOnline()) {
if (downloadPreferences.downloadOnlyOverWifi().get() && !isConnectedToWifi()) {
stopDownloads(R.string.download_notifier_text_only_wifi)
} else {
val started = downloadManager.startDownloads()
if (!started) stopSelf()
}
} else {
stopDownloads(R.string.download_notifier_no_network)
}
} }
} }
.catch { error -> .catch { error ->
@ -159,41 +146,20 @@ class DownloadService : Service() {
.launchIn(ioScope) .launchIn(ioScope)
} }
/**
* Called when the network state changes.
*/
private fun onNetworkStateChanged() {
if (isOnline()) {
if (downloadPreferences.downloadOnlyOverWifi().get() && !isConnectedToWifi()) {
stopDownloads(R.string.download_notifier_text_only_wifi)
} else {
val started = downloadManager.startDownloads()
if (!started) stopSelf()
}
} else {
stopDownloads(R.string.download_notifier_no_network)
}
}
private fun stopDownloads(@StringRes string: Int) {
downloadManager.stopDownloads(getString(string))
}
/** /**
* Listens to downloader status. Enables or disables the wake lock depending on the status. * Listens to downloader status. Enables or disables the wake lock depending on the status.
*/ */
private fun listenDownloaderState() { private fun listenDownloaderState() {
subscriptions += downloadManager.runningRelay _isRunning
.doOnError { .onEach { isRunning ->
/* Swallow wakelock error */ if (isRunning) {
}
.subscribe { running ->
if (running) {
wakeLock.acquireIfNeeded() wakeLock.acquireIfNeeded()
} else { } else {
wakeLock.releaseIfNeeded() wakeLock.releaseIfNeeded()
} }
} }
.catch { /* Ignore errors */ }
.launchIn(ioScope)
} }
/** /**

View File

@ -75,13 +75,14 @@ class DownloadQueue(
Observable.from(this).filter { download -> download.status == Download.State.DOWNLOADING } Observable.from(this).filter { download -> download.status == Download.State.DOWNLOADING }
@Deprecated("Use getStatusAsFlow instead") @Deprecated("Use getStatusAsFlow instead")
fun getStatusObservable(): Observable<Download> = statusSubject private fun getStatusObservable(): Observable<Download> = statusSubject
.startWith(getActiveDownloads()) .startWith(getActiveDownloads())
.onBackpressureBuffer() .onBackpressureBuffer()
fun getStatusAsFlow(): Flow<Download> = getStatusObservable().asFlow() fun getStatusAsFlow(): Flow<Download> = getStatusObservable().asFlow()
fun getUpdatedObservable(): Observable<List<Download>> = updatedRelay.onBackpressureBuffer() @Deprecated("Use getUpdatedAsFlow instead")
private fun getUpdatedObservable(): Observable<List<Download>> = updatedRelay.onBackpressureBuffer()
.startWith(Unit) .startWith(Unit)
.map { this } .map { this }
@ -94,7 +95,7 @@ class DownloadQueue(
} }
@Deprecated("Use getProgressAsFlow instead") @Deprecated("Use getProgressAsFlow instead")
fun getProgressObservable(): Observable<Download> { private fun getProgressObservable(): Observable<Download> {
return statusSubject.onBackpressureBuffer() return statusSubject.onBackpressureBuffer()
.startWith(getActiveDownloads()) .startWith(getActiveDownloads())
.flatMap { download -> .flatMap { download ->
@ -113,9 +114,7 @@ class DownloadQueue(
.filter { it.status == Download.State.DOWNLOADING } .filter { it.status == Download.State.DOWNLOADING }
} }
fun getProgressAsFlow(): Flow<Download> { fun getProgressAsFlow(): Flow<Download> = getProgressObservable().asFlow()
return getProgressObservable().asFlow()
}
private fun setPagesSubject(pages: List<Page>?, subject: PublishSubject<Int>?) { private fun setPagesSubject(pages: List<Page>?, subject: PublishSubject<Int>?) {
pages?.forEach { it.setStatusSubject(subject) } pages?.forEach { it.setStatusSubject(subject) }

View File

@ -11,7 +11,6 @@ import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.update import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch

View File

@ -46,7 +46,6 @@ import eu.kanade.tachiyomi.source.SourceManager
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.base.presenter.BasePresenter import eu.kanade.tachiyomi.ui.base.presenter.BasePresenter
import eu.kanade.tachiyomi.util.lang.combineLatest
import eu.kanade.tachiyomi.util.lang.launchIO import eu.kanade.tachiyomi.util.lang.launchIO
import eu.kanade.tachiyomi.util.lang.launchNonCancellable import eu.kanade.tachiyomi.util.lang.launchNonCancellable
import eu.kanade.tachiyomi.util.removeCovers import eu.kanade.tachiyomi.util.removeCovers
@ -687,6 +686,10 @@ class LibraryPresenter(
state.selection = items.filterNot { it in selection } state.selection = items.filterNot { it in selection }
} }
private fun <T, U, R> Observable<T>.combineLatest(o2: Observable<U>, combineFn: (T, U) -> R): Observable<R> {
return Observable.combineLatest(this, o2, combineFn)
}
sealed class Dialog { sealed class Dialog {
data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog() data class ChangeCategory(val manga: List<Manga>, val initialSelection: List<CheckboxState<Category>>) : Dialog()
data class DeleteManga(val manga: List<Manga>) : Dialog() data class DeleteManga(val manga: List<Manga>) : Dialog()

View File

@ -9,17 +9,14 @@ import eu.kanade.tachiyomi.util.lang.launchIO
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import rx.Observable import kotlinx.coroutines.flow.collectLatest
import rx.Subscription import kotlinx.coroutines.flow.combine
import rx.android.schedulers.AndroidSchedulers
import rx.subscriptions.CompositeSubscription
import uy.kohesive.injekt.Injekt import uy.kohesive.injekt.Injekt
import uy.kohesive.injekt.api.get import uy.kohesive.injekt.api.get
class MorePresenter( class MorePresenter(
private val downloadManager: DownloadManager = Injekt.get(), private val downloadManager: DownloadManager = Injekt.get(),
preferences: BasePreferences = Injekt.get(), preferences: BasePreferences = Injekt.get(),
) : BasePresenter<MoreController>() { ) : BasePresenter<MoreController>() {
val downloadedOnly = preferences.downloadedOnly().asState() val downloadedOnly = preferences.downloadedOnly().asState()
@ -28,58 +25,26 @@ class MorePresenter(
private var _state: MutableStateFlow<DownloadQueueState> = MutableStateFlow(DownloadQueueState.Stopped) private var _state: MutableStateFlow<DownloadQueueState> = MutableStateFlow(DownloadQueueState.Stopped)
val downloadQueueState: StateFlow<DownloadQueueState> = _state.asStateFlow() val downloadQueueState: StateFlow<DownloadQueueState> = _state.asStateFlow()
private var isDownloading: Boolean = false
private var downloadQueueSize: Int = 0
private var untilDestroySubscriptions = CompositeSubscription()
override fun onCreate(savedState: Bundle?) { override fun onCreate(savedState: Bundle?) {
super.onCreate(savedState) super.onCreate(savedState)
if (untilDestroySubscriptions.isUnsubscribed) { // Handle running/paused status change and queue progress updating
untilDestroySubscriptions = CompositeSubscription()
}
initDownloadQueueSummary()
}
override fun onDestroy() {
super.onDestroy()
untilDestroySubscriptions.unsubscribe()
}
private fun initDownloadQueueSummary() {
// Handle running/paused status change
DownloadService.runningRelay
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy { isRunning ->
isDownloading = isRunning
updateDownloadQueueState()
}
// Handle queue progress updating
downloadManager.queue.getUpdatedObservable()
.observeOn(AndroidSchedulers.mainThread())
.subscribeUntilDestroy {
downloadQueueSize = it.size
updateDownloadQueueState()
}
}
private fun updateDownloadQueueState() {
presenterScope.launchIO { presenterScope.launchIO {
val pendingDownloadExists = downloadQueueSize != 0 combine(
_state.value = when { DownloadService.isRunning,
!pendingDownloadExists -> DownloadQueueState.Stopped downloadManager.queue.getUpdatedAsFlow(),
!isDownloading && !pendingDownloadExists -> DownloadQueueState.Paused(0) ) { isRunning, downloadQueue -> Pair(isRunning, downloadQueue.size) }
!isDownloading && pendingDownloadExists -> DownloadQueueState.Paused(downloadQueueSize) .collectLatest { (isDownloading, downloadQueueSize) ->
else -> DownloadQueueState.Downloading(downloadQueueSize) val pendingDownloadExists = downloadQueueSize != 0
} _state.value = when {
!pendingDownloadExists -> DownloadQueueState.Stopped
!isDownloading && !pendingDownloadExists -> DownloadQueueState.Paused(0)
!isDownloading && pendingDownloadExists -> DownloadQueueState.Paused(downloadQueueSize)
else -> DownloadQueueState.Downloading(downloadQueueSize)
}
}
} }
} }
private fun <T> Observable<T>.subscribeUntilDestroy(onNext: (T) -> Unit): Subscription {
return subscribe(onNext).also { untilDestroySubscriptions.add(it) }
}
} }
sealed class DownloadQueueState { sealed class DownloadQueueState {

View File

@ -33,7 +33,6 @@ import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.catch import kotlinx.coroutines.flow.catch
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.receiveAsFlow import kotlinx.coroutines.flow.receiveAsFlow
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import logcat.LogPriority import logcat.LogPriority

View File

@ -1,11 +1,6 @@
package eu.kanade.tachiyomi.util.lang package eu.kanade.tachiyomi.util.lang
import rx.Observable
import rx.Subscription import rx.Subscription
import rx.subscriptions.CompositeSubscription import rx.subscriptions.CompositeSubscription
operator fun CompositeSubscription.plusAssign(subscription: Subscription) = add(subscription) operator fun CompositeSubscription.plusAssign(subscription: Subscription) = add(subscription)
fun <T, U, R> Observable<T>.combineLatest(o2: Observable<U>, combineFn: (T, U) -> R): Observable<R> {
return Observable.combineLatest(this, o2, combineFn)
}