Fix import book slow

This commit is contained in:
Horis
2025-01-21 20:30:26 +08:00
parent 17a860896e
commit c37c5d4085
6 changed files with 80 additions and 57 deletions
@@ -132,8 +132,11 @@ interface BookDao {
@Query("select 1 from books where bookUrl = :bookUrl")
fun has(bookUrl: String): Boolean?
@Query("select 1 from books where originName = :fileName or origin like '%' || :fileName")
fun hasFile(fileName: String): Boolean?
@Query(
"""select exists(select 1 from books where type & ${BookType.local} > 0
and (originName = :fileName or (origin != '${BookType.localTag}' and origin like '%' || :fileName)))"""
)
fun hasFile(fileName: String): Boolean
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun insert(vararg book: Book)
@@ -16,7 +16,6 @@ import io.legado.app.help.book.BookHelp
import io.legado.app.help.book.ContentProcessor
import io.legado.app.help.book.isEpub
import io.legado.app.help.book.isImage
import io.legado.app.help.book.isPdf
import io.legado.app.help.book.simulatedTotalChapterNum
import io.legado.app.help.config.AppConfig
import io.legado.app.help.config.ReadBookConfig
@@ -0,0 +1,14 @@
package io.legado.app.ui.book.import.local
import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.FileDoc
data class ImportBook(
val file: FileDoc,
var isOnBookShelf: Boolean = LocalBook.isOnBookShelf(file.name)
) {
val name get() = file.name
val isDir get() = file.isDir
val size get() = file.size
val lastModified get() = file.lastModified
}
@@ -101,7 +101,7 @@ class ImportBookActivity : BaseImportBookActivity<ImportBookViewModel>(),
override fun onMenuItemClick(item: MenuItem?): Boolean {
when (item?.itemId) {
R.id.menu_del_selection -> viewModel.deleteDoc(adapter.selectedUris) {
R.id.menu_del_selection -> viewModel.deleteDoc(adapter.selected) {
adapter.removeSelection()
}
}
@@ -118,8 +118,11 @@ class ImportBookActivity : BaseImportBookActivity<ImportBookViewModel>(),
@SuppressLint("NotifyDataSetChanged")
override fun onClickSelectBarMainAction() {
viewModel.addToBookshelf(adapter.selectedUris) {
adapter.selectedUris.clear()
viewModel.addToBookshelf(adapter.selected) {
adapter.selected.forEach {
it.isOnBookShelf = true
}
adapter.selected.clear()
adapter.notifyDataSetChanged()
}
}
@@ -129,6 +132,7 @@ class ImportBookActivity : BaseImportBookActivity<ImportBookViewModel>(),
binding.tvEmptyMsg.setText(R.string.empty_msg_import_book)
binding.recyclerView.layoutManager = LinearLayoutManager(this)
binding.recyclerView.adapter = adapter
binding.recyclerView.recycledViewPool.setMaxRecycledViews(0, 15)
binding.selectActionBar.setMainActionText(R.string.add_to_bookshelf)
binding.selectActionBar.inflateMenu(R.menu.import_book_sel)
binding.selectActionBar.setOnMenuItemClickListener(this)
@@ -212,7 +216,7 @@ class ImportBookActivity : BaseImportBookActivity<ImportBookViewModel>(),
viewModel.sort = sort
putPrefInt(PreferKey.localBookImportSort, sort)
if (scanDocJob?.isActive != true) {
viewModel.dataCallback?.setItems(adapter.getItems())
viewModel.dataCallback?.upAdapter()
}
}
@@ -234,7 +238,7 @@ class ImportBookActivity : BaseImportBookActivity<ImportBookViewModel>(),
path = path + doc.name + File.separator
}
binding.tvPath.text = path
adapter.selectedUris.clear()
adapter.selected.clear()
adapter.clearItems()
viewModel.loadDoc(lastDoc)
}
@@ -295,7 +299,7 @@ class ImportBookActivity : BaseImportBookActivity<ImportBookViewModel>(),
}
override fun upCountView() {
binding.selectActionBar.upCountView(adapter.selectedUris.size, adapter.checkableCount)
binding.selectActionBar.upCountView(adapter.selected.size, adapter.checkableCount)
}
override fun startRead(fileDoc: FileDoc) {
@@ -9,12 +9,18 @@ import io.legado.app.base.adapter.RecyclerAdapter
import io.legado.app.constant.AppConst
import io.legado.app.databinding.ItemImportBookBinding
import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.*
import io.legado.app.utils.ConvertUtils
import io.legado.app.utils.FileDoc
import io.legado.app.utils.gone
import io.legado.app.utils.invisible
import io.legado.app.utils.visible
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Job
class ImportBookAdapter(context: Context, val callBack: CallBack) :
RecyclerAdapter<FileDoc, ItemImportBookBinding>(context) {
val selectedUris = hashSetOf<String>()
RecyclerAdapter<ImportBook, ItemImportBookBinding>(context) {
val selected = hashSetOf<ImportBook>()
var checkableCount = 0
override fun getViewBinding(parent: ViewGroup): ItemImportBookBinding {
@@ -28,7 +34,7 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) :
override fun convert(
holder: ItemViewHolder,
binding: ItemImportBookBinding,
item: FileDoc,
item: ImportBook,
payloads: MutableList<Any>
) {
binding.run {
@@ -40,7 +46,7 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) :
llBrief.gone()
cbSelect.isChecked = false
} else {
if (isOnBookShelf(item)) {
if (item.isOnBookShelf) {
ivIcon.setImageResource(R.drawable.ic_book_has)
ivIcon.visible()
cbSelect.invisible()
@@ -52,11 +58,11 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) :
tvTag.text = item.name.substringAfterLast(".")
tvSize.text = ConvertUtils.formatFileSize(item.size)
tvDate.text = AppConst.dateFormat.format(item.lastModified)
cbSelect.isChecked = selectedUris.contains(item.toString())
cbSelect.isChecked = selected.contains(item)
}
tvName.text = item.name
} else {
cbSelect.isChecked = selectedUris.contains(item.toString())
cbSelect.isChecked = selected.contains(item)
}
}
}
@@ -65,18 +71,18 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) :
holder.itemView.setOnClickListener {
getItem(holder.layoutPosition)?.let {
if (it.isDir) {
callBack.nextDoc(it)
} else if (!isOnBookShelf(it)) {
if (!selectedUris.contains(it.toString())) {
selectedUris.add(it.toString())
callBack.nextDoc(it.file)
} else if (!it.isOnBookShelf) {
if (!selected.contains(it)) {
selected.add(it)
} else {
selectedUris.remove(it.toString())
selected.remove(it)
}
notifyItemChanged(holder.layoutPosition, true)
callBack.upCountView()
} else {
/* 点击开始阅读 */
callBack.startRead(it)
callBack.startRead(it.file)
}
}
}
@@ -89,7 +95,7 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) :
private fun upCheckableCount() {
checkableCount = 0
getItems().forEach {
if (!it.isDir && !isOnBookShelf(it)) {
if (!it.isDir && !it.isOnBookShelf) {
checkableCount++
}
}
@@ -100,12 +106,12 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) :
fun selectAll(selectAll: Boolean) {
if (selectAll) {
getItems().forEach {
if (!it.isDir && !isOnBookShelf(it)) {
selectedUris.add(it.toString())
if (!it.isDir && !it.isOnBookShelf) {
selected.add(it)
}
}
} else {
selectedUris.clear()
selected.clear()
}
notifyDataSetChanged()
callBack.upCountView()
@@ -113,11 +119,11 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) :
fun revertSelection() {
getItems().forEach {
if (!it.isDir && !isOnBookShelf(it)) {
if (selectedUris.contains(it.toString())) {
selectedUris.remove(it.toString())
if (!it.isDir && !it.isOnBookShelf) {
if (selected.contains(it)) {
selected.remove(it)
} else {
selectedUris.add(it.toString())
selected.add(it)
}
}
}
@@ -127,7 +133,7 @@ class ImportBookAdapter(context: Context, val callBack: CallBack) :
fun removeSelection() {
for (i in getItems().lastIndex downTo 0) {
if (getItem(i)?.toString() in selectedUris) {
if (getItem(i) in selected) {
removeItem(i)
}
}
@@ -1,8 +1,6 @@
package io.legado.app.ui.book.import.local
import android.app.Application
import android.net.Uri
import androidx.documentfile.provider.DocumentFile
import io.legado.app.base.BaseViewModel
import io.legado.app.constant.AppLog
import io.legado.app.constant.AppPattern.archiveFileRegex
@@ -11,19 +9,19 @@ import io.legado.app.constant.PreferKey
import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.AlphanumComparator
import io.legado.app.utils.FileDoc
import io.legado.app.utils.delete
import io.legado.app.utils.getPrefInt
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.list
import io.legado.app.utils.toastOnUi
import kotlinx.coroutines.Dispatchers.IO
import kotlinx.coroutines.Dispatchers.Main
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.channels.awaitClose
import kotlinx.coroutines.flow.callbackFlow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.isActive
import kotlinx.coroutines.withContext
import java.io.File
import java.util.Collections
import kotlin.coroutines.coroutineContext
@@ -33,20 +31,24 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
var sort = context.getPrefInt(PreferKey.localBookImportSort)
var dataCallback: DataCallback? = null
var dataFlowStart: (() -> Unit)? = null
val dataFlow = callbackFlow<List<FileDoc>> {
val dataFlow = callbackFlow<List<ImportBook>> {
val list = Collections.synchronizedList(ArrayList<FileDoc>())
val list = Collections.synchronizedList(ArrayList<ImportBook>())
dataCallback = object : DataCallback {
override fun setItems(fileDocs: List<FileDoc>) {
list.clear()
list.addAll(fileDocs)
fileDocs.mapTo(list) {
ImportBook(it)
}
trySend(list)
}
override fun addItems(fileDocs: List<FileDoc>) {
list.addAll(fileDocs)
fileDocs.mapTo(list) {
ImportBook(it)
}
trySend(list)
}
@@ -64,6 +66,10 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
)
}
}
override fun upAdapter() {
trySend(list)
}
}
withContext(Main) {
@@ -76,21 +82,17 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
}.map { docList ->
val comparator = when (sort) {
2 -> compareBy<FileDoc>({ !it.isDir }, { -it.lastModified })
2 -> compareBy<ImportBook>({ !it.isDir }, { -it.lastModified })
1 -> compareBy({ !it.isDir }, { -it.size })
else -> compareBy { !it.isDir }
} then compareBy(AlphanumComparator) { it.name }
docList.sortedWith(comparator)
}.flowOn(IO)
fun addToBookshelf(uriList: HashSet<String>, finally: () -> Unit) {
fun addToBookshelf(bookList: HashSet<ImportBook>, finally: () -> Unit) {
execute {
val fileUris = uriList.map {
if (it.isContentScheme()) {
Uri.parse(it)
} else {
Uri.fromFile(File(it))
}
val fileUris = bookList.map {
it.file.uri
}
LocalBook.importFiles(fileUris)
}.onError {
@@ -101,17 +103,10 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
}
}
fun deleteDoc(uriList: HashSet<String>, finally: () -> Unit) {
fun deleteDoc(bookList: HashSet<ImportBook>, finally: () -> Unit) {
execute {
uriList.forEach {
val uri = Uri.parse(it)
if (uri.isContentScheme()) {
DocumentFile.fromSingleUri(context, uri)?.delete()
} else {
uri.path?.let { path ->
File(path).delete()
}
}
bookList.forEach {
it.file.delete()
}
}.onFinally {
finally.invoke()
@@ -190,6 +185,8 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
fun screen(key: String?)
fun upAdapter()
}
}