Fix manga downloading
parent
8b5a985842
commit
de46cfe7ee
@ -0,0 +1,25 @@
|
||||
package org.koitharu.kotatsu.core.model
|
||||
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
fun Manga.withoutChapters() = if (chapters.isNullOrEmpty()) {
|
||||
this
|
||||
} else {
|
||||
Manga(
|
||||
id = id,
|
||||
title = title,
|
||||
altTitle = altTitle,
|
||||
url = url,
|
||||
publicUrl = publicUrl,
|
||||
rating = rating,
|
||||
isNsfw = isNsfw,
|
||||
coverUrl = coverUrl,
|
||||
tags = tags,
|
||||
state = state,
|
||||
author = author,
|
||||
largeCoverUrl = largeCoverUrl,
|
||||
description = description,
|
||||
chapters = null,
|
||||
source = source,
|
||||
)
|
||||
}
|
||||
@ -0,0 +1,251 @@
|
||||
package org.koitharu.kotatsu.download.domain
|
||||
|
||||
import android.graphics.drawable.Drawable
|
||||
import org.koitharu.kotatsu.parsers.model.Manga
|
||||
|
||||
sealed interface DownloadState {
|
||||
|
||||
val startId: Int
|
||||
val manga: Manga
|
||||
val cover: Drawable?
|
||||
|
||||
class Queued(
|
||||
override val startId: Int,
|
||||
override val manga: Manga,
|
||||
override val cover: Drawable?,
|
||||
) : DownloadState {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Queued
|
||||
|
||||
if (startId != other.startId) return false
|
||||
if (manga != other.manga) return false
|
||||
if (cover != other.cover) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = startId
|
||||
result = 31 * result + manga.hashCode()
|
||||
result = 31 * result + (cover?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class Preparing(
|
||||
override val startId: Int,
|
||||
override val manga: Manga,
|
||||
override val cover: Drawable?,
|
||||
) : DownloadState {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Preparing
|
||||
|
||||
if (startId != other.startId) return false
|
||||
if (manga != other.manga) return false
|
||||
if (cover != other.cover) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = startId
|
||||
result = 31 * result + manga.hashCode()
|
||||
result = 31 * result + (cover?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class Progress(
|
||||
override val startId: Int,
|
||||
override val manga: Manga,
|
||||
override val cover: Drawable?,
|
||||
val totalChapters: Int,
|
||||
val currentChapter: Int,
|
||||
val totalPages: Int,
|
||||
val currentPage: Int,
|
||||
) : DownloadState {
|
||||
|
||||
val max: Int = totalChapters * totalPages
|
||||
|
||||
val progress: Int = totalPages * currentChapter + currentPage + 1
|
||||
|
||||
val percent: Float = progress.toFloat() / max
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Progress
|
||||
|
||||
if (startId != other.startId) return false
|
||||
if (manga != other.manga) return false
|
||||
if (cover != other.cover) return false
|
||||
if (totalChapters != other.totalChapters) return false
|
||||
if (currentChapter != other.currentChapter) return false
|
||||
if (totalPages != other.totalPages) return false
|
||||
if (currentPage != other.currentPage) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = startId
|
||||
result = 31 * result + manga.hashCode()
|
||||
result = 31 * result + (cover?.hashCode() ?: 0)
|
||||
result = 31 * result + totalChapters
|
||||
result = 31 * result + currentChapter
|
||||
result = 31 * result + totalPages
|
||||
result = 31 * result + currentPage
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class WaitingForNetwork(
|
||||
override val startId: Int,
|
||||
override val manga: Manga,
|
||||
override val cover: Drawable?,
|
||||
) : DownloadState {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as WaitingForNetwork
|
||||
|
||||
if (startId != other.startId) return false
|
||||
if (manga != other.manga) return false
|
||||
if (cover != other.cover) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = startId
|
||||
result = 31 * result + manga.hashCode()
|
||||
result = 31 * result + (cover?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class Done(
|
||||
override val startId: Int,
|
||||
override val manga: Manga,
|
||||
override val cover: Drawable?,
|
||||
val localManga: Manga,
|
||||
) : DownloadState {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Done
|
||||
|
||||
if (startId != other.startId) return false
|
||||
if (manga != other.manga) return false
|
||||
if (cover != other.cover) return false
|
||||
if (localManga != other.localManga) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = startId
|
||||
result = 31 * result + manga.hashCode()
|
||||
result = 31 * result + (cover?.hashCode() ?: 0)
|
||||
result = 31 * result + localManga.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class Error(
|
||||
override val startId: Int,
|
||||
override val manga: Manga,
|
||||
override val cover: Drawable?,
|
||||
val error: Throwable,
|
||||
) : DownloadState {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Error
|
||||
|
||||
if (startId != other.startId) return false
|
||||
if (manga != other.manga) return false
|
||||
if (cover != other.cover) return false
|
||||
if (error != other.error) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = startId
|
||||
result = 31 * result + manga.hashCode()
|
||||
result = 31 * result + (cover?.hashCode() ?: 0)
|
||||
result = 31 * result + error.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class Cancelled(
|
||||
override val startId: Int,
|
||||
override val manga: Manga,
|
||||
override val cover: Drawable?,
|
||||
) : DownloadState {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as Cancelled
|
||||
|
||||
if (startId != other.startId) return false
|
||||
if (manga != other.manga) return false
|
||||
if (cover != other.cover) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = startId
|
||||
result = 31 * result + manga.hashCode()
|
||||
result = 31 * result + (cover?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
class PostProcessing(
|
||||
override val startId: Int,
|
||||
override val manga: Manga,
|
||||
override val cover: Drawable?,
|
||||
) : DownloadState {
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as PostProcessing
|
||||
|
||||
if (startId != other.startId) return false
|
||||
if (manga != other.manga) return false
|
||||
if (cover != other.cover) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = startId
|
||||
result = 31 * result + manga.hashCode()
|
||||
result = 31 * result + (cover?.hashCode() ?: 0)
|
||||
return result
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,25 @@
|
||||
package org.koitharu.kotatsu.download.domain
|
||||
|
||||
import android.os.PowerManager
|
||||
import kotlin.coroutines.AbstractCoroutineContextElement
|
||||
import kotlin.coroutines.CoroutineContext
|
||||
|
||||
class WakeLockNode(
|
||||
private val wakeLock: PowerManager.WakeLock,
|
||||
private val timeout: Long,
|
||||
) : AbstractCoroutineContextElement(Key) {
|
||||
|
||||
init {
|
||||
wakeLock.setReferenceCounted(true)
|
||||
}
|
||||
|
||||
fun acquire() {
|
||||
wakeLock.acquire(timeout)
|
||||
}
|
||||
|
||||
fun release() {
|
||||
wakeLock.release()
|
||||
}
|
||||
|
||||
companion object Key : CoroutineContext.Key<WakeLockNode>
|
||||
}
|
||||
@ -0,0 +1,71 @@
|
||||
package org.koitharu.kotatsu.download.ui.service
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.NotificationManager
|
||||
import android.app.Service
|
||||
import android.content.Context
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.util.SparseArray
|
||||
import androidx.core.app.ServiceCompat
|
||||
import androidx.core.util.isEmpty
|
||||
import androidx.core.util.size
|
||||
|
||||
private const val DEFAULT_DELAY = 500L
|
||||
|
||||
class ForegroundNotificationSwitcher(
|
||||
private val service: Service,
|
||||
) {
|
||||
|
||||
private val notificationManager = service.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
private val notifications = SparseArray<Notification>()
|
||||
private val handler = Handler(Looper.getMainLooper())
|
||||
|
||||
@Synchronized
|
||||
fun notify(startId: Int, notification: Notification) {
|
||||
if (notifications.isEmpty()) {
|
||||
handler.postDelayed(StartForegroundRunnable(startId, notification), DEFAULT_DELAY)
|
||||
}
|
||||
notificationManager.notify(startId, notification)
|
||||
notifications[startId] = notification
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun detach(startId: Int, notification: Notification?) {
|
||||
notifications.remove(startId)
|
||||
if (notifications.isEmpty()) {
|
||||
ServiceCompat.stopForeground(service, ServiceCompat.STOP_FOREGROUND_DETACH)
|
||||
}
|
||||
val nextIndex = notifications.size - 1
|
||||
if (nextIndex >= 0) {
|
||||
val nextStartId = notifications.keyAt(nextIndex)
|
||||
val nextNotification = notifications.valueAt(nextIndex)
|
||||
service.startForeground(nextStartId, nextNotification)
|
||||
}
|
||||
handler.postDelayed(NotifyRunnable(startId, notification), DEFAULT_DELAY)
|
||||
}
|
||||
|
||||
private inner class StartForegroundRunnable(
|
||||
private val startId: Int,
|
||||
private val notification: Notification,
|
||||
) : Runnable {
|
||||
|
||||
override fun run() {
|
||||
service.startForeground(startId, notification)
|
||||
}
|
||||
}
|
||||
|
||||
private inner class NotifyRunnable(
|
||||
private val startId: Int,
|
||||
private val notification: Notification?,
|
||||
) : Runnable {
|
||||
|
||||
override fun run() {
|
||||
if (notification != null) {
|
||||
notificationManager.notify(startId, notification)
|
||||
} else {
|
||||
notificationManager.cancel(startId)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue