Merge remote-tracking branch 'origin/master'

This commit is contained in:
kunfei
2023-11-23 11:28:44 +08:00
8 changed files with 70 additions and 39 deletions
@@ -19,6 +19,7 @@ import kotlin.coroutines.CoroutineContext
/** /**
* 链式协程 * 链式协程
* 注意:如果协程太快完成,回调会不执行
*/ */
@Suppress("unused", "MemberVisibilityCanBePrivate") @Suppress("unused", "MemberVisibilityCanBePrivate")
class Coroutine<T>( class Coroutine<T>(
@@ -18,6 +18,7 @@ import io.legado.app.utils.postEvent
import io.legado.app.utils.startService import io.legado.app.utils.startService
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers.IO import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@@ -268,6 +269,7 @@ object CacheBook {
book, book,
chapter, chapter,
context = context, context = context,
start = CoroutineStart.LAZY,
executeContext = context executeContext = context
).onSuccess { content -> ).onSuccess { content ->
onSuccess(chapter) onSuccess(chapter)
@@ -282,7 +284,7 @@ object CacheBook {
onCancel(chapterIndex) onCancel(chapterIndex)
}.onFinally { }.onFinally {
onFinally() onFinally()
} }.start()
} }
@Synchronized @Synchronized
@@ -297,22 +299,28 @@ object CacheBook {
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl) postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
onDownloadSet.add(chapter.index) onDownloadSet.add(chapter.index)
waitDownloadSet.remove(chapter.index) waitDownloadSet.remove(chapter.index)
WebBook.getContent(scope, bookSource, book, chapter, executeContext = IO) WebBook.getContent(
.onSuccess { content -> scope,
onSuccess(chapter) bookSource,
ReadBook.downloadedChapters.add(chapter.index) book,
ReadBook.downloadFailChapters.remove(chapter.index) chapter,
downloadFinish(chapter, content, resetPageOffset) start = CoroutineStart.LAZY,
}.onError { executeContext = IO
onError(chapter, it) ).onSuccess { content ->
ReadBook.downloadFailChapters[chapter.index] = onSuccess(chapter)
(ReadBook.downloadFailChapters[chapter.index] ?: 0) + 1 ReadBook.downloadedChapters.add(chapter.index)
downloadFinish(chapter, "获取正文失败\n${it.localizedMessage}", resetPageOffset) ReadBook.downloadFailChapters.remove(chapter.index)
}.onCancel { downloadFinish(chapter, content, resetPageOffset)
onCancel(chapter.index) }.onError {
}.onFinally { onError(chapter, it)
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl) ReadBook.downloadFailChapters[chapter.index] =
} (ReadBook.downloadFailChapters[chapter.index] ?: 0) + 1
downloadFinish(chapter, "获取正文失败\n${it.localizedMessage}", resetPageOffset)
}.onCancel {
onCancel(chapter.index)
}.onFinally {
postEvent(EventBus.UP_DOWNLOAD, book.bookUrl)
}.start()
} }
private fun downloadFinish( private fun downloadFinish(
@@ -11,6 +11,7 @@ import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.ui.book.search.SearchScope import io.legado.app.ui.book.search.SearchScope
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.ExecutorCoroutineDispatcher import kotlinx.coroutines.ExecutorCoroutineDispatcher
import kotlinx.coroutines.asCoroutineDispatcher import kotlinx.coroutines.asCoroutineDispatcher
import kotlinx.coroutines.ensureActive import kotlinx.coroutines.ensureActive
@@ -76,7 +77,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
return return
} }
searchIndex++ searchIndex++
val source = bookSourceList[searchIndex] val source = bookSourceList.getOrNull(searchIndex) ?: return
val searchPool = searchPool ?: return val searchPool = searchPool ?: return
val task = WebBook.searchBook( val task = WebBook.searchBook(
scope, scope,
@@ -84,6 +85,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
searchKey, searchKey,
searchPage, searchPage,
context = searchPool, context = searchPool,
start = CoroutineStart.LAZY,
executeContext = searchPool executeContext = searchPool
).timeout(30000L) ).timeout(30000L)
.onSuccess { .onSuccess {
@@ -93,6 +95,7 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
.onFinally { .onFinally {
onFinally(searchId) onFinally(searchId)
} }
task.start()
tasks.add(task) tasks.add(task)
} }
@@ -14,6 +14,7 @@ import io.legado.app.model.analyzeRule.AnalyzeRule
import io.legado.app.model.analyzeRule.AnalyzeUrl import io.legado.app.model.analyzeRule.AnalyzeUrl
import io.legado.app.model.analyzeRule.RuleData import io.legado.app.model.analyzeRule.RuleData
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.CoroutineStart
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.isActive import kotlinx.coroutines.isActive
import kotlin.coroutines.CoroutineContext import kotlin.coroutines.CoroutineContext
@@ -30,9 +31,10 @@ object WebBook {
key: String, key: String,
page: Int? = 1, page: Int? = 1,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main, executeContext: CoroutineContext = Dispatchers.Main,
): Coroutine<ArrayList<SearchBook>> { ): Coroutine<ArrayList<SearchBook>> {
return Coroutine.async(scope, context, executeContext = executeContext) { return Coroutine.async(scope, context, start = start, executeContext = executeContext) {
searchBookAwait(bookSource, key, page) searchBookAwait(bookSource, key, page)
} }
} }
@@ -266,9 +268,10 @@ object WebBook {
nextChapterUrl: String? = null, nextChapterUrl: String? = null,
needSave: Boolean = true, needSave: Boolean = true,
context: CoroutineContext = Dispatchers.IO, context: CoroutineContext = Dispatchers.IO,
start: CoroutineStart = CoroutineStart.DEFAULT,
executeContext: CoroutineContext = Dispatchers.Main, executeContext: CoroutineContext = Dispatchers.Main,
): Coroutine<String> { ): Coroutine<String> {
return Coroutine.async(scope, context, executeContext = executeContext) { return Coroutine.async(scope, context, start = start, executeContext = executeContext) {
getContentAwait(bookSource, book, bookChapter, nextChapterUrl, needSave) getContentAwait(bookSource, book, bookChapter, nextChapterUrl, needSave)
} }
} }
@@ -452,7 +452,7 @@ class AudioPlayService : BaseService(),
.setBufferedPosition(exoPlayer.bufferedPosition) .setBufferedPosition(exoPlayer.bufferedPosition)
.addCustomAction( .addCustomAction(
APP_ACTION_STOP, APP_ACTION_STOP,
getString(R.string.set_timer), getString(R.string.stop),
R.drawable.ic_stop_black_24dp R.drawable.ic_stop_black_24dp
) )
.addCustomAction( .addCustomAction(
@@ -200,9 +200,13 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
} }
++searchIndex ++searchIndex
} }
val source = bookSourceList[searchIndex] val source = bookSourceList.getOrNull(searchIndex) ?: return
bookSourceList[searchIndex] = emptyBookSource bookSourceList[searchIndex] = emptyBookSource
val task = execute(context = searchPool!!, executeContext = searchPool!!) { val task = execute(
context = searchPool!!,
start = CoroutineStart.LAZY,
executeContext = searchPool!!
) {
val resultBooks = WebBook.searchBookAwait(source, name) val resultBooks = WebBook.searchBookAwait(source, name)
resultBooks.forEach { searchBook -> resultBooks.forEach { searchBook ->
if (searchBook.name != name) { if (searchBook.name != name) {
@@ -223,11 +227,14 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
} }
}.timeout(60000L) }.timeout(60000L)
.onError { .onError {
ensureActive()
nextSearch() nextSearch()
} }
.onSuccess { .onSuccess {
ensureActive()
nextSearch() nextSearch()
} }
task.start()
tasks.add(task) tasks.add(task)
} }
@@ -293,8 +300,9 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
searchCallback?.searchSuccess(searchBook) searchCallback?.searchSuccess(searchBook)
} }
@Synchronized
private fun nextSearch() { private fun nextSearch() {
synchronized(this) { kotlin.runCatching {
if (searchIndex < bookSourceList.lastIndex) { if (searchIndex < bookSourceList.lastIndex) {
search() search()
} else { } else {
@@ -36,6 +36,7 @@ import org.apache.commons.text.StringEscapeUtils
import org.jsoup.Jsoup import org.jsoup.Jsoup
import java.io.ByteArrayInputStream import java.io.ByteArrayInputStream
import java.net.URLDecoder import java.net.URLDecoder
import java.util.regex.PatternSyntaxException
/** /**
* rss阅读界面 * rss阅读界面
@@ -346,24 +347,32 @@ class ReadRssActivity : VMBaseActivity<ActivityRssReadBinding, ReadRssViewModel>
request: WebResourceRequest request: WebResourceRequest
): WebResourceResponse? { ): WebResourceResponse? {
val url = request.url.toString() val url = request.url.toString()
viewModel.rssSource?.let { source -> val source = viewModel.rssSource ?: return super.shouldInterceptRequest(view, request)
val blacklist = source.contentBlacklist?.splitNotBlank(",") val blacklist = source.contentBlacklist?.splitNotBlank(",")
if (!blacklist.isNullOrEmpty()) { if (!blacklist.isNullOrEmpty()) {
blacklist.forEach { blacklist.forEach {
try {
if (url.startsWith(it) || url.matches(it.toRegex())) { if (url.startsWith(it) || url.matches(it.toRegex())) {
return createEmptyResource() return createEmptyResource()
} }
} catch (e: PatternSyntaxException) {
AppLog.put("黑名单规则正则语法错误 源名称:${source.sourceName} 正则:$it", e)
} }
} else { }
val whitelist = source.contentWhitelist?.splitNotBlank(",") } else {
if (!whitelist.isNullOrEmpty()) { val whitelist = source.contentWhitelist?.splitNotBlank(",")
whitelist.forEach { if (!whitelist.isNullOrEmpty()) {
whitelist.forEach {
try {
if (url.startsWith(it) || url.matches(it.toRegex())) { if (url.startsWith(it) || url.matches(it.toRegex())) {
return super.shouldInterceptRequest(view, request) return super.shouldInterceptRequest(view, request)
} }
} catch (e: PatternSyntaxException) {
val msg = "白名单规则正则语法错误 源名称:${source.sourceName} 正则:$it"
AppLog.put(msg, e)
} }
return createEmptyResource()
} }
return createEmptyResource()
} }
} }
return super.shouldInterceptRequest(view, request) return super.shouldInterceptRequest(view, request)
@@ -4,6 +4,7 @@ package io.legado.app.utils
import android.annotation.SuppressLint import android.annotation.SuppressLint
import android.content.Context import android.content.Context
import android.view.View
import android.widget.TextView import android.widget.TextView
import android.widget.Toast import android.widget.Toast
import androidx.cardview.widget.CardView import androidx.cardview.widget.CardView
@@ -28,12 +29,10 @@ fun Context.toastOnUi(message: Int, duration: Int = Toast.LENGTH_SHORT) {
fun Context.toastOnUi(message: CharSequence?, duration: Int = Toast.LENGTH_SHORT) { fun Context.toastOnUi(message: CharSequence?, duration: Int = Toast.LENGTH_SHORT) {
runOnUI { runOnUI {
kotlin.runCatching { kotlin.runCatching {
if (toast == null || BuildConfig.DEBUG || AppConfig.recordLog) { toast?.cancel()
toast?.cancel() toast = Toast(this)
toast = Toast(this) val toastView: View = inflate(R.layout.view_toast)
toast?.view = inflate(R.layout.view_toast) toast?.view = toastView
}
val toastView = toast?.view!!
val cardView = toastView.findViewById<CardView>(R.id.cv_content) val cardView = toastView.findViewById<CardView>(R.id.cv_content)
cardView.setCardBackgroundColor(bottomBackground) cardView.setCardBackgroundColor(bottomBackground)
val isLight = ColorUtils.isColorLight(bottomBackground) val isLight = ColorUtils.isColorLight(bottomBackground)