Horis 3 лет назад
Родитель
Сommit
d72c8329b0

+ 3 - 0
app/src/main/java/io/legado/app/help/book/BookHelp.kt

@@ -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(),

+ 2 - 1
app/src/main/java/io/legado/app/help/book/ContentProcessor.kt

@@ -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)*")

+ 2 - 0
app/src/main/java/io/legado/app/model/ReadBook.kt

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

+ 11 - 8
app/src/main/java/io/legado/app/model/localBook/LocalBook.kt

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

+ 41 - 4
app/src/main/java/io/legado/app/model/localBook/TextFile.kt

@@ -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, "  ")
         }
 
     }

+ 2 - 0
app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt

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

+ 37 - 31
app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt

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

+ 28 - 35
app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt

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