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