This commit is contained in:
Horis
2023-05-08 17:33:59 +08:00
parent 660a342b93
commit 7e5d8bbd7e
7 changed files with 1987 additions and 86 deletions
File diff suppressed because it is too large Load Diff
@@ -19,9 +19,11 @@ object SourceHelp {
private val list18Plus by lazy { private val list18Plus by lazy {
try { try {
return@lazy String(appCtx.assets.open("18PlusList.txt").readBytes()) return@lazy String(appCtx.assets.open("18PlusList.txt").readBytes())
.splitNotBlank("\n") .splitNotBlank("\n").map {
EncoderUtils.base64Decode(it)
}.toHashSet()
} catch (e: Exception) { } catch (e: Exception) {
return@lazy arrayOf<String>() return@lazy hashSetOf<String>()
} }
} }
@@ -32,42 +34,42 @@ object SourceHelp {
} }
fun insertRssSource(vararg rssSources: RssSource) { fun insertRssSource(vararg rssSources: RssSource) {
rssSources.forEach { rssSource -> val rssSourcesGroup = rssSources.groupBy {
if (is18Plus(rssSource.sourceUrl)) { is18Plus(it.sourceUrl)
}
rssSourcesGroup[true]?.forEach {
handler.post { handler.post {
appCtx.toastOnUi("${rssSource.sourceName}是18+网址,禁止导入.") appCtx.toastOnUi("${it.sourceName}是18+网址,禁止导入.")
} }
} else {
appDb.rssSourceDao.insert(rssSource)
} }
rssSourcesGroup[false]?.let {
appDb.rssSourceDao.insert(*it.toTypedArray())
} }
} }
fun insertBookSource(vararg bookSources: BookSource) { fun insertBookSource(vararg bookSources: BookSource) {
bookSources.forEach { bookSource -> val bookSourcesGroup = bookSources.groupBy {
if (is18Plus(bookSource.bookSourceUrl)) { is18Plus(it.bookSourceUrl)
}
bookSourcesGroup[true]?.forEach {
handler.post { handler.post {
appCtx.toastOnUi("${bookSource.bookSourceName}是18+网址,禁止导入.") appCtx.toastOnUi("${it.bookSourceName}是18+网址,禁止导入.")
} }
} else {
appDb.bookSourceDao.insert(bookSource)
} }
bookSourcesGroup[false]?.let {
appDb.bookSourceDao.insert(*it.toTypedArray())
} }
} }
private fun is18Plus(url: String?): Boolean { private fun is18Plus(url: String?): Boolean {
url ?: return false url ?: return false
val baseUrl = NetworkUtils.getBaseUrl(url)
baseUrl ?: return false
if (AppConst.isPlayChannel) return false if (AppConst.isPlayChannel) return false
val baseUrl = NetworkUtils.getBaseUrl(url) ?: return false
kotlin.runCatching { kotlin.runCatching {
val host = baseUrl.split("//", ".") val host = baseUrl.split("//", ".").let {
val base64Url = EncoderUtils.base64Encode("${host[host.lastIndex - 1]}.${host.last()}") if (it.size > 2) "${it[it.lastIndex - 1]}.${it.last()}" else return false
list18Plus.forEach {
if (base64Url == it) {
return true
}
} }
return list18Plus.contains(host)
} }
return false return false
} }
@@ -307,7 +307,6 @@ object ReadBook : CoroutineScope by MainScope() {
contentLoadFinish(book, chapter, it, upContent, resetPageOffset) { contentLoadFinish(book, chapter, it, upContent, resetPageOffset) {
success?.invoke() success?.invoke()
} }
removeLoading(chapter.index)
} ?: download(this, chapter, resetPageOffset = resetPageOffset) } ?: download(this, chapter, resetPageOffset = resetPageOffset)
} ?: removeLoading(index) } ?: removeLoading(index)
}.onError { }.onError {
@@ -320,16 +319,15 @@ object ReadBook : CoroutineScope by MainScope() {
/** /**
* 下载正文 * 下载正文
*/ */
private fun download(index: Int) { private fun downloadIndex(index: Int) {
if (index < 0) return if (index < 0) return
if (index > chapterSize - 1) { if (index > chapterSize - 1) {
upToc() upToc()
return return
} }
book?.let { book -> val book = book ?: return
if (book.isLocal) return
if (addLoading(index)) { if (addLoading(index)) {
Coroutine.async { try {
appDb.bookChapterDao.getChapter(book.bookUrl, index)?.let { chapter -> appDb.bookChapterDao.getChapter(book.bookUrl, index)?.let { chapter ->
if (BookHelp.hasContent(book, chapter)) { if (BookHelp.hasContent(book, chapter)) {
removeLoading(chapter.index) removeLoading(chapter.index)
@@ -337,12 +335,11 @@ object ReadBook : CoroutineScope by MainScope() {
download(this, chapter, false) download(this, chapter, false)
} }
} ?: removeLoading(index) } ?: removeLoading(index)
}.onError { } catch (e: Exception) {
removeLoading(index) removeLoading(index)
} }
} }
} }
}
/** /**
* 下载正文 * 下载正文
@@ -353,20 +350,17 @@ object ReadBook : CoroutineScope by MainScope() {
resetPageOffset: Boolean, resetPageOffset: Boolean,
success: (() -> Unit)? = null success: (() -> Unit)? = null
) { ) {
val book = book val book = book ?: return removeLoading(chapter.index)
val bookSource = bookSource val bookSource = bookSource
if (book != null && bookSource != null) { if (bookSource != null) {
CacheBook.getOrCreate(bookSource, book).download(scope, chapter) CacheBook.getOrCreate(bookSource, book).download(scope, chapter)
} else if (book != null) { } else {
val msg = if (book.isLocal) "无内容" else "没有书源" val msg = if (book.isLocal) "无内容" else "没有书源"
contentLoadFinish( contentLoadFinish(
book, chapter, "加载正文失败\n$msg", resetPageOffset = resetPageOffset book, chapter, "加载正文失败\n$msg", resetPageOffset = resetPageOffset
) { ) {
success?.invoke() success?.invoke()
} }
removeLoading(chapter.index)
} else {
removeLoading(chapter.index)
} }
} }
@@ -415,10 +409,12 @@ object ReadBook : CoroutineScope by MainScope() {
curPageChanged() curPageChanged()
callBack?.contentLoadFinish() callBack?.contentLoadFinish()
} }
-1 -> { -1 -> {
prevTextChapter = textChapter prevTextChapter = textChapter
if (upContent) callBack?.upContent(offset, resetPageOffset) if (upContent) callBack?.upContent(offset, resetPageOffset)
} }
1 -> { 1 -> {
nextTextChapter = textChapter nextTextChapter = textChapter
if (upContent) callBack?.upContent(offset, resetPageOffset) if (upContent) callBack?.upContent(offset, resetPageOffset)
@@ -464,7 +460,7 @@ object ReadBook : CoroutineScope by MainScope() {
fun saveRead() { fun saveRead() {
Coroutine.async { Coroutine.async {
book?.let { book -> val book = book ?: return@async
book.lastCheckCount = 0 book.lastCheckCount = 0
book.durChapterTime = System.currentTimeMillis() book.durChapterTime = System.currentTimeMillis()
book.durChapterIndex = durChapterIndex book.durChapterIndex = durChapterIndex
@@ -477,12 +473,12 @@ object ReadBook : CoroutineScope by MainScope() {
appDb.bookDao.update(book) appDb.bookDao.update(book)
} }
} }
}
/** /**
* 预下载 * 预下载
*/ */
private fun preDownload() { private fun preDownload() {
if (book?.isLocal == true) return
if (AppConfig.preDownloadNum < 2) { if (AppConfig.preDownloadNum < 2) {
return return
} }
@@ -491,12 +487,12 @@ object ReadBook : CoroutineScope by MainScope() {
val maxChapterIndex = durChapterIndex + AppConfig.preDownloadNum val maxChapterIndex = durChapterIndex + AppConfig.preDownloadNum
for (i in durChapterIndex.plus(2)..maxChapterIndex) { for (i in durChapterIndex.plus(2)..maxChapterIndex) {
delay(1000) delay(1000)
download(i) downloadIndex(i)
} }
val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum) val minChapterIndex = durChapterIndex - min(5, AppConfig.preDownloadNum)
for (i in durChapterIndex.minus(2) downTo minChapterIndex) { for (i in durChapterIndex.minus(2) downTo minChapterIndex) {
delay(1000) delay(1000)
download(i) downloadIndex(i)
} }
} }
} }
@@ -27,6 +27,7 @@ import io.legado.app.databinding.DialogBookChangeSourceBinding
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.ui.widget.dialog.WaitDialog import io.legado.app.ui.widget.dialog.WaitDialog
@@ -86,7 +87,7 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
binding.toolBar.setBackgroundColor(primaryColor) binding.toolBar.setBackgroundColor(primaryColor)
viewModel.initData(arguments) viewModel.initData(arguments, callBack?.oldBook, activity is ReadBookActivity)
showTitle() showTitle()
initMenu() initMenu()
initRecyclerView() initRecyclerView()
@@ -212,19 +213,23 @@ class ChangeBookSourceDialog() : BaseDialogFragment(R.layout.dialog_book_change_
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
viewModel.refresh() viewModel.refresh()
} }
R.id.menu_load_info -> { R.id.menu_load_info -> {
AppConfig.changeSourceLoadInfo = !item.isChecked AppConfig.changeSourceLoadInfo = !item.isChecked
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
} }
R.id.menu_load_toc -> { R.id.menu_load_toc -> {
AppConfig.changeSourceLoadToc = !item.isChecked AppConfig.changeSourceLoadToc = !item.isChecked
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
} }
R.id.menu_load_word_count -> { R.id.menu_load_word_count -> {
AppConfig.changeSourceLoadWordCount = !item.isChecked AppConfig.changeSourceLoadWordCount = !item.isChecked
item.isChecked = !item.isChecked item.isChecked = !item.isChecked
viewModel.onLoadWordCountChecked(item.isChecked) viewModel.onLoadWordCountChecked(item.isChecked)
} }
R.id.menu_start_stop -> viewModel.startOrStopSearch() R.id.menu_start_stop -> viewModel.startOrStopSearch()
R.id.menu_source_manage -> startActivity<BookSourceActivity>() R.id.menu_source_manage -> startActivity<BookSourceActivity>()
R.id.menu_refresh_list -> viewModel.startRefreshList() R.id.menu_refresh_list -> viewModel.startRefreshList()
@@ -16,12 +16,14 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.SearchBook
import io.legado.app.exception.NoStackTraceException import io.legado.app.exception.NoStackTraceException
import io.legado.app.help.book.BookHelp
import io.legado.app.help.config.AppConfig import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.SourceConfig import io.legado.app.help.config.SourceConfig
import io.legado.app.help.coroutine.CompositeCoroutine import io.legado.app.help.coroutine.CompositeCoroutine
import io.legado.app.help.coroutine.Coroutine import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.ReadBook import io.legado.app.model.ReadBook
import io.legado.app.model.webBook.WebBook import io.legado.app.model.webBook.WebBook
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.utils.getPrefBoolean import io.legado.app.utils.getPrefBoolean
import io.legado.app.utils.postEvent import io.legado.app.utils.postEvent
import io.legado.app.utils.toastOnUi import io.legado.app.utils.toastOnUi
@@ -44,6 +46,8 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
var searchFinishCallback: ((isEmpty: Boolean) -> Unit)? = null var searchFinishCallback: ((isEmpty: Boolean) -> Unit)? = null
var name: String = "" var name: String = ""
var author: String = "" var author: String = ""
private var fromReadBookActivity = false
private var oldBook: Book? = null
private var tasks = CompositeCoroutine() private var tasks = CompositeCoroutine()
private var screenKey: String = "" private var screenKey: String = ""
private var bookSourceList = arrayListOf<BookSource>() private var bookSourceList = arrayListOf<BookSource>()
@@ -114,7 +118,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
} }
@CallSuper @CallSuper
open fun initData(arguments: Bundle?) { open fun initData(arguments: Bundle?, book: Book?, fromReadBookActivity: Boolean) {
arguments?.let { bundle -> arguments?.let { bundle ->
bundle.getString("name")?.let { bundle.getString("name")?.let {
name = it name = it
@@ -122,6 +126,8 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
bundle.getString("author")?.let { bundle.getString("author")?.let {
author = it.replace(AppPattern.authorRegex, "") author = it.replace(AppPattern.authorRegex, "")
} }
this.fromReadBookActivity = fromReadBookActivity
oldBook = book
} }
} }
@@ -180,25 +186,27 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
val task = Coroutine.async(scope = viewModelScope, context = searchPool!!) { val task = Coroutine.async(scope = viewModelScope, context = 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) {
if ((AppConfig.changeSourceCheckAuthor && searchBook.author.contains(author)) return@forEach
|| !AppConfig.changeSourceCheckAuthor }
) { if (AppConfig.changeSourceCheckAuthor && !searchBook.author.contains(author)) {
return@forEach
}
when { when {
searchBook.latestChapterTitle.isNullOrEmpty() && searchBook.latestChapterTitle.isNullOrEmpty() &&
(AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) -> { (AppConfig.changeSourceLoadInfo || AppConfig.changeSourceLoadToc) -> {
loadBookInfo(source, searchBook.toBook()) loadBookInfo(source, searchBook.toBook())
} }
searchBook.chapterWordCountText.isNullOrBlank() && AppConfig.changeSourceLoadWordCount -> {
AppConfig.changeSourceLoadWordCount -> {
loadBookToc(source, searchBook.toBook()) loadBookToc(source, searchBook.toBook())
} }
else -> { else -> {
searchCallback?.searchSuccess(searchBook) searchCallback?.searchSuccess(searchBook)
} }
} }
} }
}
}
}.timeout(60000L) }.timeout(60000L)
.onError { .onError {
nextSearch() nextSearch()
@@ -211,7 +219,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
private suspend fun loadBookInfo(source: BookSource, book: Book) { private suspend fun loadBookInfo(source: BookSource, book: Book) {
WebBook.getBookInfoAwait(source, book) WebBook.getBookInfoAwait(source, book)
if (context.getPrefBoolean(PreferKey.changeSourceLoadToc)) { if (AppConfig.changeSourceLoadToc || AppConfig.changeSourceLoadWordCount) {
loadBookToc(source, book) loadBookToc(source, book)
} else { } else {
//从详情页里获取最新章节 //从详情页里获取最新章节
@@ -224,7 +232,7 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
val chapters = WebBook.getChapterListAwait(source, book).getOrThrow() val chapters = WebBook.getChapterListAwait(source, book).getOrThrow()
tocMap[book.bookUrl] = chapters tocMap[book.bookUrl] = chapters
bookMap[book.bookUrl] = book bookMap[book.bookUrl] = book
if (context.getPrefBoolean(PreferKey.changeSourceLoadWordCount)) { if (AppConfig.changeSourceLoadWordCount) {
loadBookWordCount(source, book, chapters) loadBookWordCount(source, book, chapters)
} else { } else {
val searchBook = book.toSearchBook() val searchBook = book.toSearchBook()
@@ -237,16 +245,26 @@ open class ChangeBookSourceViewModel(application: Application) : BaseViewModel(a
book: Book, book: Book,
chapters: List<BookChapter> chapters: List<BookChapter>
) = coroutineScope { ) = coroutineScope {
val chapterIndex = ReadBook.curTextChapter?.chapter?.index ?: (chapters.size - 1) val chapterIndex = if (fromReadBookActivity) {
oldBook?.let {
BookHelp.getDurChapter(
it.durChapterIndex,
it.durChapterTitle,
chapters,
it.totalChapterNum
)
} ?: chapters.lastIndex
} else chapters.lastIndex
val bookChapter = chapters.getOrNull(chapterIndex) val bookChapter = chapters.getOrNull(chapterIndex)
val startTime = System.currentTimeMillis() val startTime = System.currentTimeMillis()
val pair = try { val pair = try {
if (bookChapter == null) throw NoStackTraceException("章节缺失,总章节数${chapters.size}") if (bookChapter == null) throw NoStackTraceException("章节缺失,总章节数${chapters.size}")
if (!isActive) return@coroutineScope val nextChapterUrl = chapters.getOrNull(chapterIndex + 1)?.url
WebBook.getContentAwait(source, book, bookChapter, null, false).length.let { WebBook.getContentAwait(source, book, bookChapter, nextChapterUrl, false).length.let {
it to "${chapterIndex + 1}章 字数:${it}" it to "${chapterIndex + 1}章 字数:${it}"
} }
} catch (t: Throwable) { } catch (t: Throwable) {
if (t is CancellationException) throw t
-1 to "${chapterIndex + 1}章 获取字数失败:${t.localizedMessage}" -1 to "${chapterIndex + 1}章 获取字数失败:${t.localizedMessage}"
} }
val endTime = System.currentTimeMillis() val endTime = System.currentTimeMillis()
@@ -32,6 +32,7 @@ import io.legado.app.help.config.AppConfig
import io.legado.app.lib.dialogs.alert import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.elevation import io.legado.app.lib.theme.elevation
import io.legado.app.lib.theme.primaryColor import io.legado.app.lib.theme.primaryColor
import io.legado.app.ui.book.read.ReadBookActivity
import io.legado.app.ui.book.source.edit.BookSourceEditActivity import io.legado.app.ui.book.source.edit.BookSourceEditActivity
import io.legado.app.ui.book.source.manage.BookSourceActivity import io.legado.app.ui.book.source.manage.BookSourceActivity
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
@@ -112,7 +113,7 @@ class ChangeChapterSourceDialog() : BaseDialogFragment(R.layout.dialog_chapter_c
override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) { override fun onFragmentCreated(view: View, savedInstanceState: Bundle?) {
binding.toolBar.setBackgroundColor(primaryColor) binding.toolBar.setBackgroundColor(primaryColor)
viewModel.initData(arguments) viewModel.initData(arguments, callBack?.oldBook, activity is ReadBookActivity)
showTitle() showTitle()
initMenu() initMenu()
initView() initView()
@@ -15,8 +15,8 @@ class ChangeChapterSourceViewModel(application: Application) :
var chapterIndex: Int = 0 var chapterIndex: Int = 0
var chapterTitle: String = "" var chapterTitle: String = ""
override fun initData(arguments: Bundle?) { override fun initData(arguments: Bundle?, book: Book?, fromReadBookActivity: Boolean) {
super.initData(arguments) super.initData(arguments, book, fromReadBookActivity)
arguments?.let { bundle -> arguments?.let { bundle ->
bundle.getString("chapterTitle")?.let { bundle.getString("chapterTitle")?.let {
chapterTitle = it chapterTitle = it