This commit is contained in:
Horis 2024-01-01 12:28:32 +08:00
parent 73dc85efee
commit 8f07d73bb1
15 changed files with 80 additions and 259 deletions

View File

@ -2,7 +2,11 @@ package io.legado.app.model
import io.legado.app.constant.AppLog
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.book.BookHelp
import io.legado.app.help.book.ContentProcessor
@ -88,7 +92,7 @@ object ReadBook : CoroutineScope by MainScope() {
upWebBook(book)
lastBookPress = null
webBookProgress = null
TextFile.txtBuffer = null
TextFile.clear()
synchronized(this) {
loadingChapters.clear()
}

View File

@ -82,7 +82,7 @@ object LocalBook {
}
}
@Throws(Exception::class)
@Throws(TocEmptyException::class)
fun getChapterList(book: Book): ArrayList<BookChapter> {
val chapters = when {
book.isEpub -> {

View File

@ -16,56 +16,37 @@ import java.util.regex.Matcher
import java.util.regex.Pattern
import kotlin.math.min
class TextFile(private val book: Book) {
class TextFile(private var book: Book) {
@Suppress("ConstPropertyName")
companion object {
private val padRegex = "^[\\n\\s]+".toRegex()
private const val bufferSize = 8 * 1024 * 1024
private var bufferStart = -1
private var bufferEnd = -1
var txtBuffer: ByteArray? = null
var bookUrl = ""
private const val txtBufferSize = 8 * 1024 * 1024
private var textFile: TextFile? = null
@Synchronized
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)
fun getChapterList(book: Book): ArrayList<BookChapter> {
return TextFile(book).getChapterList()
return getTextFile(book).getChapterList()
}
@Synchronized
@Throws(FileNotFoundException::class)
fun getContent(book: Book, bookChapter: BookChapter): String {
if (txtBuffer == null || bookUrl != book.bookUrl || bookChapter.start!! > bufferEnd || bookChapter.end!! < bufferStart) {
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)
}
}
return getTextFile(book).getContent(bookChapter)
}
val count = (bookChapter.end!! - bookChapter.start!!).toInt()
val buffer = ByteArray(count)
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, "  ")
fun clear() {
textFile = null
}
}
@ -83,10 +64,14 @@ class TextFile(private val book: Book) {
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> {
if (book.charset == null || book.tocUrl.isBlank()) {
LocalBook.getBookInputStream(book).use { bis ->
@ -112,6 +97,43 @@ class TextFile(private val book: Book) {
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 chapterContent = blockContent.substring(seekPos, chapterStart)
val chapterLength = chapterContent.toByteArray(charset).size
val chapterLength = chapterContent.toByteArray(charset).size.toLong()
val lastStart = toc.lastOrNull()?.start ?: curOffset
if (book.getSplitLongChapter() && curOffset + chapterLength - lastStart > maxLengthWithToc) {
toc.lastOrNull()?.let {
@ -186,7 +208,7 @@ class TextFile(private val book: Book) {
curChapter.start = curOffset + chapterLength
toc.add(curChapter)
} else if (seekPos == 0 && chapterStart != 0) {
/*
/**
* 如果 seekPos == 0 && chapterStart != 0 表示当前block处前面有一段内容
* 第一种情况一定是序章 第二种情况是上一个章节的内容
*/
@ -196,13 +218,18 @@ class TextFile(private val book: Book) {
val qyChapter = BookChapter()
qyChapter.title = "前言"
qyChapter.start = curOffset
qyChapter.end = curOffset + chapterLength.toLong()
qyChapter.end = curOffset + chapterLength
toc.add(qyChapter)
book.intro = if (chapterContent.length <= 500) {
chapterContent
} else {
chapterContent.substring(0, 500)
}
}
//创建当前章节
val curChapter = BookChapter()
curChapter.title = matcher.group()
curChapter.start = curOffset + chapterLength.toLong()
curChapter.start = curOffset + chapterLength
toc.add(curChapter)
} else { //否则就block分割之后上一个章节的剩余内容
//获取上一章节
@ -210,7 +237,7 @@ class TextFile(private val book: Book) {
lastChapter.isVolume =
chapterContent.substringAfter(lastChapter.title).isBlank()
//将当前段落添加上一章去
lastChapter.end = lastChapter.end!! + chapterLength.toLong()
lastChapter.end = lastChapter.end!! + chapterLength
//创建当前章节
val curChapter = BookChapter()
curChapter.title = matcher.group()
@ -224,7 +251,7 @@ class TextFile(private val book: Book) {
lastChapter.isVolume =
chapterContent.substringAfter(lastChapter.title).isBlank()
lastChapter.end =
lastChapter.start!! + chapterContent.toByteArray(charset).size.toLong()
lastChapter.start!! + chapterLength
//创建当前章节
val curChapter = BookChapter()
curChapter.title = matcher.group()

View File

@ -1,45 +1,27 @@
package io.legado.app.ui.book.explore
import android.os.Bundle
import android.view.Menu
import android.view.MenuItem
import androidx.activity.viewModels
import androidx.recyclerview.widget.RecyclerView
import io.legado.app.R
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.SearchBook
import io.legado.app.databinding.ActivityExploreShowBinding
import io.legado.app.databinding.DialogPageChoiceBinding
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.widget.dialog.WaitDialog
import io.legado.app.ui.widget.recycler.LoadMoreView
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.viewbindingdelegate.viewBinding
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.launch
class ExploreShowActivity : VMBaseActivity<ActivityExploreShowBinding, ExploreShowViewModel>(),
ExploreShowAdapter.CallBack,
GroupSelectDialog.CallBack {
ExploreShowAdapter.CallBack {
override val binding by viewBinding(ActivityExploreShowBinding::inflate)
override val viewModel by viewModels<ExploreShowViewModel>()
private val adapter by lazy { ExploreShowAdapter(this, this) }
private val loadMoreView by lazy { LoadMoreView(this) }
private val waitDialog by lazy {
WaitDialog(this)
}
override fun onActivityCreated(savedInstanceState: Bundle?) {
binding.titleBar.title = intent.getStringExtra("exploreName")
@ -116,84 +98,4 @@ class ExploreShowActivity : VMBaseActivity<ActivityExploreShowBinding, ExploreSh
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()
}
}
}

View File

@ -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
}
}

View File

@ -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>

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>

View File

@ -1117,8 +1117,6 @@
<string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</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="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string>

View File

@ -1120,8 +1120,6 @@
<string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</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="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string>

View File

@ -1120,8 +1120,6 @@
<string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</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="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string>

View File

@ -1116,8 +1116,6 @@ Còn </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="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="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">起效的替换</string>

View File

@ -1117,8 +1117,6 @@
<string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</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="bookshelf_px_4">綜合排序</string>
<string name="effective_replaces">起效的替换</string>

View File

@ -1119,8 +1119,6 @@
<string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</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="bookshelf_px_4">綜合排序</string>
<string name="effective_replaces">起效的替换</string>

View File

@ -1119,8 +1119,6 @@
<string name="test">测试</string>
<string name="show_wait_up_count">显示等待更新数量</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="bookshelf_px_4">综合排序</string>
<string name="effective_replaces">起效的替换</string>

View File

@ -1120,8 +1120,6 @@
<string name="test">Test</string>
<string name="show_wait_up_count">Display the number of pending updates</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="bookshelf_px_4">Comprehensive</string>
<string name="effective_replaces">Effective replacement</string>