pull/1352/head
gedoor 3 years ago
parent c4e51aaf9a
commit a5c6e6f7dc
  1. 433
      app/src/main/java/io/legado/app/model/CacheBook.kt

@ -19,269 +19,270 @@ import kotlinx.coroutines.delay
import java.util.concurrent.ConcurrentHashMap import java.util.concurrent.ConcurrentHashMap
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
class CacheBook(var bookSource: BookSource, var book: Book) { object CacheBook {
companion object { val cacheBookMap = ConcurrentHashMap<String, CacheBookModel>()
val cacheBookMap = ConcurrentHashMap<String, CacheBook>() @Synchronized
fun getOrCreate(bookUrl: String): CacheBookModel? {
@Synchronized val book = appDb.bookDao.getBook(bookUrl) ?: return null
fun getOrCreate(bookUrl: String): CacheBook? { val bookSource = appDb.bookSourceDao.getBookSource(book.origin) ?: return null
val book = appDb.bookDao.getBook(bookUrl) ?: return null var cacheBook = cacheBookMap[bookUrl]
val bookSource = appDb.bookSourceDao.getBookSource(book.origin) ?: return null if (cacheBook != null) {
var cacheBook = cacheBookMap[bookUrl] //存在时更新,书源可能会变化,必须更新
if (cacheBook != null) { cacheBook.bookSource = bookSource
//存在时更新,书源可能会变化,必须更新 cacheBook.book = book
cacheBook.bookSource = bookSource
cacheBook.book = book
return cacheBook
}
cacheBook = CacheBook(bookSource, book)
cacheBookMap[bookUrl] = cacheBook
return cacheBook return cacheBook
} }
cacheBook = CacheBookModel(bookSource, book)
cacheBookMap[bookUrl] = cacheBook
return cacheBook
}
@Synchronized @Synchronized
fun getOrCreate(bookSource: BookSource, book: Book): CacheBook { fun getOrCreate(bookSource: BookSource, book: Book): CacheBookModel {
var cacheBook = cacheBookMap[book.bookUrl] var cacheBook = cacheBookMap[book.bookUrl]
if (cacheBook != null) { if (cacheBook != null) {
//存在时更新,书源可能会变化,必须更新 //存在时更新,书源可能会变化,必须更新
cacheBook.bookSource = bookSource cacheBook.bookSource = bookSource
cacheBook.book = book cacheBook.book = book
return cacheBook
}
cacheBook = CacheBook(bookSource, book)
cacheBookMap[book.bookUrl] = cacheBook
return cacheBook return cacheBook
} }
cacheBook = CacheBookModel(bookSource, book)
cacheBookMap[book.bookUrl] = cacheBook
return cacheBook
}
fun start(context: Context, bookUrl: String, start: Int, end: Int) { fun start(context: Context, bookUrl: String, start: Int, end: Int) {
context.startService<CacheBookService> { context.startService<CacheBookService> {
action = IntentAction.start action = IntentAction.start
putExtra("bookUrl", bookUrl) putExtra("bookUrl", bookUrl)
putExtra("start", start) putExtra("start", start)
putExtra("end", end) putExtra("end", end)
}
} }
}
fun remove(context: Context, bookUrl: String) { fun remove(context: Context, bookUrl: String) {
context.startService<CacheBookService> { context.startService<CacheBookService> {
action = IntentAction.remove action = IntentAction.remove
putExtra("bookUrl", bookUrl) putExtra("bookUrl", bookUrl)
}
} }
}
fun stop(context: Context) { fun stop(context: Context) {
context.startService<CacheBookService> { context.startService<CacheBookService> {
action = IntentAction.stop action = IntentAction.stop
}
} }
}
val downloadSummary: String val downloadSummary: String
get() { get() {
return "正在下载:${onDownloadCount}|等待中:${waitCount}|失败:${errorCount}|成功:${successCount}" return "正在下载:${onDownloadCount}|等待中:${waitCount}|失败:${errorCount}|成功:${successCount}"
} }
val isRun: Boolean val isRun: Boolean
get() { get() {
var isRun = false var isRun = false
cacheBookMap.forEach { cacheBookMap.forEach {
isRun = isRun || it.value.isRun() isRun = isRun || it.value.isRun()
}
return isRun
} }
return isRun
}
private val waitCount: Int private val waitCount: Int
get() { get() {
var count = 0 var count = 0
cacheBookMap.forEach { cacheBookMap.forEach {
count += it.value.waitCount count += it.value.waitCount
}
return count
} }
return count
}
private val successCount: Int private val successCount: Int
get() { get() {
var count = 0 var count = 0
cacheBookMap.forEach { cacheBookMap.forEach {
count += it.value.successCount count += it.value.successCount
}
return count
} }
return count
}
val onDownloadCount: Int val onDownloadCount: Int
get() { get() {
var count = 0 var count = 0
cacheBookMap.forEach { cacheBookMap.forEach {
count += it.value.onDownloadCount count += it.value.onDownloadCount
}
return count
} }
return count
}
private val errorCount: Int private val errorCount: Int
get() { get() {
var count = 0 var count = 0
cacheBookMap.forEach { cacheBookMap.forEach {
count += it.value.errorCount count += it.value.errorCount
}
return count
} }
} return count
}
private val waitDownloadSet = hashSetOf<Int>()
private val onDownloadSet = hashSetOf<Int>()
private val successDownloadSet = hashSetOf<Int>()
private val errorDownloadMap = hashMapOf<Int, Int>()
val waitCount get() = waitDownloadSet.size class CacheBookModel(var bookSource: BookSource, var book: Book) {
val onDownloadCount get() = onDownloadSet.size
val successCount get() = successDownloadSet.size
val errorCount get() = errorDownloadMap.size
@Synchronized private val waitDownloadSet = hashSetOf<Int>()
fun isRun(): Boolean { private val onDownloadSet = hashSetOf<Int>()
return waitDownloadSet.size > 0 || onDownloadSet.size > 0 private val successDownloadSet = hashSetOf<Int>()
} private val errorDownloadMap = hashMapOf<Int, Int>()
@Synchronized val waitCount get() = waitDownloadSet.size
fun stop() { val onDownloadCount get() = onDownloadSet.size
waitDownloadSet.clear() val successCount get() = successDownloadSet.size
} val errorCount get() = errorDownloadMap.size
@Synchronized @Synchronized
fun addDownload(start: Int, end: Int) { fun isRun(): Boolean {
for (i in start..end) { return waitDownloadSet.size > 0 || onDownloadSet.size > 0
if (!onDownloadSet.contains(i)) {
waitDownloadSet.add(i)
}
} }
}
@Synchronized @Synchronized
private fun onSuccess(index: Int) { fun stop() {
onDownloadSet.remove(index) waitDownloadSet.clear()
successDownloadSet.add(index) }
errorDownloadMap.remove(index)
}
@Synchronized @Synchronized
private fun onError(index: Int, error: Throwable, chapterTitle: String) { fun addDownload(start: Int, end: Int) {
if (error !is ConcurrentException) { for (i in start..end) {
errorDownloadMap[index] = (errorDownloadMap[index] ?: 0) + 1 if (!onDownloadSet.contains(i)) {
waitDownloadSet.add(i)
}
}
} }
onDownloadSet.remove(index)
//重试3次 @Synchronized
if (errorDownloadMap[index] ?: 0 < 3) { private fun onSuccess(index: Int) {
waitDownloadSet.add(index) onDownloadSet.remove(index)
} else { successDownloadSet.add(index)
AppLog.addLog( errorDownloadMap.remove(index)
"下载${book.name}-${chapterTitle}失败\n${error.localizedMessage}",
error
)
error.printOnDebug()
} }
}
@Synchronized @Synchronized
private fun onCancel(index: Int) { private fun onError(index: Int, error: Throwable, chapterTitle: String) {
onDownloadSet.remove(index) if (error !is ConcurrentException) {
waitDownloadSet.add(index) errorDownloadMap[index] = (errorDownloadMap[index] ?: 0) + 1
} }
onDownloadSet.remove(index)
//重试3次
if (errorDownloadMap[index] ?: 0 < 3) {
waitDownloadSet.add(index)
} else {
AppLog.addLog(
"下载${book.name}-${chapterTitle}失败\n${error.localizedMessage}",
error
)
error.printOnDebug()
}
}
@Synchronized @Synchronized
private fun onFinally() { private fun onCancel(index: Int) {
if (waitDownloadSet.isEmpty() && onDownloadSet.isEmpty()) { onDownloadSet.remove(index)
postEvent(EventBus.UP_DOWNLOAD, "") waitDownloadSet.add(index)
cacheBookMap.remove(book.bookUrl)
} }
}
/** @Synchronized
* 从待下载列表内取第一条下载 private fun onFinally() {
*/ if (waitDownloadSet.isEmpty() && onDownloadSet.isEmpty()) {
@Synchronized postEvent(EventBus.UP_DOWNLOAD, "")
fun download(scope: CoroutineScope, context: CoroutineContext): Boolean {
val chapterIndex = waitDownloadSet.firstOrNull()
if (chapterIndex == null) {
if (onDownloadSet.isEmpty()) {
cacheBookMap.remove(book.bookUrl) cacheBookMap.remove(book.bookUrl)
} }
return false
}
if (onDownloadSet.contains(chapterIndex)) {
waitDownloadSet.remove(chapterIndex)
return download(scope, context)
}
val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, chapterIndex) ?: let {
waitDownloadSet.remove(chapterIndex)
return download(scope, context)
}
if (BookHelp.hasContent(book, chapter)) {
waitDownloadSet.remove(chapterIndex)
return download(scope, context)
}
waitDownloadSet.remove(chapterIndex)
onDownloadSet.add(chapterIndex)
WebBook.getContent(
scope,
bookSource,
book,
chapter,
context = context
).onSuccess { content ->
onSuccess(chapterIndex)
downloadFinish(chapter, content)
}.onError {
//出现错误等待一秒后重新加入待下载列表
delay(1000)
onError(chapterIndex, it, chapter.title)
downloadFinish(chapter, "获取正文失败\n${it.localizedMessage}")
}.onCancel {
onCancel(chapterIndex)
}.onFinally {
onFinally()
} }
return true
}
@Synchronized /**
fun download( * 从待下载列表内取第一条下载
scope: CoroutineScope, */
chapter: BookChapter, @Synchronized
resetPageOffset: Boolean = false fun download(scope: CoroutineScope, context: CoroutineContext): Boolean {
) { val chapterIndex = waitDownloadSet.firstOrNull()
if (onDownloadSet.contains(chapter.index)) { if (chapterIndex == null) {
return if (onDownloadSet.isEmpty()) {
} cacheBookMap.remove(book.bookUrl)
onDownloadSet.add(chapter.index) }
waitDownloadSet.remove(chapter.index) return false
WebBook.getContent(scope, bookSource, book, chapter) }
.onSuccess { content -> if (onDownloadSet.contains(chapterIndex)) {
onSuccess(chapter.index) waitDownloadSet.remove(chapterIndex)
downloadFinish(chapter, content, resetPageOffset) return download(scope, context)
}
val chapter = appDb.bookChapterDao.getChapter(book.bookUrl, chapterIndex) ?: let {
waitDownloadSet.remove(chapterIndex)
return download(scope, context)
}
if (BookHelp.hasContent(book, chapter)) {
waitDownloadSet.remove(chapterIndex)
return download(scope, context)
}
waitDownloadSet.remove(chapterIndex)
onDownloadSet.add(chapterIndex)
WebBook.getContent(
scope,
bookSource,
book,
chapter,
context = context
).onSuccess { content ->
onSuccess(chapterIndex)
downloadFinish(chapter, content)
}.onError { }.onError {
onError(chapter.index, it, chapter.title) //出现错误等待一秒后重新加入待下载列表
downloadFinish(chapter, "获取正文失败\n${it.localizedMessage}", resetPageOffset) delay(1000)
onError(chapterIndex, it, chapter.title)
downloadFinish(chapter, "获取正文失败\n${it.localizedMessage}")
}.onCancel { }.onCancel {
onCancel(chapter.index) onCancel(chapterIndex)
}.onFinally { }.onFinally {
if (waitDownloadSet.isEmpty() && onDownloadSet.isEmpty()) { onFinally()
postEvent(EventBus.UP_DOWNLOAD, "")
}
} }
} return true
}
private fun downloadFinish( @Synchronized
chapter: BookChapter, fun download(
content: String, scope: CoroutineScope,
resetPageOffset: Boolean = false chapter: BookChapter,
) { resetPageOffset: Boolean = false
if (ReadBook.book?.bookUrl == book.bookUrl) { ) {
ReadBook.contentLoadFinish( if (onDownloadSet.contains(chapter.index)) {
book, chapter, content, return
resetPageOffset = resetPageOffset }
) onDownloadSet.add(chapter.index)
waitDownloadSet.remove(chapter.index)
WebBook.getContent(scope, bookSource, book, chapter)
.onSuccess { content ->
onSuccess(chapter.index)
downloadFinish(chapter, content, resetPageOffset)
}.onError {
onError(chapter.index, it, chapter.title)
downloadFinish(chapter, "获取正文失败\n${it.localizedMessage}", resetPageOffset)
}.onCancel {
onCancel(chapter.index)
}.onFinally {
if (waitDownloadSet.isEmpty() && onDownloadSet.isEmpty()) {
postEvent(EventBus.UP_DOWNLOAD, "")
}
}
} }
private fun downloadFinish(
chapter: BookChapter,
content: String,
resetPageOffset: Boolean = false
) {
if (ReadBook.book?.bookUrl == book.bookUrl) {
ReadBook.contentLoadFinish(
book, chapter, content,
resetPageOffset = resetPageOffset
)
}
}
} }
} }
Loading…
Cancel
Save