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

This commit is contained in:
gedoor 2021-12-27 20:57:14 +08:00
parent 23447a51f2
commit 4a2e62d413
2 changed files with 151 additions and 19 deletions

View File

@ -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
}
}

View File

@ -1,13 +1,19 @@
package io.legado.app.ui.association package io.legado.app.ui.association
import android.app.Application import android.app.Application
import android.app.RecoverableSecurityException
import android.content.IntentSender
import android.net.Uri import android.net.Uri
import android.os.Build
import androidx.documentfile.provider.DocumentFile import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import io.legado.app.help.BookMediaStore
import io.legado.app.model.NoStackTraceException import io.legado.app.model.NoStackTraceException
import io.legado.app.model.localBook.LocalBook import io.legado.app.model.localBook.LocalBook
import io.legado.app.utils.isContentScheme
import io.legado.app.utils.isJson import io.legado.app.utils.isJson
import io.legado.app.utils.readText import io.legado.app.utils.readText
import splitties.init.appCtx
import timber.log.Timber import timber.log.Timber
import java.io.File import java.io.File
@ -18,7 +24,9 @@ class FileAssociationViewModel(application: Application) : BaseAssociationViewMo
val importReplaceRuleLive = MutableLiveData<String>() val importReplaceRuleLive = MutableLiveData<String>()
val openBookLiveData = MutableLiveData<String>() val openBookLiveData = MutableLiveData<String>()
val errorLiveData = MutableLiveData<String>() val errorLiveData = MutableLiveData<String>()
val recoverErrorLiveData = MutableLiveData<IntentSender>()
@Suppress("BlockingMethodInNonBlockingContext")
fun dispatchIndent(uri: Uri, finally: (title: String, msg: String) -> Unit) { fun dispatchIndent(uri: Uri, finally: (title: String, msg: String) -> Unit) {
execute { execute {
//如果是普通的url需要根据返回的内容判断是什么 //如果是普通的url需要根据返回的内容判断是什么
@ -27,34 +35,68 @@ class FileAssociationViewModel(application: Application) : BaseAssociationViewMo
File(uri.path.toString()).readText() File(uri.path.toString()).readText()
} else { } else {
DocumentFile.fromSingleUri(context, uri)?.readText(context) DocumentFile.fromSingleUri(context, uri)?.readText(context)
} } ?: throw NoStackTraceException("文件不存在")
content?.let { if (content.isJson()) {
if (it.isJson()) { //暂时根据文件内容判断属于什么
//暂时根据文件内容判断属于什么 when {
when { content.contains("bookSourceUrl") ->
content.contains("bookSourceUrl") -> importBookSourceLive.postValue(content)
importBookSourceLive.postValue(it) content.contains("sourceUrl") ->
content.contains("sourceUrl") -> importRssSourceLive.postValue(content)
importRssSourceLive.postValue(it) content.contains("pattern") ->
content.contains("pattern") -> importReplaceRuleLive.postValue(content)
importReplaceRuleLive.postValue(it) content.contains("themeName") ->
content.contains("themeName") -> importTheme(content, finally)
importTheme(content, finally) content.contains("name") && content.contains("rule") ->
content.contains("name") && content.contains("rule") -> importTextTocRule(content, finally)
importTextTocRule(content, finally) content.contains("name") && content.contains("url") ->
content.contains("name") && content.contains("url") -> importHttpTTS(content, finally)
importHttpTTS(content, finally) else -> errorLiveData.postValue("格式不对")
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 { } else {
val book = LocalBook.importFile(uri) val book = LocalBook.importFile(uri)
openBookLiveData.postValue(book.bookUrl) openBookLiveData.postValue(book.bookUrl)
} }
} ?: throw NoStackTraceException("文件不存在") }
} else { } else {
onLineImportLive.postValue(uri) onLineImportLive.postValue(uri)
} }
}.onError { }.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) Timber.e(it)
errorLiveData.postValue(it.localizedMessage) errorLiveData.postValue(it.localizedMessage)
} }