diff --git a/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt b/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt index 8ea229b83..049fdd5e9 100644 --- a/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt +++ b/app/src/main/java/io/legado/app/model/localBook/AnalyzeTxtFile.kt @@ -13,34 +13,43 @@ import java.nio.charset.Charset import java.util.regex.Matcher import java.util.regex.Pattern -object AnalyzeTxtFile { - private const val folderName = "bookTxt" - private const val BLANK: Byte = 0x0a - - //默认从文件中获取数据的长度 - private const val BUFFER_SIZE = 512 * 1024 - - //没有标题的时候,每个章节的最大长度 - private const val MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024 - val cacheFolder: File by lazy { - val rootFile = App.INSTANCE.getExternalFilesDir(null) - ?: App.INSTANCE.externalCacheDir - ?: App.INSTANCE.cacheDir - FileUtils.createFolderIfNotExist(rootFile, subDirs = *arrayOf(folderName)) - } +class AnalyzeTxtFile { + + private val tocRules = arrayListOf() + private lateinit var charset: Charset fun analyze(context: Context, book: Book): ArrayList { val bookFile = getBookFile(context, book) book.charset = EncodingDetect.getEncode(bookFile) - val charset = book.fileCharset() - val toc = arrayListOf() + charset = book.fileCharset() + val rulePattern = if (book.tocUrl.isNotEmpty()) { + Pattern.compile(book.tocUrl, Pattern.MULTILINE) + } else { + tocRules.addAll(getTocRules()) + null + } //获取文件流 val bookStream = RandomAccessFile(bookFile, "r") - val rulePattern = getTocRule(book, bookStream, charset) + return analyze(bookStream, book, rulePattern) + } + private fun analyze( + bookStream: RandomAccessFile, + book: Book, + pattern: Pattern? + ): ArrayList { + val toc = arrayListOf() + var tocRule: TxtTocRule? = null + val rulePattern = pattern ?: let { + tocRule = getTocRule(bookStream) + tocRule?.let { + Pattern.compile(it.rule, Pattern.MULTILINE) + } + } //加载章节 val buffer = ByteArray(BUFFER_SIZE) //获取到的块起始点,在文件中的位置 + bookStream.seek(0) var curOffset: Long = 0 //block的个数 var blockPos = 0 @@ -70,6 +79,10 @@ object AnalyzeTxtFile { val chapterStart = matcher.start() //获取章节内容 val chapterContent = blockContent.substring(seekPos, chapterStart) + if (chapterContent.length > 30000 && pattern == null) { + tocRules.remove(tocRule) + return analyze(bookStream, book, null) + } //如果 seekPos == 0 && nextChapterPos != 0 表示当前block处前面有一段内容 //第一种情况一定是序章 第二种情况可能是上一个章节的内容 if (seekPos == 0 && chapterStart != 0) { //获取当前章节的内容 @@ -161,41 +174,14 @@ object AnalyzeTxtFile { System.gc() System.runFinalization() - return toc - } - - fun getContent(book: Book, bookChapter: BookChapter): String { - val bookFile = getBookFile(App.INSTANCE, book) - //获取文件流 - val bookStream = RandomAccessFile(bookFile, "r") - bookStream.seek(bookChapter.start ?: 0) - val extent = (bookChapter.end!! - bookChapter.start!!).toInt() - val content = ByteArray(extent) - bookStream.read(content, 0, extent) - return String(content, book.fileCharset()) - } - - private fun getBookFile(context: Context, book: Book): File { - if (book.bookUrl.isContentPath()) { - val uri = Uri.parse(book.bookUrl) - val bookFile = FileUtils.getFile(cacheFolder, book.originName, subDirs = *arrayOf()) - if (!bookFile.exists()) { - bookFile.createNewFile() - DocumentUtils.readBytes(context, uri)?.let { - bookFile.writeBytes(it) - } - } - return bookFile + tocRule?.let { + book.tocUrl = it.rule } - return File(book.bookUrl) + return toc } - private fun getTocRule(book: Book, bookStream: RandomAccessFile, charset: Charset): Pattern? { - if (book.tocUrl.isNotEmpty()) { - return Pattern.compile(book.tocUrl, Pattern.MULTILINE) - } - val tocRules = getTocRules() - var rulePattern: Pattern? = null + private fun getTocRule(bookStream: RandomAccessFile): TxtTocRule? { + var txtTocRule: TxtTocRule? = null //首先获取128k的数据 val buffer = ByteArray(BUFFER_SIZE / 4) val length = bookStream.read(buffer, 0, buffer.size) @@ -204,30 +190,73 @@ object AnalyzeTxtFile { val pattern = Pattern.compile(tocRule.rule, Pattern.MULTILINE) val matcher = pattern.matcher(content) if (matcher.find()) { - book.tocUrl = tocRule.rule - rulePattern = pattern + txtTocRule = tocRule break } } - bookStream.seek(0) - return rulePattern + + return txtTocRule } - private fun getTocRules(): List { - val rules = App.db.txtTocRule().all - if (rules.isEmpty()) { - return getDefaultRules() + companion object { + private const val folderName = "bookTxt" + private const val BLANK: Byte = 0x0a + + //默认从文件中获取数据的长度 + private const val BUFFER_SIZE = 512 * 1024 + + //没有标题的时候,每个章节的最大长度 + private const val MAX_LENGTH_WITH_NO_CHAPTER = 10 * 1024 + val cacheFolder: File by lazy { + val rootFile = App.INSTANCE.getExternalFilesDir(null) + ?: App.INSTANCE.externalCacheDir + ?: App.INSTANCE.cacheDir + FileUtils.createFolderIfNotExist(rootFile, subDirs = *arrayOf(folderName)) } - return rules - } - fun getDefaultRules(): List { - App.INSTANCE.assets.open("txtTocRule.json").readBytes().let { byteArray -> - GSON.fromJsonArray(String(byteArray))?.let { - App.db.txtTocRule().insert(*it.toTypedArray()) - return it + fun getContent(book: Book, bookChapter: BookChapter): String { + val bookFile = getBookFile(App.INSTANCE, book) + //获取文件流 + val bookStream = RandomAccessFile(bookFile, "r") + bookStream.seek(bookChapter.start ?: 0) + val extent = (bookChapter.end!! - bookChapter.start!!).toInt() + val content = ByteArray(extent) + bookStream.read(content, 0, extent) + return String(content, book.fileCharset()) + } + + private fun getBookFile(context: Context, book: Book): File { + if (book.bookUrl.isContentPath()) { + val uri = Uri.parse(book.bookUrl) + val bookFile = FileUtils.getFile(cacheFolder, book.originName, subDirs = *arrayOf()) + if (!bookFile.exists()) { + bookFile.createNewFile() + DocumentUtils.readBytes(context, uri)?.let { + bookFile.writeBytes(it) + } + } + return bookFile } + return File(book.bookUrl) + } + + private fun getTocRules(): List { + val rules = App.db.txtTocRule().all + if (rules.isEmpty()) { + return getDefaultRules() + } + return rules + } + + fun getDefaultRules(): List { + App.INSTANCE.assets.open("txtTocRule.json").readBytes().let { byteArray -> + GSON.fromJsonArray(String(byteArray))?.let { + App.db.txtTocRule().insert(*it.toTypedArray()) + return it + } + } + return emptyList() } - return emptyList() } + } \ No newline at end of file diff --git a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt index e44018ee7..ba83c10ef 100644 --- a/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/info/BookInfoViewModel.kt @@ -81,7 +81,7 @@ class BookInfoViewModel(application: Application) : BaseViewModel(application) { ) { execute { if (book.isLocalBook()) { - AnalyzeTxtFile.analyze(context, book).let { + AnalyzeTxtFile().analyze(context, book).let { App.db.bookDao().update(book) App.db.bookChapterDao().insert(*it.toTypedArray()) chapterListData.postValue(it) diff --git a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt index 19c10ee83..977a65a10 100644 --- a/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/read/ReadBookViewModel.kt @@ -102,7 +102,7 @@ class ReadBookViewModel(application: Application) : BaseViewModel(application) { ) { execute { if (book.isLocalBook()) { - AnalyzeTxtFile.analyze(context, book).let { + AnalyzeTxtFile().analyze(context, book).let { App.db.bookChapterDao().delByBook(book.bookUrl) App.db.bookChapterDao().insert(*it.toTypedArray()) App.db.bookDao().update(book)