This commit is contained in:
Horis
2025-03-25 12:20:50 +08:00
parent 7f381174cf
commit 64d7d3f300
10 changed files with 66 additions and 56 deletions
@@ -365,7 +365,7 @@ data class Book(
} }
fun save() { fun save() {
if (appDb.bookDao.has(bookUrl) == true) { if (appDb.bookDao.has(bookUrl)) {
appDb.bookDao.update(this) appDb.bookDao.update(this)
} else { } else {
appDb.bookDao.insert(this) appDb.bookDao.insert(this)
@@ -244,6 +244,7 @@ fun Book.sync(oldBook: Book) {
} }
} }
canUpdate = curBook.canUpdate canUpdate = curBook.canUpdate
readConfig = curBook.readConfig
} }
fun Book.update() { fun Book.update() {
@@ -815,7 +815,7 @@ class AnalyzeRule(
if (bookSource == null || book == null) return if (bookSource == null || book == null) return
runBlocking(coroutineContext) { runBlocking(coroutineContext) {
withTimeout(1800000) { withTimeout(1800000) {
WebBook.getBookInfoAwait(bookSource, book) WebBook.getBookInfoAwait(bookSource, book, false)
} }
} }
} }
@@ -829,7 +829,7 @@ class AnalyzeRule(
if (bookSource == null || book == null) return if (bookSource == null || book == null) return
runBlocking(coroutineContext) { runBlocking(coroutineContext) {
withTimeout(1800000) { withTimeout(1800000) {
WebBook.getBookInfoAwait(bookSource, book) WebBook.getBookInfoAwait(bookSource, book, false)
} }
} }
} }
@@ -32,7 +32,9 @@ object BookList {
baseUrl: String, baseUrl: String,
body: String?, body: String?,
isSearch: Boolean = true, isSearch: Boolean = true,
isRedirect: Boolean = false isRedirect: Boolean = false,
filter: ((name: String, author: String) -> Boolean)? = null,
shouldBreak: ((size: Int) -> Boolean)? = null
): ArrayList<SearchBook> { ): ArrayList<SearchBook> {
body ?: throw NoStackTraceException( body ?: throw NoStackTraceException(
appCtx.getString( appCtx.getString(
@@ -107,6 +109,7 @@ object BookList {
getSearchItem( getSearchItem(
bookSource, analyzeRule, item, baseUrl, ruleData.getVariable(), bookSource, analyzeRule, item, baseUrl, ruleData.getVariable(),
index == 0, index == 0,
filter,
ruleName = ruleName, ruleName = ruleName,
ruleBookUrl = ruleBookUrl, ruleBookUrl = ruleBookUrl,
ruleAuthor = ruleAuthor, ruleAuthor = ruleAuthor,
@@ -121,6 +124,9 @@ object BookList {
} }
bookList.add(searchBook) bookList.add(searchBook)
} }
if (shouldBreak?.invoke(bookList.size) == true) {
break
}
} }
val lh = LinkedHashSet(bookList) val lh = LinkedHashSet(bookList)
bookList.clear() bookList.clear()
@@ -177,6 +183,7 @@ object BookList {
baseUrl: String, baseUrl: String,
variable: String?, variable: String?,
log: Boolean, log: Boolean,
filter: ((name: String, author: String) -> Boolean)? = null,
ruleName: List<AnalyzeRule.SourceRule>, ruleName: List<AnalyzeRule.SourceRule>,
ruleBookUrl: List<AnalyzeRule.SourceRule>, ruleBookUrl: List<AnalyzeRule.SourceRule>,
ruleAuthor: List<AnalyzeRule.SourceRule>, ruleAuthor: List<AnalyzeRule.SourceRule>,
@@ -202,6 +209,9 @@ object BookList {
Debug.log(bookSource.bookSourceUrl, "┌获取作者", log) Debug.log(bookSource.bookSourceUrl, "┌获取作者", log)
searchBook.author = BookHelp.formatBookAuthor(analyzeRule.getString(ruleAuthor)) searchBook.author = BookHelp.formatBookAuthor(analyzeRule.getString(ruleAuthor))
Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}", log) Debug.log(bookSource.bookSourceUrl, "└${searchBook.author}", log)
if (filter?.invoke(searchBook.name, searchBook.author) == false) {
return null
}
coroutineContext.ensureActive() coroutineContext.ensureActive()
Debug.log(bookSource.bookSourceUrl, "┌获取分类", log) Debug.log(bookSource.bookSourceUrl, "┌获取分类", log)
try { try {
@@ -85,7 +85,12 @@ class SearchModel(private val scope: CoroutineScope, private val callBack: CallB
callBack.onSearchStart() callBack.onSearchStart()
}.mapParallelSafe(threadCount) { }.mapParallelSafe(threadCount) {
withTimeout(30000L) { withTimeout(30000L) {
WebBook.searchBookAwait(it, searchKey, searchPage) WebBook.searchBookAwait(
it, searchKey, searchPage,
filter = { name, author ->
!precision || name.contains(searchKey) ||
author.contains(searchKey)
})
} }
}.onEach { items -> }.onEach { items ->
for (book in items) { for (book in items) {
@@ -49,6 +49,8 @@ object WebBook {
bookSource: BookSource, bookSource: BookSource,
key: String, key: String,
page: Int? = 1, page: Int? = 1,
filter: ((name: String, author: String) -> Boolean)? = null,
shouldBreak: ((size: Int) -> Boolean)? = null
): ArrayList<SearchBook> { ): ArrayList<SearchBook> {
val searchUrl = bookSource.searchUrl val searchUrl = bookSource.searchUrl
if (searchUrl.isNullOrBlank()) { if (searchUrl.isNullOrBlank()) {
@@ -80,7 +82,9 @@ object WebBook {
baseUrl = res.url, baseUrl = res.url,
body = res.body, body = res.body,
isSearch = true, isSearch = true,
isRedirect = res.raw.priorResponse?.isRedirect == true isRedirect = res.raw.priorResponse?.isRedirect == true,
filter = filter,
shouldBreak = shouldBreak
) )
} }
@@ -207,21 +211,16 @@ object WebBook {
} }
} }
suspend fun runPreUpdateJs(bookSource: BookSource, book: Book): Result<Boolean> { suspend fun runPreUpdateJs(bookSource: BookSource, book: Book): Result<Unit> {
return kotlin.runCatching { return kotlin.runCatching {
val preUpdateJs = bookSource.ruleToc?.preUpdateJs val preUpdateJs = bookSource.ruleToc?.preUpdateJs
if (!preUpdateJs.isNullOrBlank()) { if (!preUpdateJs.isNullOrBlank()) {
kotlin.runCatching { AnalyzeRule(book, bookSource)
AnalyzeRule(book, bookSource) .setCoroutineContext(coroutineContext)
.setCoroutineContext(coroutineContext) .evalJS(preUpdateJs)
.evalJS(preUpdateJs)
}.onFailure {
AppLog.put("执行preUpdateJs规则失败 书源:${bookSource.bookSourceName}", it)
throw it
}
return@runCatching true
} }
return@runCatching false }.onFailure {
AppLog.put("执行preUpdateJs规则失败 书源:${bookSource.bookSourceName}", it)
} }
} }
@@ -386,9 +385,11 @@ object WebBook {
): Result<Book> { ): Result<Book> {
return kotlin.runCatching { return kotlin.runCatching {
scope.isActive scope.isActive
searchBookAwait(bookSource, name).firstOrNull { searchBookAwait(
it.name == name && it.author == author bookSource, name,
}?.let { searchBook -> filter = { fName, fAuthor -> fName == name && fAuthor == author },
shouldBreak = { it > 0 }
).firstOrNull()?.let { searchBook ->
scope.isActive scope.isActive
var book = searchBook.toBook() var book = searchBook.toBook()
if (book.tocUrl.isBlank()) { if (book.tocUrl.isBlank()) {
@@ -137,7 +137,9 @@ class ChangeCoverViewModel(application: Application) : BaseViewModel(application
if (source.getSearchRule().coverUrl.isNullOrBlank()) { if (source.getSearchRule().coverUrl.isNullOrBlank()) {
return return
} }
val searchBook = WebBook.searchBookAwait(source, name).firstOrNull() ?: return val searchBook = WebBook.searchBookAwait(
source, name,
shouldBreak = { it > 0 }).firstOrNull() ?: return
if (searchBook.name == name && searchBook.author == author if (searchBook.name == name && searchBook.author == author
&& !searchBook.coverUrl.isNullOrEmpty() && !searchBook.coverUrl.isNullOrEmpty()
) { ) {
@@ -237,18 +237,18 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
} }
private suspend fun search(source: BookSource) { private suspend fun search(source: BookSource) {
val resultBooks = WebBook.searchBookAwait(source, name) val checkAuthor = AppConfig.changeSourceCheckAuthor
resultBooks.filter { searchBook -> val loadInfo = AppConfig.changeSourceLoadInfo
if (searchBook.name != name) { val loadToc = AppConfig.changeSourceLoadToc
return@filter false val loadWordCount = AppConfig.changeSourceLoadWordCount
} val resultBooks = WebBook.searchBookAwait(
if (AppConfig.changeSourceCheckAuthor && !searchBook.author.contains(author)) { source, name,
return@filter false filter = { fName, fAuthor ->
} fName == name && (!checkAuthor || fAuthor.contains(author))
true })
}.forEach { searchBook -> resultBooks.forEach { searchBook ->
when { when {
AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc || AppConfig.changeSourceLoadWordCount -> { loadInfo || loadToc || loadWordCount -> {
loadBookInfo(source, searchBook.toBook()) loadBookInfo(source, searchBook.toBook())
} }
@@ -98,7 +98,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
bookSource = if (book.isLocal) null else bookSource = if (book.isLocal) null else
appDb.bookSourceDao.getBookSource(book.origin) appDb.bookSourceDao.getBookSource(book.origin)
if (book.tocUrl.isEmpty() && !book.isLocal) { if (book.tocUrl.isEmpty() && !book.isLocal) {
loadBookInfo(book) loadBookInfo(book, runPreUpdateJs = inBookshelf)
} else { } else {
val chapterList = appDb.bookChapterDao.getChapterList(book.bookUrl) val chapterList = appDb.bookChapterDao.getChapterList(book.bookUrl)
if (chapterList.isNotEmpty()) { if (chapterList.isNotEmpty()) {
@@ -166,12 +166,13 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
fun loadBookInfo( fun loadBookInfo(
book: Book, book: Book,
canReName: Boolean = true, canReName: Boolean = true,
runPreUpdateJs: Boolean = true,
scope: CoroutineScope = viewModelScope scope: CoroutineScope = viewModelScope
) { ) {
if (book.isLocal) { if (book.isLocal) {
LocalBook.upBookInfo(book) LocalBook.upBookInfo(book)
bookData.postValue(book) bookData.postValue(book)
loadChapter(book, scope) loadChapter(book)
} else { } else {
val bookSource = bookSource ?: let { val bookSource = bookSource ?: let {
chapterListData.postValue(emptyList()) chapterListData.postValue(emptyList())
@@ -192,22 +193,12 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
} }
bookData.postValue(it) bookData.postValue(it)
if (inBookshelf) { if (inBookshelf) {
if (!appDb.bookDao.has(it.bookUrl)) { it.save()
/**
* 来自搜索,同一本书,不同 bookUrl
*/
appDb.bookDao.insert(it)
} else {
appDb.bookDao.update(it)
}
if (dbBook != null && (dbBook.name != book.name || dbBook.bookUrl != book.bookUrl)) {
BookHelp.updateCacheFolder(dbBook, book)
}
} }
if (it.isWebFile) { if (it.isWebFile) {
loadWebFile(it, scope) loadWebFile(it)
} else { } else {
loadChapter(it, scope) loadChapter(it, runPreUpdateJs)
} }
}.onError { }.onError {
AppLog.put("获取书籍信息失败\n${it.localizedMessage}", it) AppLog.put("获取书籍信息失败\n${it.localizedMessage}", it)
@@ -218,6 +209,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
private fun loadChapter( private fun loadChapter(
book: Book, book: Book,
runPreUpdateJs: Boolean = true,
scope: CoroutineScope = viewModelScope scope: CoroutineScope = viewModelScope
) { ) {
if (book.isLocal) { if (book.isLocal) {
@@ -240,16 +232,14 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) {
return return
} }
val oldBook = book.copy() val oldBook = book.copy()
WebBook.getChapterList(scope, bookSource, book, true) WebBook.getChapterList(scope, bookSource, book, runPreUpdateJs)
.onSuccess(IO) { .onSuccess(IO) {
/**
* runPreUpdateJs 有可能会修改 book 的书名作者和 bookUrl
*/
if (inBookshelf) { if (inBookshelf) {
if (oldBook.bookUrl == book.bookUrl) { book.save()
appDb.bookDao.update(book) /**
} else { * runPreUpdateJs 有可能会修改 book 的 bookUrl
appDb.bookDao.insert(book) */
if (oldBook.bookUrl != book.bookUrl) {
BookHelp.updateCacheFolder(oldBook, book) BookHelp.updateCacheFolder(oldBook, book)
} }
appDb.bookChapterDao.delByBook(oldBook.bookUrl) appDb.bookChapterDao.delByBook(oldBook.bookUrl)
@@ -152,9 +152,10 @@ class MainViewModel(application: Application) : BaseViewModel(application) {
} }
kotlin.runCatching { kotlin.runCatching {
val oldBook = book.copy() val oldBook = book.copy()
WebBook.runPreUpdateJs(source, book)
if (book.tocUrl.isBlank()) { if (book.tocUrl.isBlank()) {
WebBook.getBookInfoAwait(source, book) WebBook.getBookInfoAwait(source, book)
} else {
WebBook.runPreUpdateJs(source, book)
} }
val toc = WebBook.getChapterListAwait(source, book).getOrThrow() val toc = WebBook.getChapterListAwait(source, book).getOrThrow()
book.sync(oldBook) book.sync(oldBook)