Merge pull request #467 from gedoor/master

修复换源不获取详情页最新章节的bug
pull/481/head
Antecer 4 years ago committed by GitHub
commit 413011e2ac
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
  1. 2
      app/src/main/assets/updateLog.md
  2. 3
      app/src/main/java/io/legado/app/help/AppConfig.kt
  3. 20
      app/src/main/java/io/legado/app/model/analyzeRule/AnalyzeRule.kt
  4. 2
      app/src/main/java/io/legado/app/model/webBook/BookInfo.kt
  5. 12
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceDialog.kt
  6. 119
      app/src/main/java/io/legado/app/ui/book/changesource/ChangeSourceViewModel.kt
  7. 4
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesFragment.kt
  8. 1
      app/src/main/java/io/legado/app/ui/rss/article/RssArticlesViewModel.kt
  9. 2
      app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditActivity.kt
  10. 26
      app/src/main/java/io/legado/app/ui/rss/source/edit/RssSourceEditViewModel.kt
  11. 8
      app/src/main/res/menu/change_source.xml

@ -3,7 +3,7 @@
* 关注合作公众号 **[小说拾遗]()** 获取好看的小说。 * 关注合作公众号 **[小说拾遗]()** 获取好看的小说。
* 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。 * 旧版数据导入教程:先在旧版阅读(2.x)中进行备份,然后在新版阅读(3.x)【我的】->【备份与恢复】,选择【导入旧版本数据】。
**2020/11/06** **2020/11/07**
* 详情页菜单添加拷贝URL * 详情页菜单添加拷贝URL
* 解决一些书名太长缓存报错的bug * 解决一些书名太长缓存报错的bug
* 添加备份搜索记录 * 添加备份搜索记录

@ -131,5 +131,8 @@ object AppConfig {
App.INSTANCE.getPrefBoolean(PreferKey.replaceEnableDefault, true) App.INSTANCE.getPrefBoolean(PreferKey.replaceEnableDefault, true)
} }
val changeSourceLoadInfo get() = App.INSTANCE.getPrefBoolean(PreferKey.changeSourceLoadToc)
val changeSourceLoadToc get() = App.INSTANCE.getPrefBoolean(PreferKey.changeSourceLoadToc)
} }

