mirror of
https://github.com/gedoor/legado.git
synced 2024-09-01 09:34:25 +08:00
本地书籍采用MediaStore保存到Download/books
This commit is contained in:
parent
23447a51f2
commit
4a2e62d413
90
app/src/main/java/io/legado/app/help/BookMediaStore.kt
Normal file
90
app/src/main/java/io/legado/app/help/BookMediaStore.kt
Normal 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
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
@ -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)
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user