diff --git a/app/src/main/java/io/legado/app/data/repository/ExploreRepository.kt b/app/src/main/java/io/legado/app/data/repository/ExploreRepository.kt index 6919478e8..e6ec23019 100644 --- a/app/src/main/java/io/legado/app/data/repository/ExploreRepository.kt +++ b/app/src/main/java/io/legado/app/data/repository/ExploreRepository.kt @@ -4,7 +4,6 @@ import io.legado.app.data.AppDatabase import io.legado.app.data.entities.BookSource import io.legado.app.data.entities.SearchBook import io.legado.app.data.entities.rule.ExploreKind -import io.legado.app.help.book.isNotShelf import io.legado.app.help.source.exploreKinds import io.legado.app.model.webBook.WebBook import kotlinx.coroutines.Dispatchers @@ -26,9 +25,19 @@ class ExploreRepositoryImpl( ) : ExploreRepository { override fun getBookshelfItems(): Flow> { - return appDb.bookDao.flowAll().map { books -> + return appDb.bookDao.flowBookShelf().map { books -> books.filterNot { it.isNotShelf } - .map { it.toSearchBook() } + .map { + SearchBook( + bookUrl = it.bookUrl, + name = it.name, + author = it.author, + originName = it.originName, + type = it.type, + coverUrl = it.coverUrl, + latestChapterTitle = it.latestChapterTitle + ) + } } } @@ -56,4 +65,4 @@ class ExploreRepositoryImpl( override suspend fun saveSearchBooks(books: List) { appDb.searchBookDao.insert(*books.toTypedArray()) } -} \ No newline at end of file +} diff --git a/app/src/main/java/io/legado/app/help/AppFreezeMonitor.kt b/app/src/main/java/io/legado/app/help/AppFreezeMonitor.kt index 59acb1520..df076e8db 100644 --- a/app/src/main/java/io/legado/app/help/AppFreezeMonitor.kt +++ b/app/src/main/java/io/legado/app/help/AppFreezeMonitor.kt @@ -24,6 +24,7 @@ object AppFreezeMonitor { } private var registeredReceiver = false + private var monitorRunnable: Runnable? = null @SuppressLint("UnspecifiedRegisterReceiverFlag") fun init(context: Context) { @@ -32,6 +33,10 @@ object AppFreezeMonitor { registeredReceiver = false context.unregisterReceiver(screenStatusReceiver) } + monitorRunnable?.let { + handler.removeCallbacks(it) + monitorRunnable = null + } return } @@ -40,6 +45,7 @@ object AppFreezeMonitor { context.registerReceiver(screenStatusReceiver, screenStatusReceiver.filter) } + if (monitorRunnable != null) return var previous = SystemClock.uptimeMillis() val runnable = object : Runnable { @@ -56,9 +62,12 @@ object AppFreezeMonitor { if (AppConfig.recordLog) { handler.postDelayed(this, 3000) + } else { + monitorRunnable = null } } } + monitorRunnable = runnable handler.postDelayed(runnable, 3000) } 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 887eb1940..bba633036 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 @@ -428,28 +428,87 @@ object BookHelp { var ret = true val op = BitmapFactory.Options() op.inJustDecodeBounds = true - getContent(book, bookChapter)?.let { - val matcher = AppPattern.imgPattern.matcher(it) - while (matcher.find()) { - val src = matcher.group(1)!! - val image = getImage(book, src) - if (!image.exists()) { - ret = false - continue - } - BitmapFactory.decodeFile(image.absolutePath, op) - if (op.outWidth < 1 && op.outHeight < 1) { - if (SvgUtils.getSize(image.absolutePath) != null) { - continue - } - ret = false - image.delete() + forEachImageSrc(book, bookChapter) { src -> + val image = getImage(book, src) + if (!image.exists()) { + ret = false + return@forEachImageSrc + } + BitmapFactory.decodeFile(image.absolutePath, op) + if (op.outWidth < 1 && op.outHeight < 1) { + if (SvgUtils.getSize(image.absolutePath) != null) { + return@forEachImageSrc } + ret = false + image.delete() } } return ret } + private inline fun forEachImageSrc( + book: Book, + bookChapter: BookChapter, + action: (String) -> Unit + ) { + val file = downloadDir.getFile( + cacheFolderName, + book.getFolderName(), + bookChapter.getFileName() + ) + if (file.exists()) { + forEachImageSrc(file, action) + return + } + getContent(book, bookChapter)?.let { content -> + val matcher = AppPattern.imgPattern.matcher(content) + while (matcher.find()) { + action(matcher.group(1) ?: continue) + } + } + } + + private inline fun forEachImageSrc(file: File, action: (String) -> Unit) { + val chars = CharArray(8192) + val buffer = StringBuilder() + file.bufferedReader().use { reader -> + while (true) { + val readSize = reader.read(chars) + if (readSize < 0) break + buffer.append(chars, 0, readSize) + consumeImageSrcMatches(buffer, action) + trimImageScanBuffer(buffer) + } + consumeImageSrcMatches(buffer, action) + } + } + + private inline fun consumeImageSrcMatches( + buffer: StringBuilder, + action: (String) -> Unit + ) { + val matcher = AppPattern.imgPattern.matcher(buffer) + var lastEnd = 0 + while (matcher.find()) { + action(matcher.group(1) ?: continue) + lastEnd = matcher.end() + } + if (lastEnd > 0) { + buffer.delete(0, lastEnd) + } + } + + private fun trimImageScanBuffer(buffer: StringBuilder) { + val maxBufferSize = 16 * 1024 + if (buffer.length <= maxBufferSize) return + val partialImgStart = buffer.lastIndexOf("= 0 -> partialImgStart + else -> max(buffer.length - 1024, 0) + } + buffer.delete(0, keepFrom) + } + private fun checkImage(bytes: ByteArray): Boolean { val op = BitmapFactory.Options() op.inJustDecodeBounds = true diff --git a/app/src/main/java/io/legado/app/model/CacheBook.kt b/app/src/main/java/io/legado/app/model/CacheBook.kt index 6f236aadd..15340c483 100644 --- a/app/src/main/java/io/legado/app/model/CacheBook.kt +++ b/app/src/main/java/io/legado/app/model/CacheBook.kt @@ -103,6 +103,8 @@ object CacheBook { private val _downloadErrorFlow = MutableStateFlow>>("" to emptySet()) val downloadErrorFlow = _downloadErrorFlow.asStateFlow() + @Volatile + private var lastQueueStats = QueueStats(0, 0) val successDownloadSet = ConcurrentHashMap.newKeySet() val errorDownloadMap = ConcurrentHashMap() @@ -123,7 +125,10 @@ object CacheBook { } private fun updateSummary() { - _downloadSummaryFlow.value = downloadSummary + val stats = collectQueueStats() + lastQueueStats = stats + _downloadSummaryFlow.value = + "正在下载:${stats.downloadingCount}|等待中:${stats.waitingCount}|失败:${errorDownloadMap.size}|成功:${successDownloadSet.size}" } @Synchronized @@ -213,12 +218,7 @@ object CacheBook { } val isRun: Boolean - get() { - cacheBookMap.forEach { (_, model) -> - if (model.isRun()) return true - } - return false - } + get() = lastQueueStats.waitingCount > 0 || lastQueueStats.downloadingCount > 0 private fun onTaskQueuesChanged(bookUrl: String) { updateSummary() diff --git a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt index a58a6d24e..cad581fb4 100644 --- a/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt +++ b/app/src/main/java/io/legado/app/ui/book/cache/CacheViewModel.kt @@ -21,6 +21,7 @@ import io.legado.app.model.CacheBook import io.legado.app.help.config.LocalConfig import io.legado.app.service.ExportBookService import io.legado.app.ui.config.cacheConfig.CacheConfig +import io.legado.app.ui.main.bookshelf.toLightBook import io.legado.app.utils.cnCompare import kotlinx.coroutines.Job import kotlinx.coroutines.ensureActive @@ -219,8 +220,8 @@ class CacheViewModel( private fun observeBooks(groupId: Long) { booksJob?.cancel() booksJob = viewModelScope.launch { - bookDao.flowByGroup(groupId).map { books -> - val booksDownload = books.filter { !it.isAudio } + bookDao.flowBookShelfByGroup(groupId).map { books -> + val booksDownload = books.filter { !it.isAudio }.map { it.toLightBook() } when (cacheConfig.getBookSortByGroupId(groupId)) { 1 -> booksDownload.sortedByDescending { it.latestChapterTime } 2 -> booksDownload.sortedWith { o1, o2 -> o1.name.cnCompare(o2.name) } diff --git a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt index c73dcb5c4..0290c38fb 100644 --- a/app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt +++ b/app/src/main/java/io/legado/app/ui/main/bookshelf/BookShelfItem.kt @@ -2,6 +2,7 @@ package io.legado.app.ui.main.bookshelf import androidx.compose.runtime.Stable import io.legado.app.constant.BookType +import io.legado.app.data.entities.Book import kotlin.math.max @Stable @@ -40,3 +41,24 @@ data class BookShelfItem( fun getUnreadChapterNum() = max(totalChapterNum - durChapterIndex - 1, 0) } + +fun BookShelfItem.toLightBook() = Book( + bookUrl = bookUrl, + originName = originName, + name = name, + author = author, + coverUrl = coverUrl, + customCoverUrl = customCoverUrl, + latestChapterTitle = latestChapterTitle, + latestChapterTime = latestChapterTime, + lastCheckCount = lastCheckCount, + totalChapterNum = totalChapterNum, + durChapterTitle = durChapterTitle, + durChapterIndex = durChapterIndex, + durChapterPos = durChapterPos, + durChapterTime = durChapterTime, + type = type, + group = group, + order = order, + canUpdate = canUpdate +)