@ -17,8 +17,7 @@ import javax.script.SimpleBindings
import kotlin.collections.HashMap import kotlin.collections.HashMap
/** /**
* Created by REFGD. * 解析规则获取结果
* 统一解析接口
*/ */
@Keep @Keep
@Suppress("unused", "RegExpRedundantEscape") @Suppress("unused", "RegExpRedundantEscape")
@ -38,11 +37,11 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
private var objectChangedJS = false private var objectChangedJS = false
private var objectChangedJP = false private var objectChangedJP = false
@Throws(Exception::class) fun setContent(content: Any?, baseUrl: String? = null): AnalyzeRule {
fun setContent(content: Any?): AnalyzeRule {
if (content == null) throw AssertionError("Content cannot be null") if (content == null) throw AssertionError("Content cannot be null")
isJSON = content.toString().isJson()
this.content = content this.content = content
setBaseUrl(baseUrl)
isJSON = content.toString().isJson()
objectChangedXP = true objectChangedXP = true
objectChangedJS = true objectChangedJS = true
objectChangedJP = true objectChangedJP = true
@ -50,8 +49,8 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
} }
fun setBaseUrl(baseUrl: String?): AnalyzeRule { fun setBaseUrl(baseUrl: String?): AnalyzeRule {
this.baseUrl = baseUrl
baseUrl?.let { baseUrl?.let {
this.baseUrl = baseUrl
try { try {
baseURL = URL(baseUrl.substringBefore(",")) baseURL = URL(baseUrl.substringBefore(","))
} catch (e: Exception) { } catch (e: Exception) {
@ -118,7 +117,6 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
/** /**
* 获取文本列表 * 获取文本列表
*/ */
@Throws(Exception::class)
@JvmOverloads @JvmOverloads
fun getStringList(rule: String?, isUrl: Boolean = false): List<String>? { fun getStringList(rule: String?, isUrl: Boolean = false): List<String>? {
if (rule.isNullOrEmpty()) return null if (rule.isNullOrEmpty()) return null
@ -126,7 +124,6 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
return getStringList(ruleList, isUrl) return getStringList(ruleList, isUrl)
} }
@Throws(Exception::class)
fun getStringList(ruleList: List<SourceRule>, isUrl: Boolean = false): List<String>? { fun getStringList(ruleList: List<SourceRule>, isUrl: Boolean = false): List<String>? {
var result: Any? = null var result: Any? = null
val content = this.content val content = this.content
@ -184,14 +181,12 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
/** /**
* 获取文本 * 获取文本
*/ */
@Throws(Exception::class)
fun getString(ruleStr: String?, isUrl: Boolean = false): String { fun getString(ruleStr: String?, isUrl: Boolean = false): String {
if (TextUtils.isEmpty(ruleStr)) return "" if (TextUtils.isEmpty(ruleStr)) return ""
val ruleList = splitSourceRule(ruleStr) val ruleList = splitSourceRule(ruleStr)
return getString(ruleList, isUrl) return getString(ruleList, isUrl)
} }
@Throws(Exception::class)
@JvmOverloads @JvmOverloads
fun getString(ruleList: List<SourceRule>, isUrl: Boolean = false): String { fun getString(ruleList: List<SourceRule>, isUrl: Boolean = false): String {
var result: Any? = null var result: Any? = null
@ -244,7 +239,6 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
/** /**
* 获取Element * 获取Element
*/ */
@Throws(Exception::class)
fun getElement(ruleStr: String): Any? { fun getElement(ruleStr: String): Any? {
if (TextUtils.isEmpty(ruleStr)) return null if (TextUtils.isEmpty(ruleStr)) return null
var result: Any? = null var result: Any? = null
@ -278,7 +272,6 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
* 获取列表 * 获取列表
*/ */
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
@Throws(Exception::class)
fun getElements(ruleStr: String): List<Any> { fun getElements(ruleStr: String): List<Any> {
var result: Any? = null var result: Any? = null
val ruleList = splitSourceRule(ruleStr) val ruleList = splitSourceRule(ruleStr)
@ -313,7 +306,6 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
/** /**
* 保存变量 * 保存变量
*/ */
@Throws(Exception::class)
private fun putRule(map: Map<String, String>) { private fun putRule(map: Map<String, String>) {
for ((key, value) in map) { for ((key, value) in map) {
put(key, getString(value)) put(key, getString(value))
@ -323,7 +315,6 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
/** /**
* 分离put规则 * 分离put规则
*/ */
@Throws(Exception::class)
private fun splitPutRule(ruleStr: String, putMap: HashMap<String, String>): String { private fun splitPutRule(ruleStr: String, putMap: HashMap<String, String>): String {
var vRuleStr = ruleStr var vRuleStr = ruleStr
val putMatcher = putPattern.matcher(vRuleStr) val putMatcher = putPattern.matcher(vRuleStr)
@ -359,7 +350,6 @@ class AnalyzeRule(var book: BaseBook? = null) : JsExtensions {
/** /**
* 分解规则生成规则列表 * 分解规则生成规则列表
*/ */
@Throws(Exception::class)
fun splitSourceRule(ruleStr: String?, mode: Mode = Mode.Default): List<SourceRule> { fun splitSourceRule(ruleStr: String?, mode: Mode = Mode.Default): List<SourceRule> {
var vRuleStr = ruleStr var vRuleStr = ruleStr
val ruleList = ArrayList<SourceRule>() val ruleList = ArrayList<SourceRule>()

@ -29,7 +29,7 @@ object BookInfo {
val analyzeRule = AnalyzeRule(book) val analyzeRule = AnalyzeRule(book)
analyzeRule.setContent(body).setBaseUrl(baseUrl) analyzeRule.setContent(body).setBaseUrl(baseUrl)
infoRule.init?.let { infoRule.init?.let {
if (it.isNotEmpty()) { if (it.isNotBlank()) {
Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则") Debug.log(bookSource.bookSourceUrl, "≡执行详情页初始化规则")
analyzeRule.setContent(analyzeRule.getElement(it)) analyzeRule.setContent(analyzeRule.getElement(it))
} }

@ -16,9 +16,13 @@ import io.legado.app.base.BaseDialogFragment
import io.legado.app.constant.PreferKey import io.legado.app.constant.PreferKey
import io.legado.app.data.entities.Book import io.legado.app.data.entities.Book
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.help.AppConfig
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.* import io.legado.app.utils.applyTint
import io.legado.app.utils.getSize
import io.legado.app.utils.getViewModel
import io.legado.app.utils.putPrefBoolean
import kotlinx.android.synthetic.main.dialog_change_source.* import kotlinx.android.synthetic.main.dialog_change_source.*
@ -81,10 +85,8 @@ class ChangeSourceDialog : BaseDialogFragment(),
tool_bar.inflateMenu(R.menu.change_source) tool_bar.inflateMenu(R.menu.change_source)
tool_bar.menu.applyTint(requireContext()) tool_bar.menu.applyTint(requireContext())
tool_bar.setOnMenuItemClickListener(this) tool_bar.setOnMenuItemClickListener(this)
tool_bar.menu.findItem(R.id.menu_load_toc)?.isChecked = tool_bar.menu.findItem(R.id.menu_load_info)?.isChecked = AppConfig.changeSourceLoadInfo
getPrefBoolean(PreferKey.changeSourceLoadToc) tool_bar.menu.findItem(R.id.menu_load_toc)?.isChecked = AppConfig.changeSourceLoadToc
tool_bar.menu.findItem(R.id.menu_load_info)?.isChecked =
getPrefBoolean(PreferKey.changeSourceLoadInfo)
} }
private fun initRecyclerView() { private fun initRecyclerView() {

@ -23,7 +23,6 @@ import kotlinx.coroutines.asCoroutineDispatcher
import org.jetbrains.anko.debug import org.jetbrains.anko.debug
import java.util.concurrent.CopyOnWriteArraySet import java.util.concurrent.CopyOnWriteArraySet
import java.util.concurrent.Executors import java.util.concurrent.Executors
import kotlin.math.min
class ChangeSourceViewModel(application: Application) : BaseViewModel(application) { class ChangeSourceViewModel(application: Application) : BaseViewModel(application) {
private val threadCount = AppConfig.threadCount private val threadCount = AppConfig.threadCount
@ -39,6 +38,7 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
private val searchBooks = CopyOnWriteArraySet<SearchBook>() private val searchBooks = CopyOnWriteArraySet<SearchBook>()
private var postTime = 0L private var postTime = 0L
private val sendRunnable = Runnable { upAdapter() } private val sendRunnable = Runnable { upAdapter() }
@Volatile @Volatile
private var searchIndex = -1 private var searchIndex = -1
@ -113,80 +113,73 @@ class ChangeSourceViewModel(application: Application) : BaseViewModel(applicatio
return return
} }
searchIndex++ searchIndex++
val source = bookSourceList[searchIndex] }
val variableBook = SearchBook() val source = bookSourceList[searchIndex]
val task = WebBook(source) val variableBook = SearchBook()
.searchBook(name, variableBook = variableBook, scope = this, context = searchPool!!) val webBook = WebBook(source)
.timeout(60000L) val task = webBook
.onSuccess(IO) { .searchBook(name, variableBook = variableBook, scope = this, context = searchPool!!)
it.forEach { searchBook -> .timeout(60000L)
if (searchBook.name == name && searchBook.author == author) { .onSuccess(IO) {
if (searchBook.latestChapterTitle.isNullOrEmpty()) { it.forEach { searchBook ->
if (context.getPrefBoolean(PreferKey.changeSourceLoadInfo) || context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { if (searchBook.name == name && searchBook.author == author) {
loadBookInfo(searchBook.toBook()) if (searchBook.latestChapterTitle.isNullOrEmpty()) {
} else { if (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) {
searchFinish(searchBook) loadBookInfo(webBook, searchBook.toBook())
}
} else { } else {
searchFinish(searchBook) searchFinish(searchBook)
} }
return@forEach } else {
searchFinish(searchBook)
} }
return@onSuccess
} }
} }
.onFinally { }
synchronized(this) { .onFinally {
if (searchIndex < bookSourceList.lastIndex) { synchronized(this) {
search() if (searchIndex < bookSourceList.lastIndex) {
} else { search()
searchIndex++ } else {
} searchIndex++
if (searchIndex >= bookSourceList.lastIndex + min(bookSourceList.size, }
threadCount) if (searchIndex >= bookSourceList.lastIndex + bookSourceList.size
) { || searchIndex >= bookSourceList.lastIndex + threadCount
searchStateData.postValue(false) ) {
} searchStateData.postValue(false)
} }
} }
tasks.add(task) }
} tasks.add(task)
} }
private fun loadBookInfo(book: Book) { private fun loadBookInfo(webBook: WebBook, book: Book) {
execute { webBook.getBookInfo(book, this)
App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> .onSuccess {
WebBook(bookSource).getBookInfo(book, this) if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) {
.onSuccess { loadChapter(webBook, book)
if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { } else {
loadChapter(it) //从详情页里获取最新章节
} else { book.latestChapterTitle = it.latestChapterTitle
//从详情页里获取最新章节 val searchBook = book.toSearchBook()
book.latestChapterTitle = it.latestChapterTitle searchFinish(searchBook)
val searchBook = book.toSearchBook() }
searchFinish(searchBook) }.onError {
} debug { context.getString(R.string.error_get_book_info) }
}.onError { }
debug { context.getString(R.string.error_get_book_info) }
}
} ?: debug { context.getString(R.string.error_no_source) }
}
} }
private fun loadChapter(book: Book) { private fun loadChapter(webBook: WebBook, book: Book) {
execute { webBook.getChapterList(book, this)
App.db.bookSourceDao().getBookSource(book.origin)?.let { bookSource -> .onSuccess(IO) { chapters ->
WebBook(bookSource).getChapterList(book, this) if (chapters.isNotEmpty()) {
.onSuccess(IO) { chapters -> book.latestChapterTitle = chapters.last().title
if (chapters.isNotEmpty()) { val searchBook: SearchBook = book.toSearchBook()
book.latestChapterTitle = chapters.last().title searchFinish(searchBook)
val searchBook: SearchBook = book.toSearchBook() }
searchFinish(searchBook) }.onError {
} debug { context.getString(R.string.error_get_chapter_list) }
}.onError { }
debug { context.getString(R.string.error_get_chapter_list) }
}
} ?: debug { R.string.error_no_source }
}
} }
/** /**

@ -103,14 +103,14 @@ class RssArticlesFragment : VMBaseFragment<RssArticlesViewModel>(R.layout.fragme
} }
override fun observeLiveBus() { override fun observeLiveBus() {
viewModel.loadFinally.observe(viewLifecycleOwner, { viewModel.loadFinally.observe(viewLifecycleOwner) {
refresh_recycler_view.stopLoading() refresh_recycler_view.stopLoading()
if (it) { if (it) {
loadMoreView.startLoad() loadMoreView.startLoad()
} else { } else {
loadMoreView.noMore() loadMoreView.noMore()
} }
}) }
} }
override fun readRss(rssArticle: RssArticle) { override fun readRss(rssArticle: RssArticle) {

@ -49,6 +49,7 @@ class RssArticlesViewModel(application: Application) : BaseViewModel(application
isLoading = false isLoading = false
} }
}.onError { }.onError {
loadFinally.postValue(false)
it.printStackTrace() it.printStackTrace()
toast(it.localizedMessage) toast(it.localizedMessage)
} }

@ -136,7 +136,7 @@ class RssSourceEditActivity :
} }
private fun getRssSource(): RssSource { private fun getRssSource(): RssSource {
val source = viewModel.rssSource?.copy() ?: RssSource() val source = viewModel.rssSource
source.enabled = cb_is_enable.isChecked source.enabled = cb_is_enable.isChecked
source.enableJs = cb_enable_js.isChecked source.enableJs = cb_enable_js.isChecked
source.loadWithBaseUrl = cb_enable_base_url.isChecked source.loadWithBaseUrl = cb_enable_base_url.isChecked

@ -8,24 +8,23 @@ import io.legado.app.data.entities.RssSource
import io.legado.app.utils.GSON import io.legado.app.utils.GSON
import io.legado.app.utils.fromJsonObject import io.legado.app.utils.fromJsonObject
import io.legado.app.utils.getClipText import io.legado.app.utils.getClipText
import io.legado.app.utils.msg
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
class RssSourceEditViewModel(application: Application) : BaseViewModel(application) { class RssSourceEditViewModel(application: Application) : BaseViewModel(application) {
var rssSource: RssSource? = null var rssSource: RssSource = RssSource()
private var oldSourceUrl: String? = null private var oldSourceUrl: String = ""
fun initData(intent: Intent, onFinally: () -> Unit) { fun initData(intent: Intent, onFinally: () -> Unit) {
execute { execute {
val key = intent.getStringExtra("data") val key = intent.getStringExtra("data")
var source: RssSource? = null
if (key != null) { if (key != null) {
source = App.db.rssSourceDao().getByKey(key) App.db.rssSourceDao().getByKey(key)?.let {
} rssSource = it
source?.let { }
oldSourceUrl = it.sourceUrl
rssSource = it
} }
oldSourceUrl = rssSource.sourceUrl
}.onFinally { }.onFinally {
onFinally() onFinally()
} }
@ -33,14 +32,11 @@ class RssSourceEditViewModel(application: Application) : BaseViewModel(applicati
fun save(source: RssSource, success: (() -> Unit)) { fun save(source: RssSource, success: (() -> Unit)) {
execute { execute {
oldSourceUrl?.let { if (oldSourceUrl != source.sourceUrl) {
if (oldSourceUrl != source.sourceUrl) { App.db.rssSourceDao().delete(oldSourceUrl)
App.db.rssSourceDao().delete(it) oldSourceUrl = source.sourceUrl
}
} }
oldSourceUrl = source.sourceUrl
App.db.rssSourceDao().insert(source) App.db.rssSourceDao().insert(source)
rssSource = source
}.onSuccess { }.onSuccess {
success() success()
}.onError { }.onError {
@ -74,7 +70,7 @@ class RssSourceEditViewModel(application: Application) : BaseViewModel(applicati
finally.invoke(it) finally.invoke(it)
} }
}.onError { }.onError {
toast(it.localizedMessage ?: "Error") toast(it.msg)
} }
} }

@ -19,14 +19,14 @@
tools:ignore="AlwaysShowAction" /> tools:ignore="AlwaysShowAction" />
<item <item
android:id="@+id/menu_load_toc" android:id="@+id/menu_load_info"
android:title="@string/load_toc" android:title="@string/load_info"
android:checkable="true" android:checkable="true"
app:showAsAction="never" /> app:showAsAction="never" />
<item <item
android:id="@+id/menu_load_info" android:id="@+id/menu_load_toc"
android:title="@string/load_info" android:title="@string/load_toc"
android:checkable="true" android:checkable="true"
app:showAsAction="never" /> app:showAsAction="never" />

Loading…
Cancel
Save