本地书籍采用MediaStore保存到Download/books

pull/1486/head
gedoor 3 years ago
parent 23447a51f2
commit 4a2e62d413
  1. 90
      app/src/main/java/io/legado/app/help/BookMediaStore.kt
  2. 56
      app/src/main/java/io/legado/app/ui/association/FileAssociationViewModel.kt

@ -0,0 +1,90 @@
package io.legado.app.help
import android.content.ContentUris
import android.content.ContentValues
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import androidx.core.content.FileProvider
import androidx.documentfile.provider.DocumentFile
import io.legado.app.constant.AppConst
import io.legado.app.utils.FileDoc
import io.legado.app.utils.FileUtils.getMimeType
import splitties.init.appCtx
import java.io.File
import java.util.*
object BookMediaStore {
private val DOWNLOAD_DIR =
Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS)
fun insertBook(doc: DocumentFile): Uri? {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val bookDetails = ContentValues().apply {
put(MediaStore.Downloads.RELATIVE_PATH, "Download${File.separator}books")
put(MediaStore.MediaColumns.DISPLAY_NAME, doc.name)
put(MediaStore.MediaColumns.MIME_TYPE, getMimeType(doc.name!!))
put(MediaStore.MediaColumns.SIZE, doc.length())
}
appCtx.contentResolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, bookDetails)
} else {
val destinyFile = File(DOWNLOAD_DIR, doc.name!!)
FileProvider.getUriForFile(appCtx, AppConst.authority, destinyFile)
}?.also { uri ->
appCtx.contentResolver.openOutputStream(uri).use { outputStream ->
val brr = ByteArray(1024)
var len: Int
val bufferedInputStream = appCtx.contentResolver.openInputStream(doc.uri)!!
while ((bufferedInputStream.read(brr, 0, brr.size).also { len = it }) != -1) {
outputStream?.write(brr, 0, len)
}
outputStream?.flush()
bufferedInputStream.close()
}
}
}
fun getBook(name: String): FileDoc? {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
val projection = arrayOf(
MediaStore.Downloads._ID,
MediaStore.Downloads.DISPLAY_NAME,
MediaStore.Downloads.SIZE,
MediaStore.Downloads.DATE_MODIFIED
)
val selection =
"${MediaStore.Downloads.RELATIVE_PATH} like 'Download${File.separator}books${File.separator}%'"
val sortOrder = "${MediaStore.Downloads.DISPLAY_NAME} ASC"
appCtx.contentResolver.query(
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
projection,
selection,
emptyArray(),
sortOrder
)?.use {
val idColumn = it.getColumnIndex(projection[0])
val nameColumn = it.getColumnIndex(projection[1])
val sizeColumn = it.getColumnIndex(projection[2])
val dateColumn = it.getColumnIndex(projection[3])
if (it.moveToNext()) {
val id = it.getLong(idColumn)
return FileDoc(
name = it.getString(nameColumn),
isDir = false,
size = it.getLong(sizeColumn),
date = Date(it.getLong(dateColumn)),
uri = ContentUris.withAppendedId(
MediaStore.Downloads.EXTERNAL_CONTENT_URI,
id
)
)
}
}
}
return null
}
}

@ -1,13 +1,19 @@
package io.legado.app.ui.association
import android.app.Application
import android.app.RecoverableSecurityException
import android.content.IntentSender
import android.net.Uri
import android.os.Build
import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.MutableLiveData
import io.legado.app.help.BookMediaStore
import io.legado.app.model.NoStackTraceException
import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.isJson
import io.legado.app.utils.readText
import splitties.init.appCtx
import timber.log.Timber
import java.io.File
@ -18,7 +24,9 @@ class FileAssociationViewModel(application: Application) : BaseAssociationViewMo
val importReplaceRuleLive = MutableLiveData<String>()
val openBookLiveData = MutableLiveData<String>()
val errorLiveData = MutableLiveData<String>()
val recoverErrorLiveData = MutableLiveData<IntentSender>()
@Suppress("BlockingMethodInNonBlockingContext")
fun dispatchIndent(uri: Uri, finally: (title: String, msg: String) -> Unit) {
execute {
//如果是普通的url,需要根据返回的内容判断是什么
@ -27,17 +35,16 @@ class FileAssociationViewModel(application: Application) : BaseAssociationViewMo
File(uri.path.toString()).readText()
} else {
DocumentFile.fromSingleUri(context, uri)?.readText(context)
}
content?.let {
if (it.isJson()) {
} ?: throw NoStackTraceException("文件不存在")
if (content.isJson()) {
//暂时根据文件内容判断属于什么
when {
content.contains("bookSourceUrl") ->
importBookSourceLive.postValue(it)
importBookSourceLive.postValue(content)
content.contains("sourceUrl") ->
importRssSourceLive.postValue(it)
importRssSourceLive.postValue(content)
content.contains("pattern") ->
importReplaceRuleLive.postValue(it)
importReplaceRuleLive.postValue(content)
content.contains("themeName") ->
importTheme(content, finally)
content.contains("name") && content.contains("rule") ->
@ -46,15 +53,50 @@ class FileAssociationViewModel(application: Application) : BaseAssociationViewMo
importHttpTTS(content, finally)
else -> errorLiveData.postValue("格式不对")
}
} else {
if (uri.isContentScheme()) {
val doc = DocumentFile.fromSingleUri(appCtx, uri)!!
val bookDoc = BookMediaStore.getBook(doc.name!!)
if (bookDoc == null) {
val bookUri = BookMediaStore.insertBook(doc)
val book = LocalBook.importFile(bookUri!!)
openBookLiveData.postValue(book.bookUrl)
} else {
if (doc.lastModified() > bookDoc.date.time) {
context.contentResolver.openOutputStream(bookDoc.uri)
.use { outputStream ->
val brr = ByteArray(1024)
var len: Int
val bufferedInputStream =
appCtx.contentResolver.openInputStream(doc.uri)!!
while ((bufferedInputStream.read(brr, 0, brr.size)
.also { len = it }) != -1
) {
outputStream?.write(brr, 0, len)
}
outputStream?.flush()
bufferedInputStream.close()
}
}
val book = LocalBook.importFile(bookDoc.uri)
openBookLiveData.postValue(book.bookUrl)
}
} else {
val book = LocalBook.importFile(uri)
openBookLiveData.postValue(book.bookUrl)
}
} ?: throw NoStackTraceException("文件不存在")
}
} else {
onLineImportLive.postValue(uri)
}
}.onError {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
if (it is RecoverableSecurityException) {
val intentSender = it.userAction.actionIntent.intentSender
recoverErrorLiveData.postValue(intentSender)
return@onError
}
}
Timber.e(it)
errorLiveData.postValue(it.localizedMessage)
}

Loading…
Cancel
Save