From 5d407f3753c5c67c505126b0a64450e5538c3639 Mon Sep 17 00:00:00 2001 From: Horis <821938089@qq.com> Date: Thu, 16 Feb 2023 11:42:35 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/io/legado/app/help/book/BookHelp.kt | 3 + .../legado/app/help/book/ContentProcessor.kt | 3 +- .../main/java/io/legado/app/model/ReadBook.kt | 2 + .../legado/app/model/localBook/LocalBook.kt | 19 +++--- .../io/legado/app/model/localBook/TextFile.kt | 45 ++++++++++-- .../app/ui/book/read/ReadBookActivity.kt | 2 + .../searchContent/SearchContentActivity.kt | 68 ++++++++++--------- .../searchContent/SearchContentViewModel.kt | 63 ++++++++--------- 8 files changed, 126 insertions(+), 79 deletions(-) diff --git a/app/src/main/java/io/legado/app/help/book/BookHelp.kt b/app/src/main/java/io/legado/app/help/book/BookHelp.kt index dcf8560de..f8a541693 100644 --- a/app/src/main/java/io/legado/app/help/book/BookHelp.kt +++ b/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(), diff --git a/app/src/main/java/io/legado/app/help/book/ContentProcessor.kt b/app/src/main/java/io/legado/app/help/book/ContentProcessor.kt index 521fb277b..6b51a4959 100644 --- a/app/src/main/java/io/legado/app/help/book/ContentProcessor.kt +++ b/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>() + 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)*") diff --git a/app/src/main/java/io/legado/app/model/ReadBook.kt b/app/src/main/java/io/legado/app/model/ReadBook.kt index 2da67c7c8..48b7430ea 100644 --- a/app/src/main/java/io/legado/app/model/ReadBook.kt +++ b/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() } diff --git a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt b/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt index acfdd69ab..17720bebd 100644 --- a/app/src/main/java/io/legado/app/model/localBook/LocalBook.kt +++ b/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("<img", "< 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("<img", "< 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 { diff --git a/app/src/main/java/io/legado/app/model/localBook/TextFile.kt b/app/src/main/java/io/legado/app/model/localBook/TextFile.kt index b283dd895..d4568b515 100644 --- a/app/src/main/java/io/legado/app/model/localBook/TextFile.kt +++ b/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 { 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, "  ") } } diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt index 8f63036c2..5a4bba3dd 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookActivity.kt @@ -121,6 +121,7 @@ class ReadBookActivity : BaseReadBookActivity(), val searchResult = IntentData.get("searchResult$key") val searchResultList = IntentData.get>("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 diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt index 1ebbfe4c0..2d3f9cd70 100644 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentActivity.kt +++ b/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 } diff --git a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt b/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt index 25452b9c7..22bb7de9d 100644 --- a/app/src/main/java/io/legado/app/ui/book/searchContent/SearchContentViewModel.kt +++ b/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 { val searchResultsWithinChapter: MutableList = 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 }