This commit is contained in:
Horis
2024-01-01 12:28:32 +08:00
parent 8b1c2f3f4f
commit 4040be7bf0
15 changed files with 80 additions and 259 deletions
@@ -2,7 +2,11 @@ package io.legado.app.model
import io.legado.app.constant.AppLog import io.legado.app.constant.AppLog
import io.legado.app.data.appDb import io.legado.app.data.appDb
import io.legado.app.data.entities.* import io.legado.app.data.entities.Book
import io.legado.app.data.entities.BookChapter
import io.legado.app.data.entities.BookProgress
import io.legado.app.data.entities.BookSource
import io.legado.app.data.entities.ReadRecord
import io.legado.app.help.AppWebDav import io.legado.app.help.AppWebDav
import io.legado.app.help.book.BookHelp import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.ContentProcessor import io.legado.app.help.book.ContentProcessor
@@ -88,7 +92,7 @@ object ReadBook : CoroutineScope by MainScope() {
upWebBook(book) upWebBook(book)
lastBookPress = null lastBookPress = null
webBookProgress = null webBookProgress = null
TextFile.txtBuffer = null TextFile.clear()
synchronized(this) { synchronized(this) {
loadingChapters.clear() loadingChapters.clear()
} }
@@ -82,7 +82,7 @@ object LocalBook {
} }
} }
@Throws(Exception::class) @Throws(TocEmptyException::class)
fun getChapterList(book: Book): ArrayList<BookChapter> { fun getChapterList(book: Book): ArrayList<BookChapter> {
val chapters = when { val chapters = when {
book.isEpub -> { book.isEpub -> {
@@ -16,56 +16,37 @@ import java.util.regex.Matcher
import java.util.regex.Pattern import java.util.regex.Pattern
import kotlin.math.min import kotlin.math.min
class TextFile(private val book: Book) { class TextFile(private var book: Book) {
@Suppress("ConstPropertyName")
companion object { companion object {
private val padRegex = "^[\\n\\s]+".toRegex() private val padRegex = "^[\\n\\s]+".toRegex()
private const val bufferSize = 8 * 1024 * 1024 private const val txtBufferSize = 8 * 1024 * 1024
private var bufferStart = -1 private var textFile: TextFile? = null
private var bufferEnd = -1
var txtBuffer: ByteArray? = null @Synchronized
var bookUrl = "" private fun getTextFile(book: Book): TextFile {
if (textFile == null || textFile?.book?.bookUrl != book.bookUrl) {
textFile = TextFile(book)
return textFile!!
}
textFile?.book = book
return textFile!!
}
@Throws(FileNotFoundException::class) @Throws(FileNotFoundException::class)
fun getChapterList(book: Book): ArrayList<BookChapter> { fun getChapterList(book: Book): ArrayList<BookChapter> {
return TextFile(book).getChapterList() return getTextFile(book).getChapterList()
} }
@Synchronized @Synchronized
@Throws(FileNotFoundException::class) @Throws(FileNotFoundException::class)
fun getContent(book: Book, bookChapter: BookChapter): String { fun getContent(book: Book, bookChapter: BookChapter): String {
if (txtBuffer == null || bookUrl != book.bookUrl || bookChapter.start!! > bufferEnd || bookChapter.end!! < bufferStart) { return getTextFile(book).getContent(bookChapter)
bookUrl = book.bookUrl }
LocalBook.getBookInputStream(book).use { bis ->
bufferStart = bufferSize * (bookChapter.start!! / bufferSize).toInt()
txtBuffer = ByteArray(min(bufferSize, bis.available() - bufferStart))
bufferEnd = bufferStart + txtBuffer!!.size
bis.skip(bufferStart.toLong())
bis.read(txtBuffer)
}
}
val count = (bookChapter.end!! - bookChapter.start!!).toInt() fun clear() {
val buffer = ByteArray(count) textFile = null
if (bookChapter.start!! < bufferEnd && bookChapter.end!! > bufferEnd || bookChapter.start!! < bufferStart && bookChapter.end!! > bufferStart) {
/** 章节内容在缓冲区交界处 */
LocalBook.getBookInputStream(book).use { bis ->
bis.skip(bookChapter.start!!)
bis.read(buffer)
}
} else {
/** 章节内容在缓冲区内 */
txtBuffer!!.copyInto(
buffer,
0,
(bookChapter.start!! - bufferStart).toInt(),
(bookChapter.end!! - bufferStart).toInt()
)
}
return String(buffer, book.fileCharset()).substringAfter(bookChapter.title)
.replace(padRegex, "  ")
} }
} }
@@ -83,10 +64,14 @@ class TextFile(private val book: Book) {
private var charset: Charset = book.fileCharset() private var charset: Charset = book.fileCharset()
private var txtBuffer: ByteArray? = null
private var bufferStart = -1L
private var bufferEnd = -1L
/** /**
* 获取目录 * 获取目录
*/ */
@Throws(FileNotFoundException::class) @Throws(FileNotFoundException::class, SecurityException::class, EmptyFileException::class)
fun getChapterList(): ArrayList<BookChapter> { fun getChapterList(): ArrayList<BookChapter> {
if (book.charset == null || book.tocUrl.isBlank()) { if (book.charset == null || book.tocUrl.isBlank()) {
LocalBook.getBookInputStream(book).use { bis -> LocalBook.getBookInputStream(book).use { bis ->
@@ -112,6 +97,43 @@ class TextFile(private val book: Book) {
return toc return toc
} }
fun getContent(chapter: BookChapter): String {
val start = chapter.start!!
val end = chapter.end!!
if (txtBuffer == null || start > bufferEnd || end < bufferStart) {
LocalBook.getBookInputStream(book).use { bis ->
bufferStart = txtBufferSize * (start / txtBufferSize)
txtBuffer = ByteArray(min(txtBufferSize, bis.available() - bufferStart.toInt()))
bufferEnd = bufferStart + txtBuffer!!.size
bis.skip(bufferStart)
bis.read(txtBuffer)
}
}
val count = (end - start).toInt()
val buffer = ByteArray(count)
if (bufferEnd in start..end || bufferStart in start..end) {
/** 章节内容在缓冲区交界处 */
LocalBook.getBookInputStream(book).use { bis ->
bis.skip(start)
bis.read(buffer)
}
} else {
/** 章节内容在缓冲区内 */
txtBuffer!!.copyInto(
buffer,
0,
(start - bufferStart).toInt(),
(end - bufferStart).toInt()
)
}
return String(buffer, charset)
.substringAfter(chapter.title)
.replace(padRegex, "  ")
}
/** /**
* 按规则解析目录 * 按规则解析目录
*/ */
@@ -162,7 +184,7 @@ class TextFile(private val book: Book) {
val chapterStart = matcher.start() val chapterStart = matcher.start()
//获取章节内容 //获取章节内容
val chapterContent = blockContent.substring(seekPos, chapterStart) val chapterContent = blockContent.substring(seekPos, chapterStart)
val chapterLength = chapterContent.toByteArray(charset).size val chapterLength = chapterContent.toByteArray(charset).size.toLong()
val lastStart = toc.lastOrNull()?.start ?: curOffset val lastStart = toc.lastOrNull()?.start ?: curOffset
if (book.getSplitLongChapter() && curOffset + chapterLength - lastStart > maxLengthWithToc) { if (book.getSplitLongChapter() && curOffset + chapterLength - lastStart > maxLengthWithToc) {
toc.lastOrNull()?.let { toc.lastOrNull()?.let {
@@ -186,7 +208,7 @@ class TextFile(private val book: Book) {
curChapter.start = curOffset + chapterLength curChapter.start = curOffset + chapterLength
toc.add(curChapter) toc.add(curChapter)
} else if (seekPos == 0 && chapterStart != 0) { } else if (seekPos == 0 && chapterStart != 0) {
/* /**
* 如果 seekPos == 0 && chapterStart != 0 表示当前block处前面有一段内容 * 如果 seekPos == 0 && chapterStart != 0 表示当前block处前面有一段内容
* 第一种情况一定是序章 第二种情况是上一个章节的内容 * 第一种情况一定是序章 第二种情况是上一个章节的内容
*/ */
@@ -196,13 +218,18 @@ class TextFile(private val book: Book) {
val qyChapter = BookChapter() val qyChapter = BookChapter()
qyChapter.title = "前言" qyChapter.title = "前言"
qyChapter.start = curOffset qyChapter.start = curOffset
qyChapter.end = curOffset + chapterLength.toLong() qyChapter.end = curOffset + chapterLength
toc.add(qyChapter) toc.add(qyChapter)
book.intro = if (chapterContent.length <= 500) {
chapterContent
} else {
chapterContent.substring(0, 500)
}
} }
//创建当前章节 //创建当前章节
val curChapter = BookChapter() val curChapter = BookChapter()
curChapter.title = matcher.group() curChapter.title = matcher.group()
curChapter.start = curOffset + chapterLength.toLong() curChapter.start = curOffset + chapterLength
toc.add(curChapter) toc.add(curChapter)
} else { //否则就block分割之后,上一个章节的剩余内容 } else { //否则就block分割之后,上一个章节的剩余内容
//获取上一章节 //获取上一章节
@@ -210,7 +237,7 @@ class TextFile(private val book: Book) {
lastChapter.isVolume = lastChapter.isVolume =
chapterContent.substringAfter(lastChapter.title).isBlank() chapterContent.substringAfter(lastChapter.title).isBlank()
//将当前段落添加上一章去 //将当前段落添加上一章去
lastChapter.end = lastChapter.end!! + chapterLength.toLong() lastChapter.end = lastChapter.end!! + chapterLength
//创建当前章节 //创建当前章节
val curChapter = BookChapter() val curChapter = BookChapter()
curChapter.title = matcher.group() curChapter.title = matcher.group()
@@ -224,7 +251,7 @@ class TextFile(private val book: Book) {
lastChapter.isVolume = lastChapter.isVolume =
chapterContent.substringAfter(lastChapter.title).isBlank() chapterContent.substringAfter(lastChapter.title).isBlank()
lastChapter.end = lastChapter.end =
lastChapter.start!! + chapterContent.toByteArray(charset).size.toLong() lastChapter.start!! + chapterLength
//创建当前章节 //创建当前章节
val curChapter = BookChapter() val curChapter = BookChapter()
curChapter.title = matcher.group() curChapter.title = matcher.group()
@@ -1,45 +1,27 @@
package io.legado.app.ui.book.explore package io.legado.app.ui.book.explore
import android.os.Bundle import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R import io.legado.app.R
import io.legado.app.base.VMBaseActivity import io.legado.app.base.VMBaseActivity
import io.legado.app.constant.AppLog
import io.legado.app.data.appDb
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.databinding.ActivityExploreShowBinding import io.legado.app.databinding.ActivityExploreShowBinding
import io.legado.app.databinding.DialogPageChoiceBinding
import io.legado.app.databinding.ViewLoadMoreBinding import io.legado.app.databinding.ViewLoadMoreBinding
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.lib.dialogs.alert
import io.legado.app.lib.theme.backgroundColor
import io.legado.app.model.webBook.WebBook
import io.legado.app.ui.book.group.GroupSelectDialog
import io.legado.app.ui.book.info.BookInfoActivity import io.legado.app.ui.book.info.BookInfoActivity
import io.legado.app.ui.widget.dialog.WaitDialog
import io.legado.app.ui.widget.recycler.LoadMoreView import io.legado.app.ui.widget.recycler.LoadMoreView
import io.legado.app.ui.widget.recycler.VerticalDivider import io.legado.app.ui.widget.recycler.VerticalDivider
import io.legado.app.utils.showDialogFragment
import io.legado.app.utils.startActivity import io.legado.app.utils.startActivity
import io.legado.app.utils.viewbindingdelegate.viewBinding import io.legado.app.utils.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
class ExploreShowActivity : VMBaseActivity<ActivityExploreShowBinding, ExploreShowViewModel>(), class ExploreShowActivity : VMBaseActivity<ActivityExploreShowBinding, ExploreShowViewModel>(),
ExploreShowAdapter.CallBack, ExploreShowAdapter.CallBack {
GroupSelectDialog.CallBack {
override val binding by viewBinding(ActivityExploreShowBinding::inflate) override val binding by viewBinding(ActivityExploreShowBinding::inflate)
override val viewModel by viewModels<ExploreShowViewModel>() override val viewModel by viewModels<ExploreShowViewModel>()
private val adapter by lazy { ExploreShowAdapter(this, this) } private val adapter by lazy { ExploreShowAdapter(this, this) }
private val loadMoreView by lazy { LoadMoreView(this) } private val loadMoreView by lazy { LoadMoreView(this) }
private val waitDialog by lazy {
WaitDialog(this)
}
override fun onActivityCreated(savedInstanceState: Bundle?) { override fun onActivityCreated(savedInstanceState: Bundle?) {
binding.titleBar.title = intent.getStringExtra("exploreName") binding.titleBar.title = intent.getStringExtra("exploreName")
@@ -116,84 +98,4 @@ class ExploreShowActivity : VMBaseActivity<ActivityExploreShowBinding, ExploreSh
putExtra("bookUrl", book.bookUrl) putExtra("bookUrl", book.bookUrl)
} }
} }
override fun onCompatCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.explore_show, menu)
return super.onCompatCreateOptionsMenu(menu)
}
override fun onCompatOptionsItemSelected(item: MenuItem): Boolean {
when (item.itemId) {
R.id.menu_add_all_to_bookshelf -> addAllToBookshelf()
}
return super.onCompatOptionsItemSelected(item)
}
private fun addAllToBookshelf() {
showDialogFragment(GroupSelectDialog(0))
}
override fun upGroup(requestCode: Int, groupId: Long) {
alert("选择页数范围") {
val alertBinding = DialogPageChoiceBinding.inflate(layoutInflater).apply {
root.setBackgroundColor(root.context.backgroundColor)
}
customView { alertBinding.root }
yesButton {
alertBinding.run {
val start = editStart.text
.runCatching {
toString().toInt()
}.getOrDefault(1)
val end = editEnd.text
.runCatching {
toString().toInt()
}.getOrDefault(9)
addAllToBookshelf(start, end, groupId)
}
}
noButton()
}
}
private fun addAllToBookshelf(start: Int, end: Int, groupId: Long) {
val job = Coroutine.async {
launch(Main) {
waitDialog.setText("加载列表中...")
waitDialog.show()
}
val searchBooks = viewModel.loadExploreBooks(start, end)
val books = searchBooks.map {
it.toBook()
}
launch(Main) {
waitDialog.setText("添加书架中...")
}
books.forEach {
appDb.bookDao.getBook(it.bookUrl)?.let { book ->
book.group = book.group or groupId
it.order = appDb.bookDao.minOrder - 1
book.save()
return@forEach
}
if (it.tocUrl.isEmpty()) {
val source = appDb.bookSourceDao.getBookSource(it.origin)!!
WebBook.getBookInfoAwait(source, it)
}
it.order = appDb.bookDao.minOrder - 1
it.group = groupId
it.save()
}
}.onError {
AppLog.put("添加书架出错\n${it.localizedMessage}", it)
}.onFinally {
waitDialog.dismiss()
}
waitDialog.setOnCancelListener {
job.cancel()
}
}
} }
@@ -70,18 +70,4 @@ class ExploreShowViewModel(application: Application) : BaseViewModel(application
} }
} }
suspend fun loadExploreBooks(start: Int, end: Int): List<SearchBook> {
val source = bookSource
val url = exploreUrl
if (source == null || url == null) return emptyList()
val searchBooks = arrayListOf<SearchBook>()
for (page in start..end) {
val books = WebBook.exploreBookAwait(source, url, page)
if (books.isEmpty()) break
searchBooks.addAll(books)
}
searchBooks.reverse()
return searchBooks
}
} }
@@ -1,74 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/ll_content"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/background"
android:gravity="center"
android:orientation="horizontal"
android:padding="16dp">
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
<EditText
android:id="@+id/edit_start"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@drawable/bg_edit"
android:hint="@string/start"
android:importantForAutofill="no"
android:inputType="number"
android:lines="1"
android:maxLength="5"
android:minWidth="60dp"
android:paddingLeft="5dp"
android:paddingTop="4dp"
android:paddingRight="5dp"
android:paddingBottom="4dp"
android:textColor="@color/primaryText"
android:textCursorDrawable="@drawable/shape_text_cursor"
android:textSize="14sp"
tools:ignore="TouchTargetSizeCheck,TextContrastCheck" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:layout_marginLeft="5dp"
android:layout_marginRight="5dp"
android:text="@string/page_to"
android:textColor="@color/primaryText"
android:textSize="16sp" />
<EditText
android:id="@+id/edit_end"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:background="@drawable/bg_edit"
android:hint="@string/end"
android:importantForAutofill="no"
android:inputType="number"
android:lines="1"
android:maxLength="5"
android:minWidth="60dp"
android:paddingLeft="5dp"
android:paddingTop="4dp"
android:paddingRight="5dp"
android:paddingBottom="4dp"
android:textColor="@color/primaryText"
android:textCursorDrawable="@drawable/shape_text_cursor"
android:textSize="14sp"
tools:ignore="TouchTargetSizeCheck,SpeakableTextPresentCheck,TextContrastCheck" />
<View
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
-8
View File
@@ -1,8 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/menu_add_all_to_bookshelf"
android:title="@string/add_all_to_bookshelf" />
</menu>
@@ -1117,8 +1117,6 @@
<string name="test">测试</string> <string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</string> <string name="show_wait_up_count">显示等待更新数量</string>
<string name="exit_app">退出软件</string> <string name="exit_app">退出软件</string>
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">Analyzed</string> <string name="result_analyzed">Analyzed</string>
<string name="bookshelf_px_4">Comprehensive</string> <string name="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string> <string name="effective_replaces">起效的替换</string>
@@ -1120,8 +1120,6 @@
<string name="test">测试</string> <string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</string> <string name="show_wait_up_count">显示等待更新数量</string>
<string name="exit_app">退出软件</string> <string name="exit_app">退出软件</string>
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">Analyzed</string> <string name="result_analyzed">Analyzed</string>
<string name="bookshelf_px_4">Comprehensive</string> <string name="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string> <string name="effective_replaces">起效的替换</string>
@@ -1120,8 +1120,6 @@
<string name="test">测试</string> <string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</string> <string name="show_wait_up_count">显示等待更新数量</string>
<string name="exit_app">退出软件</string> <string name="exit_app">退出软件</string>
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">Analyzed</string> <string name="result_analyzed">Analyzed</string>
<string name="bookshelf_px_4">Comprehensive</string> <string name="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string> <string name="effective_replaces">起效的替换</string>
-2
View File
@@ -1116,8 +1116,6 @@ Còn </string>
<string name="test">Kiểm tra</string> <string name="test">Kiểm tra</string>
<string name="show_wait_up_count">Hiển thị số bản cập nhật đang đợi</string> <string name="show_wait_up_count">Hiển thị số bản cập nhật đang đợi</string>
<string name="exit_app">Thoát khỏi phần mềm</string> <string name="exit_app">Thoát khỏi phần mềm</string>
<string name="add_all_to_bookshelf">Thêm tất cả vào giá sách</string>
<string name="page_to">Trang tới</string>
<string name="result_analyzed">Analyzed</string> <string name="result_analyzed">Analyzed</string>
<string name="bookshelf_px_4">Comprehensive</string> <string name="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string> <string name="effective_replaces">起效的替换</string>
@@ -1117,8 +1117,6 @@
<string name="test">测试</string> <string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</string> <string name="show_wait_up_count">显示等待更新数量</string>
<string name="exit_app">退出软件</string> <string name="exit_app">退出软件</string>
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">解析示例</string> <string name="result_analyzed">解析示例</string>
<string name="bookshelf_px_4">綜合排序</string> <string name="bookshelf_px_4">綜合排序</string>
<string name="effective_replaces">起效的替换</string> <string name="effective_replaces">起效的替换</string>
@@ -1119,8 +1119,6 @@
<string name="test">测试</string> <string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</string> <string name="show_wait_up_count">显示等待更新数量</string>
<string name="exit_app">退出软件</string> <string name="exit_app">退出软件</string>
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">解析示例</string> <string name="result_analyzed">解析示例</string>
<string name="bookshelf_px_4">綜合排序</string> <string name="bookshelf_px_4">綜合排序</string>
<string name="effective_replaces">起效的替换</string> <string name="effective_replaces">起效的替换</string>
-2
View File
@@ -1119,8 +1119,6 @@
<string name="test">测试</string> <string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</string> <string name="show_wait_up_count">显示等待更新数量</string>
<string name="exit_app">退出软件</string> <string name="exit_app">退出软件</string>
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">解析示例</string> <string name="result_analyzed">解析示例</string>
<string name="bookshelf_px_4">综合排序</string> <string name="bookshelf_px_4">综合排序</string>
<string name="effective_replaces">起效的替换</string> <string name="effective_replaces">起效的替换</string>
-2
View File
@@ -1120,8 +1120,6 @@
<string name="test">Test</string> <string name="test">Test</string>
<string name="show_wait_up_count">Display the number of pending updates</string> <string name="show_wait_up_count">Display the number of pending updates</string>
<string name="exit_app">Exit</string> <string name="exit_app">Exit</string>
<string name="add_all_to_bookshelf">全部加入书架</string>
<string name="page_to">页至</string>
<string name="result_analyzed">Analyzed</string> <string name="result_analyzed">Analyzed</string>
<string name="bookshelf_px_4">Comprehensive</string> <string name="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">Effective replacement</string> <string name="effective_replaces">Effective replacement</string>