优化
This commit is contained in:
@@ -302,6 +302,9 @@ object BookHelp {
|
|||||||
* 读取章节内容
|
* 读取章节内容
|
||||||
*/
|
*/
|
||||||
fun getContent(book: Book, bookChapter: BookChapter): String? {
|
fun getContent(book: Book, bookChapter: BookChapter): String? {
|
||||||
|
if (book.isLocalTxt) {
|
||||||
|
return LocalBook.getContent(book, bookChapter)
|
||||||
|
}
|
||||||
val file = downloadDir.getFile(
|
val file = downloadDir.getFile(
|
||||||
cacheFolderName,
|
cacheFolderName,
|
||||||
book.getFolderName(),
|
book.getFolderName(),
|
||||||
|
|||||||
@@ -25,6 +25,7 @@ class ContentProcessor private constructor(
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private val processors = hashMapOf<String, WeakReference<ContentProcessor>>()
|
private val processors = hashMapOf<String, WeakReference<ContentProcessor>>()
|
||||||
|
var enableRemoveSameTitle = true
|
||||||
|
|
||||||
fun get(bookName: String, bookOrigin: String): ContentProcessor {
|
fun get(bookName: String, bookOrigin: String): ContentProcessor {
|
||||||
val processorWr = processors[bookName + bookOrigin]
|
val processorWr = processors[bookName + bookOrigin]
|
||||||
@@ -84,7 +85,7 @@ class ContentProcessor private constructor(
|
|||||||
var sameTitleRemoved = false
|
var sameTitleRemoved = false
|
||||||
if (content != "null") {
|
if (content != "null") {
|
||||||
//去除重复标题
|
//去除重复标题
|
||||||
if (BookHelp.removeSameTitle(book, chapter)) try {
|
if (enableRemoveSameTitle && BookHelp.removeSameTitle(book, chapter)) try {
|
||||||
val name = Pattern.quote(book.name)
|
val name = Pattern.quote(book.name)
|
||||||
var title = Pattern.quote(chapter.title)
|
var title = Pattern.quote(chapter.title)
|
||||||
var matcher = Pattern.compile("^(\\s|\\p{P}|${name})*${title}(\\s)*")
|
var matcher = Pattern.compile("^(\\s|\\p{P}|${name})*${title}(\\s)*")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import io.legado.app.help.book.isLocal
|
|||||||
import io.legado.app.help.config.AppConfig
|
import io.legado.app.help.config.AppConfig
|
||||||
import io.legado.app.help.config.ReadBookConfig
|
import io.legado.app.help.config.ReadBookConfig
|
||||||
import io.legado.app.help.coroutine.Coroutine
|
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.model.webBook.WebBook
|
||||||
import io.legado.app.service.BaseReadAloudService
|
import io.legado.app.service.BaseReadAloudService
|
||||||
import io.legado.app.ui.book.read.page.entities.TextChapter
|
import io.legado.app.ui.book.read.page.entities.TextChapter
|
||||||
@@ -77,6 +78,7 @@ object ReadBook : CoroutineScope by MainScope() {
|
|||||||
upWebBook(book)
|
upWebBook(book)
|
||||||
lastBookPress = null
|
lastBookPress = null
|
||||||
webBookProgress = null
|
webBookProgress = null
|
||||||
|
TextFile.txtBuffer = null
|
||||||
synchronized(this) {
|
synchronized(this) {
|
||||||
loadingChapters.clear()
|
loadingChapters.clear()
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ object LocalBook {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun getContent(book: Book, chapter: BookChapter): String? {
|
fun getContent(book: Book, chapter: BookChapter): String? {
|
||||||
val content = try {
|
var content = try {
|
||||||
when {
|
when {
|
||||||
book.isEpub -> {
|
book.isEpub -> {
|
||||||
EpubFile.getContent(book, chapter)
|
EpubFile.getContent(book, chapter)
|
||||||
@@ -114,13 +114,16 @@ object LocalBook {
|
|||||||
e.printOnDebug()
|
e.printOnDebug()
|
||||||
AppLog.put("获取本地书籍内容失败\n${e.localizedMessage}", e)
|
AppLog.put("获取本地书籍内容失败\n${e.localizedMessage}", e)
|
||||||
"获取本地书籍内容失败\n${e.localizedMessage}"
|
"获取本地书籍内容失败\n${e.localizedMessage}"
|
||||||
}?.replace("<img", "< img", true)
|
}
|
||||||
content ?: return null
|
if (book.isEpub) {
|
||||||
return kotlin.runCatching {
|
content = content?.replace("<img", "< img", true) ?: return null
|
||||||
Entities.unescape(content)
|
return kotlin.runCatching {
|
||||||
}.onFailure {
|
Entities.unescape(content)
|
||||||
AppLog.put("HTML实体解码失败\n${it.localizedMessage}", it)
|
}.onFailure {
|
||||||
}.getOrElse { content }
|
AppLog.put("HTML实体解码失败\n${it.localizedMessage}", it)
|
||||||
|
}.getOrDefault(content)
|
||||||
|
}
|
||||||
|
return content
|
||||||
}
|
}
|
||||||
|
|
||||||
fun getCoverPath(book: Book): String {
|
fun getCoverPath(book: Book): String {
|
||||||
|
|||||||
@@ -18,23 +18,60 @@ import kotlin.math.min
|
|||||||
class TextFile(private val book: Book) {
|
class TextFile(private val book: Book) {
|
||||||
|
|
||||||
companion object {
|
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)
|
@Throws(FileNotFoundException::class)
|
||||||
fun getChapterList(book: Book): ArrayList<BookChapter> {
|
fun getChapterList(book: Book): ArrayList<BookChapter> {
|
||||||
return TextFile(book).getChapterList()
|
return TextFile(book).getChapterList()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@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
|
||||||
|
) {
|
||||||
|
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 count = (bookChapter.end!! - bookChapter.start!!).toInt()
|
||||||
val buffer = ByteArray(count)
|
val buffer = ByteArray(count)
|
||||||
LocalBook.getBookInputStream(book).use { bis ->
|
|
||||||
bis.skip(bookChapter.start!!)
|
if (bookChapter.start!! < bufferEnd && bookChapter.end!! > bufferEnd
|
||||||
bis.read(buffer)
|
|| 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())
|
return String(buffer, book.fileCharset())
|
||||||
.substringAfter(bookChapter.title)
|
.substringAfter(bookChapter.title)
|
||||||
.replace("^[\\n\\s]+".toRegex(), " ")
|
.replace(padRegex, " ")
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -121,6 +121,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
val searchResult = IntentData.get<SearchResult>("searchResult$key")
|
val searchResult = IntentData.get<SearchResult>("searchResult$key")
|
||||||
val searchResultList = IntentData.get<List<SearchResult>>("searchResultList$key")
|
val searchResultList = IntentData.get<List<SearchResult>>("searchResultList$key")
|
||||||
if (searchResult != null && searchResultList != null) {
|
if (searchResult != null && searchResultList != null) {
|
||||||
|
ContentProcessor.enableRemoveSameTitle = false
|
||||||
viewModel.searchContentQuery = searchResult.query
|
viewModel.searchContentQuery = searchResult.query
|
||||||
binding.searchMenu.upSearchResultList(searchResultList)
|
binding.searchMenu.upSearchResultList(searchResultList)
|
||||||
isShowingSearchResult = true
|
isShowingSearchResult = true
|
||||||
@@ -1005,6 +1006,7 @@ class ReadBookActivity : BaseReadBookActivity(),
|
|||||||
override fun exitSearchMenu() {
|
override fun exitSearchMenu() {
|
||||||
if (isShowingSearchResult) {
|
if (isShowingSearchResult) {
|
||||||
isShowingSearchResult = false
|
isShowingSearchResult = false
|
||||||
|
ContentProcessor.enableRemoveSameTitle = true
|
||||||
binding.searchMenu.invalidate()
|
binding.searchMenu.invalidate()
|
||||||
binding.searchMenu.invisible()
|
binding.searchMenu.invisible()
|
||||||
binding.readView.isTextSelected = false
|
binding.readView.isTextSelected = false
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import io.legado.app.data.entities.BookChapter
|
|||||||
import io.legado.app.databinding.ActivitySearchContentBinding
|
import io.legado.app.databinding.ActivitySearchContentBinding
|
||||||
import io.legado.app.help.IntentData
|
import io.legado.app.help.IntentData
|
||||||
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.isLocal
|
import io.legado.app.help.book.isLocal
|
||||||
import io.legado.app.lib.theme.bottomBackground
|
import io.legado.app.lib.theme.bottomBackground
|
||||||
import io.legado.app.lib.theme.getPrimaryTextColor
|
import io.legado.app.lib.theme.getPrimaryTextColor
|
||||||
@@ -144,43 +145,48 @@ class SearchContentActivity :
|
|||||||
@SuppressLint("SetTextI18n")
|
@SuppressLint("SetTextI18n")
|
||||||
fun startContentSearch(query: String) {
|
fun startContentSearch(query: String) {
|
||||||
// 按章节搜索内容
|
// 按章节搜索内容
|
||||||
if (query.isNotBlank()) {
|
if (query.isBlank()) return
|
||||||
searchJob?.cancel()
|
searchJob?.cancel()
|
||||||
adapter.clearItems()
|
adapter.clearItems()
|
||||||
viewModel.searchResultList.clear()
|
viewModel.searchResultList.clear()
|
||||||
viewModel.searchResultCounts = 0
|
viewModel.searchResultCounts = 0
|
||||||
viewModel.lastQuery = query
|
viewModel.lastQuery = query
|
||||||
searchJob = launch {
|
binding.refreshProgressBar.isAutoLoading = true
|
||||||
kotlin.runCatching {
|
binding.fbStop.visible()
|
||||||
binding.refreshProgressBar.isAutoLoading = true
|
ContentProcessor.enableRemoveSameTitle = false
|
||||||
binding.fbStop.visible()
|
searchJob = launch(IO) {
|
||||||
withContext(IO) {
|
kotlin.runCatching {
|
||||||
appDb.bookChapterDao.getChapterList(viewModel.bookUrl)
|
appDb.bookChapterDao.getChapterList(viewModel.bookUrl).forEach { bookChapter ->
|
||||||
}.forEach { bookChapter ->
|
ensureActive()
|
||||||
ensureActive()
|
val searchResults = if (isLocalBook
|
||||||
val searchResults = withContext(IO) {
|
|| viewModel.cacheChapterNames.contains(bookChapter.getFileName())
|
||||||
if (isLocalBook || viewModel.cacheChapterNames.contains(bookChapter.getFileName())) {
|
) {
|
||||||
viewModel.searchChapter(query, bookChapter)
|
viewModel.searchChapter(query, bookChapter)
|
||||||
} else {
|
} else {
|
||||||
null
|
return@forEach
|
||||||
}
|
}
|
||||||
}
|
ensureActive()
|
||||||
binding.tvCurrentSearchInfo.text =
|
if (searchResults.isNotEmpty()) {
|
||||||
this@SearchContentActivity.getString(R.string.search_content_size) + ": ${viewModel.searchResultCounts}"
|
viewModel.searchResultList.addAll(searchResults)
|
||||||
ensureActive()
|
binding.tvCurrentSearchInfo.post {
|
||||||
if (searchResults != null && searchResults.isNotEmpty()) {
|
binding.tvCurrentSearchInfo.text =
|
||||||
viewModel.searchResultList.addAll(searchResults)
|
this@SearchContentActivity.getString(R.string.search_content_size) + ": ${viewModel.searchResultCounts}"
|
||||||
adapter.addItems(searchResults)
|
adapter.addItems(searchResults)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (viewModel.searchResultCounts == 0) {
|
}
|
||||||
val noSearchResult =
|
if (viewModel.searchResultCounts == 0) {
|
||||||
SearchResult(resultText = getString(R.string.search_content_empty))
|
val noSearchResult =
|
||||||
|
SearchResult(resultText = getString(R.string.search_content_empty))
|
||||||
|
binding.tvCurrentSearchInfo.post {
|
||||||
adapter.addItem(noSearchResult)
|
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.fbStop.invisible()
|
||||||
binding.refreshProgressBar.isAutoLoading = false
|
binding.refreshProgressBar.isAutoLoading = false
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,42 +41,35 @@ class SearchContentViewModel(application: Application) : BaseViewModel(applicati
|
|||||||
chapter: BookChapter?
|
chapter: BookChapter?
|
||||||
): List<SearchResult> {
|
): List<SearchResult> {
|
||||||
val searchResultsWithinChapter: MutableList<SearchResult> = mutableListOf()
|
val searchResultsWithinChapter: MutableList<SearchResult> = mutableListOf()
|
||||||
if (chapter != null) {
|
chapter ?: return searchResultsWithinChapter
|
||||||
book?.let { book ->
|
val book = book ?: return searchResultsWithinChapter
|
||||||
val chapterContent = BookHelp.getContent(book, chapter)
|
val chapterContent = BookHelp.getContent(book, chapter) ?: return searchResultsWithinChapter
|
||||||
val mContent: String
|
coroutineContext.ensureActive()
|
||||||
coroutineContext.ensureActive()
|
chapter.title = when (AppConfig.chineseConverterType) {
|
||||||
if (chapterContent != null) {
|
1 -> ChineseUtils.t2s(chapter.title)
|
||||||
withContext(Dispatchers.IO) {
|
2 -> ChineseUtils.s2t(chapter.title)
|
||||||
chapter.title = when (AppConfig.chineseConverterType) {
|
else -> chapter.title
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
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
|
return searchResultsWithinChapter
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user