This commit is contained in:
Horis 2023-02-16 11:42:35 +08:00
parent d5caabf2cd
commit 5d407f3753
8 changed files with 126 additions and 79 deletions

View File

@ -302,6 +302,9 @@ object BookHelp {
* 读取章节内容
*/
fun getContent(book: Book, bookChapter: BookChapter): String? {
if (book.isLocalTxt) {
return LocalBook.getContent(book, bookChapter)
}
val file = downloadDir.getFile(
cacheFolderName,
book.getFolderName(),

View File

@ -25,6 +25,7 @@ class ContentProcessor private constructor(
companion object {
private val processors = hashMapOf<String, WeakReference<ContentProcessor>>()
var enableRemoveSameTitle = true
fun get(bookName: String, bookOrigin: String): ContentProcessor {
val processorWr = processors[bookName + bookOrigin]
@ -84,7 +85,7 @@ class ContentProcessor private constructor(
var sameTitleRemoved = false
if (content != "null") {
//去除重复标题
if (BookHelp.removeSameTitle(book, chapter)) try {
if (enableRemoveSameTitle && BookHelp.removeSameTitle(book, chapter)) try {
val name = Pattern.quote(book.name)
var title = Pattern.quote(chapter.title)
var matcher = Pattern.compile("^(\\s|\\p{P}|${name})*${title}(\\s)*")

View File

@ -10,6 +10,7 @@ import io.legado.app.help.book.isLocal
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig
import io.legado.app.help.coroutine.Coroutine
import io.legado.app.model.localBook.TextFile
import io.legado.app.model.webBook.WebBook
import io.legado.app.service.BaseReadAloudService
import io.legado.app.ui.book.read.page.entities.TextChapter
@ -77,6 +78,7 @@ object ReadBook : CoroutineScope by MainScope() {
upWebBook(book)
lastBookPress = null
webBookProgress = null
TextFile.txtBuffer = null
synchronized(this) {
loadingChapters.clear()
}

View File

@ -95,7 +95,7 @@ object LocalBook {
}
fun getContent(book: Book, chapter: BookChapter): String? {
val content = try {
var content = try {
when {
book.isEpub -> {
EpubFile.getContent(book, chapter)
@ -114,13 +114,16 @@ object LocalBook {
e.printOnDebug()
AppLog.put("获取本地书籍内容失败\n${e.localizedMessage}", e)
"获取本地书籍内容失败\n${e.localizedMessage}"
}?.replace("&lt;img", "&lt; img", true)
content ?: return null
return kotlin.runCatching {
Entities.unescape(content)
}.onFailure {
AppLog.put("HTML实体解码失败\n${it.localizedMessage}", it)
}.getOrElse { content }
}
if (book.isEpub) {
content = content?.replace("&lt;img", "&lt; img", true) ?: return null
return kotlin.runCatching {
Entities.unescape(content)
}.onFailure {
AppLog.put("HTML实体解码失败\n${it.localizedMessage}", it)
}.getOrDefault(content)
}
return content
}
fun getCoverPath(book: Book): String {

View File

@ -18,23 +18,60 @@ import kotlin.math.min
class TextFile(private val book: Book) {
companion object {
private val padRegex = "^[\\n\\s]+".toRegex()
private const val bufferSize = 8 * 1024 * 1024
var txtBuffer: ByteArray? = null
var bufferStart = -1
var bufferEnd = -1
var bookUrl = ""
@Throws(FileNotFoundException::class)
fun getChapterList(book: Book): ArrayList<BookChapter> {
return TextFile(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)
}
}
val count = (bookChapter.end!! - bookChapter.start!!).toInt()
val buffer = ByteArray(count)
LocalBook.getBookInputStream(book).use { bis ->
bis.skip(bookChapter.start!!)
bis.read(buffer)
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("^[\\n\\s]+".toRegex(), "  ")
.replace(padRegex, "  ")
}
}

View File

@ -121,6 +121,7 @@ class ReadBookActivity : BaseReadBookActivity(),
val searchResult = IntentData.get<SearchResult>("searchResult$key")
val searchResultList = IntentData.get<List<SearchResult>>("searchResultList$key")
if (searchResult != null && searchResultList != null) {
ContentProcessor.enableRemoveSameTitle = false
viewModel.searchContentQuery = searchResult.query
binding.searchMenu.upSearchResultList(searchResultList)
isShowingSearchResult = true
@ -1005,6 +1006,7 @@ class ReadBookActivity : BaseReadBookActivity(),
override fun exitSearchMenu() {
if (isShowingSearchResult) {
isShowingSearchResult = false
ContentProcessor.enableRemoveSameTitle = true
binding.searchMenu.invalidate()
binding.searchMenu.invisible()
binding.readView.isTextSelected = false

View File

@ -15,6 +15,7 @@ import io.legado.app.data.entities.BookChapter
import io.legado.app.databinding.ActivitySearchContentBinding
import io.legado.app.help.IntentData
import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.book.isLocal
import io.legado.app.lib.theme.bottomBackground
import io.legado.app.lib.theme.getPrimaryTextColor
@ -144,43 +145,48 @@ class SearchContentActivity :
@SuppressLint("SetTextI18n")
fun startContentSearch(query: String) {
// 按章节搜索内容
if (query.isNotBlank()) {
searchJob?.cancel()
adapter.clearItems()
viewModel.searchResultList.clear()
viewModel.searchResultCounts = 0
viewModel.lastQuery = query
searchJob = launch {
kotlin.runCatching {
binding.refreshProgressBar.isAutoLoading = true
binding.fbStop.visible()
withContext(IO) {
appDb.bookChapterDao.getChapterList(viewModel.bookUrl)
}.forEach { bookChapter ->
ensureActive()
val searchResults = withContext(IO) {
if (isLocalBook || viewModel.cacheChapterNames.contains(bookChapter.getFileName())) {
viewModel.searchChapter(query, bookChapter)
} else {
null
}
}
binding.tvCurrentSearchInfo.text =
this@SearchContentActivity.getString(R.string.search_content_size) + ": ${viewModel.searchResultCounts}"
ensureActive()
if (searchResults != null && searchResults.isNotEmpty()) {
viewModel.searchResultList.addAll(searchResults)
if (query.isBlank()) return
searchJob?.cancel()
adapter.clearItems()
viewModel.searchResultList.clear()
viewModel.searchResultCounts = 0
viewModel.lastQuery = query
binding.refreshProgressBar.isAutoLoading = true
binding.fbStop.visible()
ContentProcessor.enableRemoveSameTitle = false
searchJob = launch(IO) {
kotlin.runCatching {
appDb.bookChapterDao.getChapterList(viewModel.bookUrl).forEach { bookChapter ->
ensureActive()
val searchResults = if (isLocalBook
|| viewModel.cacheChapterNames.contains(bookChapter.getFileName())
) {
viewModel.searchChapter(query, bookChapter)
} else {
return@forEach
}
ensureActive()
if (searchResults.isNotEmpty()) {
viewModel.searchResultList.addAll(searchResults)
binding.tvCurrentSearchInfo.post {
binding.tvCurrentSearchInfo.text =
this@SearchContentActivity.getString(R.string.search_content_size) + ": ${viewModel.searchResultCounts}"
adapter.addItems(searchResults)
}
}
if (viewModel.searchResultCounts == 0) {
val noSearchResult =
SearchResult(resultText = getString(R.string.search_content_empty))
}
if (viewModel.searchResultCounts == 0) {
val noSearchResult =
SearchResult(resultText = getString(R.string.search_content_empty))
binding.tvCurrentSearchInfo.post {
adapter.addItem(noSearchResult)
}
}.onFailure {
AppLog.put("全文搜索出错\n${it.localizedMessage}", it)
}
}.onFailure {
AppLog.put("全文搜索出错\n${it.localizedMessage}", it)
}
ContentProcessor.enableRemoveSameTitle = true
binding.tvCurrentSearchInfo.post {
binding.fbStop.invisible()
binding.refreshProgressBar.isAutoLoading = false
}

View File

@ -41,42 +41,35 @@ class SearchContentViewModel(application: Application) : BaseViewModel(applicati
chapter: BookChapter?
): List<SearchResult> {
val searchResultsWithinChapter: MutableList<SearchResult> = mutableListOf()
if (chapter != null) {
book?.let { book ->
val chapterContent = BookHelp.getContent(book, chapter)
val mContent: String
coroutineContext.ensureActive()
if (chapterContent != null) {
withContext(Dispatchers.IO) {
chapter.title = when (AppConfig.chineseConverterType) {
1 -> ChineseUtils.t2s(chapter.title)
2 -> ChineseUtils.s2t(chapter.title)
else -> chapter.title
}
coroutineContext.ensureActive()
mContent = contentProcessor!!.getContent(
book, chapter, chapterContent
).toString()
}
val positions = searchPosition(mContent, query)
positions.forEachIndexed { index, position ->
coroutineContext.ensureActive()
val construct = getResultAndQueryIndex(mContent, position, query)
val result = SearchResult(
resultCountWithinChapter = index,
resultText = construct.second,
chapterTitle = chapter.title,
query = query,
chapterIndex = chapter.index,
queryIndexInResult = construct.first,
queryIndexInChapter = position
)
searchResultsWithinChapter.add(result)
}
searchResultCounts += searchResultsWithinChapter.size
}
}
chapter ?: return searchResultsWithinChapter
val book = book ?: return searchResultsWithinChapter
val chapterContent = BookHelp.getContent(book, chapter) ?: return searchResultsWithinChapter
coroutineContext.ensureActive()
chapter.title = when (AppConfig.chineseConverterType) {
1 -> ChineseUtils.t2s(chapter.title)
2 -> ChineseUtils.s2t(chapter.title)
else -> chapter.title
}
coroutineContext.ensureActive()
val mContent = contentProcessor!!.getContent(
book, chapter, chapterContent
).toString()
val positions = searchPosition(mContent, query)
positions.forEachIndexed { index, position ->
coroutineContext.ensureActive()
val construct = getResultAndQueryIndex(mContent, position, query)
val result = SearchResult(
resultCountWithinChapter = index,
resultText = construct.second,
chapterTitle = chapter.title,
query = query,
chapterIndex = chapter.index,
queryIndexInResult = construct.first,
queryIndexInChapter = position
)
searchResultsWithinChapter.add(result)
}
searchResultCounts += searchResultsWithinChapter.size
return searchResultsWithinChapter
}