pull/2437/head
kunfei 3 years ago
parent 6265daa5d9
commit a51348f70f
  1. 2
      app/src/main/java/io/legado/app/ui/book/import/local/ImportBookActivity.kt
  2. 8
      app/src/main/java/io/legado/app/ui/book/import/local/ImportBookViewModel.kt
  3. 8
      app/src/main/java/io/legado/app/ui/font/FontSelectDialog.kt
  4. 238
      app/src/main/java/io/legado/app/utils/DocumentExtensions.kt
  5. 58
      app/src/main/java/io/legado/app/utils/DocumentUtils.kt
  6. 182
      app/src/main/java/io/legado/app/utils/FileDocExtensions.kt
  7. 21
      app/src/main/java/io/legado/app/utils/FileExtensions.kt
  8. 3
      app/src/main/java/io/legado/app/utils/FileUtils.kt

@ -233,7 +233,7 @@ class ImportBookActivity : BaseImportBookActivity<ActivityImportBookBinding, Imp
binding.tvPath.text = path
adapter.selectedUris.clear()
adapter.clearItems()
viewModel.loadDoc(lastDoc.uri)
viewModel.loadDoc(lastDoc)
}
/**

@ -102,16 +102,16 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
}
}
fun loadDoc(uri: Uri) {
fun loadDoc(fileDoc: FileDoc) {
execute {
val docList = DocumentUtils.listFiles(uri) { item ->
val docList = fileDoc.list { item ->
when {
item.name.startsWith(".") -> false
item.isDir -> true
else -> item.name.matches(bookFileRegex)
}
}
dataCallback?.setItems(docList)
dataCallback?.setItems(docList!!)
}.onError {
context.toastOnUi("获取文件列表出错\n${it.localizedMessage}")
}
@ -132,7 +132,7 @@ class ImportBookViewModel(application: Application) : BaseViewModel(application)
}
kotlin.runCatching {
val list = ArrayList<FileDoc>()
DocumentUtils.listFiles(fileDoc.uri).forEach { docItem ->
fileDoc.list()!!.forEach { docItem ->
if (!scope.isActive) {
finally?.invoke()
return

@ -117,16 +117,16 @@ class FontSelectDialog : BaseDialogFragment(R.layout.dialog_font_select),
private fun getLocalFonts(): ArrayList<FileDoc> {
val path = FileUtils.getPath(requireContext().externalFiles, "font")
return DocumentUtils.listFiles(path) {
return File(path).listFileDocs {
it.name.matches(fontRegex)
}
}
private fun loadFontFiles(doc: DocumentFile) {
execute {
val fontItems = DocumentUtils.listFiles(doc.uri) {
val fontItems = doc.listFileDocs {
it.name.matches(fontRegex)
}
}!!
mergeFontItems(fontItems, getLocalFonts())
}.onSuccess {
adapter.setItems(it)
@ -147,7 +147,7 @@ class FontSelectDialog : BaseDialogFragment(R.layout.dialog_font_select),
private fun loadFontFiles(path: String) {
execute {
val fontItems = DocumentUtils.listFiles(path) {
val fontItems = File(path).listFileDocs {
it.name.matches(fontRegex)
}
mergeFontItems(fontItems, getLocalFonts())

@ -1,238 +0,0 @@
package io.legado.app.utils
import android.app.DownloadManager
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile
import io.legado.app.exception.NoStackTraceException
import splitties.init.appCtx
import splitties.systemservices.downloadManager
import java.io.File
import java.nio.charset.Charset
@Suppress("MemberVisibilityCanBePrivate")
object DocumentUtils {
fun exists(root: DocumentFile, fileName: String, vararg subDirs: String): Boolean {
val parent = getDirDocument(root, *subDirs) ?: return false
return parent.findFile(fileName)?.exists() ?: false
}
fun delete(root: DocumentFile, fileName: String, vararg subDirs: String) {
val parent: DocumentFile? = createFolderIfNotExist(root, *subDirs)
parent?.findFile(fileName)?.delete()
}
fun createFileIfNotExist(
root: DocumentFile,
fileName: String,
mimeType: String = "",
vararg subDirs: String
): DocumentFile? {
val parent: DocumentFile? = createFolderIfNotExist(root, *subDirs)
return parent?.findFile(fileName) ?: parent?.createFile(mimeType, fileName)
}
fun createFolderIfNotExist(root: DocumentFile, vararg subDirs: String): DocumentFile? {
var parent: DocumentFile? = root
for (subDirName in subDirs) {
val subDir = parent?.findFile(subDirName)
?: parent?.createDirectory(subDirName)
parent = subDir
}
return parent
}
fun getDirDocument(root: DocumentFile, vararg subDirs: String): DocumentFile? {
var parent = root
for (subDirName in subDirs) {
val subDir = parent.findFile(subDirName)
parent = subDir ?: return null
}
return parent
}
private val projection by lazy {
arrayOf(
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_LAST_MODIFIED,
DocumentsContract.Document.COLUMN_SIZE,
DocumentsContract.Document.COLUMN_MIME_TYPE
)
}
/**
* DocumentFile listFiles() 非常的慢,所以这里直接从数据库查询
*/
@Throws(Exception::class)
fun listFiles(uri: Uri, filter: ((file: FileDoc) -> Boolean)? = null): ArrayList<FileDoc> {
if (!uri.isContentScheme()) {
return listFiles(uri.path!!, filter)
}
val childrenUri = DocumentsContract
.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri))
val docList = arrayListOf<FileDoc>()
var cursor: Cursor? = null
try {
cursor = appCtx.contentResolver.query(
childrenUri, projection, null, null, DocumentsContract.Document.COLUMN_DISPLAY_NAME
)
cursor?.let {
val ici = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
val nci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME)
val sci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_SIZE)
val mci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE)
val dci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED)
if (cursor.moveToFirst()) {
do {
val item = FileDoc(
name = cursor.getString(nci),
isDir = cursor.getString(mci) == DocumentsContract.Document.MIME_TYPE_DIR,
size = cursor.getLong(sci),
lastModified = cursor.getLong(dci),
uri = DocumentsContract
.buildDocumentUriUsingTree(uri, cursor.getString(ici))
)
if (filter == null || filter.invoke(item)) {
docList.add(item)
}
} while (cursor.moveToNext())
}
}
} finally {
cursor?.close()
}
return docList
}
@Throws(Exception::class)
fun listFiles(path: String, filter: ((file: FileDoc) -> Boolean)? = null): ArrayList<FileDoc> {
val docList = arrayListOf<FileDoc>()
val file = File(path)
file.listFiles()?.forEach {
val item = FileDoc(
it.name,
it.isDirectory,
it.length(),
it.lastModified(),
Uri.fromFile(it)
)
if (filter == null || filter.invoke(item)) {
docList.add(item)
}
}
return docList
}
}
data class FileDoc(
val name: String,
val isDir: Boolean,
val size: Long,
val lastModified: Long,
val uri: Uri
) {
override fun toString(): String {
return if (uri.isContentScheme()) uri.toString() else uri.path!!
}
val isContentScheme get() = uri.isContentScheme()
fun readBytes(): ByteArray {
return uri.readBytes(appCtx)
}
companion object {
fun fromUri(uri: Uri, isDir: Boolean): FileDoc {
if (uri.isContentScheme()) {
val doc = if (isDir) {
DocumentFile.fromTreeUri(appCtx, uri)!!
} else if (uri.host == "downloads") {
val query = DownloadManager.Query()
query.setFilterById(uri.lastPathSegment!!.toLong())
downloadManager.query(query).use {
if (it.moveToFirst()) {
val lUriColum = it.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
val lUri = it.getString(lUriColum)
DocumentFile.fromSingleUri(appCtx, Uri.parse(lUri))!!
} else {
DocumentFile.fromSingleUri(appCtx, uri)!!
}
}
} else {
DocumentFile.fromSingleUri(appCtx, uri)!!
}
return FileDoc(doc.name ?: "", true, doc.length(), doc.lastModified(), doc.uri)
}
val file = File(uri.path!!)
return FileDoc(file.name, isDir, file.length(), file.lastModified(), uri)
}
fun fromDocumentFile(doc: DocumentFile): FileDoc {
return FileDoc(
name = doc.name ?: "",
isDir = doc.isDirectory,
size = doc.length(),
lastModified = doc.lastModified(),
uri = doc.uri
)
}
fun fromFile(file: File): FileDoc {
return FileDoc(
name = file.name,
isDir = file.isDirectory,
size = file.length(),
lastModified = file.lastModified(),
uri = Uri.fromFile(file)
)
}
}
}
fun FileDoc.list(filter: ((file: FileDoc) -> Boolean)? = null): ArrayList<FileDoc>? {
if (isDir) {
return DocumentUtils.listFiles(uri, filter)
}
return null
}
fun FileDoc.find(name: String): FileDoc? {
return list {
it.name == name
}?.firstOrNull()
}
@Throws(Exception::class)
fun DocumentFile.writeText(context: Context, data: String, charset: Charset = Charsets.UTF_8) {
uri.writeText(context, data, charset)
}
@Throws(Exception::class)
fun DocumentFile.writeBytes(context: Context, data: ByteArray) {
uri.writeBytes(context, data)
}
@Throws(Exception::class)
fun DocumentFile.readText(context: Context): String {
return String(readBytes(context))
}
@Throws(Exception::class)
fun DocumentFile.readBytes(context: Context): ByteArray {
return context.contentResolver.openInputStream(uri)?.let {
val len: Int = it.available()
val buffer = ByteArray(len)
it.read(buffer)
it.close()
return buffer
} ?: throw NoStackTraceException("打开文件失败\n${uri}")
}

@ -0,0 +1,58 @@
package io.legado.app.utils
import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile
@Suppress("MemberVisibilityCanBePrivate")
object DocumentUtils {
fun exists(root: DocumentFile, fileName: String, vararg subDirs: String): Boolean {
val parent = getDirDocument(root, *subDirs) ?: return false
return parent.findFile(fileName)?.exists() ?: false
}
fun delete(root: DocumentFile, fileName: String, vararg subDirs: String) {
val parent: DocumentFile? = createFolderIfNotExist(root, *subDirs)
parent?.findFile(fileName)?.delete()
}
fun createFileIfNotExist(
root: DocumentFile,
fileName: String,
mimeType: String = "",
vararg subDirs: String
): DocumentFile? {
val parent: DocumentFile? = createFolderIfNotExist(root, *subDirs)
return parent?.findFile(fileName) ?: parent?.createFile(mimeType, fileName)
}
fun createFolderIfNotExist(root: DocumentFile, vararg subDirs: String): DocumentFile? {
var parent: DocumentFile? = root
for (subDirName in subDirs) {
val subDir = parent?.findFile(subDirName)
?: parent?.createDirectory(subDirName)
parent = subDir
}
return parent
}
fun getDirDocument(root: DocumentFile, vararg subDirs: String): DocumentFile? {
var parent = root
for (subDirName in subDirs) {
val subDir = parent.findFile(subDirName)
parent = subDir ?: return null
}
return parent
}
val projection by lazy {
arrayOf(
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
DocumentsContract.Document.COLUMN_DISPLAY_NAME,
DocumentsContract.Document.COLUMN_LAST_MODIFIED,
DocumentsContract.Document.COLUMN_SIZE,
DocumentsContract.Document.COLUMN_MIME_TYPE
)
}
}

@ -0,0 +1,182 @@
package io.legado.app.utils
import android.app.DownloadManager
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.provider.DocumentsContract
import androidx.documentfile.provider.DocumentFile
import io.legado.app.exception.NoStackTraceException
import splitties.init.appCtx
import splitties.systemservices.downloadManager
import java.io.File
import java.nio.charset.Charset
data class FileDoc(
val name: String,
val isDir: Boolean,
val size: Long,
val lastModified: Long,
val uri: Uri
) {
override fun toString(): String {
return if (uri.isContentScheme()) uri.toString() else uri.path!!
}
val isContentScheme get() = uri.isContentScheme()
fun readBytes(): ByteArray {
return uri.readBytes(appCtx)
}
companion object {
fun fromUri(uri: Uri, isDir: Boolean): FileDoc {
if (uri.isContentScheme()) {
val doc = if (isDir) {
DocumentFile.fromTreeUri(appCtx, uri)!!
} else if (uri.host == "downloads") {
val query = DownloadManager.Query()
query.setFilterById(uri.lastPathSegment!!.toLong())
downloadManager.query(query).use {
if (it.moveToFirst()) {
val lUriColum = it.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI)
val lUri = it.getString(lUriColum)
DocumentFile.fromSingleUri(appCtx, Uri.parse(lUri))!!
} else {
DocumentFile.fromSingleUri(appCtx, uri)!!
}
}
} else {
DocumentFile.fromSingleUri(appCtx, uri)!!
}
return FileDoc(doc.name ?: "", true, doc.length(), doc.lastModified(), doc.uri)
}
val file = File(uri.path!!)
return FileDoc(file.name, isDir, file.length(), file.lastModified(), uri)
}
fun fromDocumentFile(doc: DocumentFile): FileDoc {
return FileDoc(
name = doc.name ?: "",
isDir = doc.isDirectory,
size = doc.length(),
lastModified = doc.lastModified(),
uri = doc.uri
)
}
fun fromFile(file: File): FileDoc {
return FileDoc(
name = file.name,
isDir = file.isDirectory,
size = file.length(),
lastModified = file.lastModified(),
uri = Uri.fromFile(file)
)
}
}
}
/**
* 过滤器
*/
typealias FileDocFilter = (file: FileDoc) -> Boolean
/**
* 返回子文件列表,如果不是文件夹则返回null
*/
fun FileDoc.list(filter: FileDocFilter? = null): ArrayList<FileDoc>? {
if (isDir) {
if (uri.isContentScheme()) {
/**
* DocumentFile listFiles() 非常的慢,所以这里直接从数据库查询
*/
val childrenUri = DocumentsContract
.buildChildDocumentsUriUsingTree(uri, DocumentsContract.getDocumentId(uri))
val docList = arrayListOf<FileDoc>()
var cursor: Cursor? = null
try {
cursor = appCtx.contentResolver.query(
childrenUri,
DocumentUtils.projection,
null,
null,
DocumentsContract.Document.COLUMN_DISPLAY_NAME
)
cursor?.let {
val ici = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DOCUMENT_ID)
val nci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_DISPLAY_NAME)
val sci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_SIZE)
val mci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_MIME_TYPE)
val dci = cursor.getColumnIndex(DocumentsContract.Document.COLUMN_LAST_MODIFIED)
if (cursor.moveToFirst()) {
do {
val item = FileDoc(
name = cursor.getString(nci),
isDir = cursor.getString(mci) == DocumentsContract.Document.MIME_TYPE_DIR,
size = cursor.getLong(sci),
lastModified = cursor.getLong(dci),
uri = DocumentsContract.buildDocumentUriUsingTree(
uri,
cursor.getString(ici)
)
)
if (filter == null || filter.invoke(item)) {
docList.add(item)
}
} while (cursor.moveToNext())
}
}
} finally {
cursor?.close()
}
return docList
} else {
return File(uri.path!!).listFileDocs(filter)
}
}
return null
}
fun FileDoc.find(name: String): FileDoc? {
return list {
it.name == name
}?.firstOrNull()
}
/**
* DocumentFile listFiles() 非常的慢,尽量不要使用
*/
fun DocumentFile.listFileDocs(filter: FileDocFilter? = null): ArrayList<FileDoc>? {
return FileDoc.fromDocumentFile(this).list(filter)
}
@Throws(Exception::class)
fun DocumentFile.writeText(context: Context, data: String, charset: Charset = Charsets.UTF_8) {
uri.writeText(context, data, charset)
}
@Throws(Exception::class)
fun DocumentFile.writeBytes(context: Context, data: ByteArray) {
uri.writeBytes(context, data)
}
@Throws(Exception::class)
fun DocumentFile.readText(context: Context): String {
return String(readBytes(context))
}
@Throws(Exception::class)
fun DocumentFile.readBytes(context: Context): ByteArray {
return context.contentResolver.openInputStream(uri)?.let {
val len: Int = it.available()
val buffer = ByteArray(len)
it.read(buffer)
it.close()
return buffer
} ?: throw NoStackTraceException("打开文件失败\n${uri}")
}

@ -1,5 +1,6 @@
package io.legado.app.utils
import android.net.Uri
import java.io.File
fun File.getFile(vararg subDirFiles: String): File {
@ -11,3 +12,23 @@ fun File.exists(vararg subDirFiles: String): Boolean {
return getFile(*subDirFiles).exists()
}
@Throws(Exception::class)
fun File.listFileDocs(filter: FileDocFilter? = null): ArrayList<FileDoc> {
val docList = arrayListOf<FileDoc>()
listFiles()
listFiles()?.forEach {
val item = FileDoc(
it.name,
it.isDirectory,
it.length(),
it.lastModified(),
Uri.fromFile(it)
)
if (filter == null || filter.invoke(item)) {
docList.add(item)
}
}
return docList
}

@ -4,7 +4,6 @@ import android.os.Environment
import android.webkit.MimeTypeMap
import androidx.annotation.IntDef
import splitties.init.appCtx
import java.io.*
import java.nio.charset.Charset
import java.text.SimpleDateFormat
@ -115,7 +114,7 @@ object FileUtils {
const val BY_EXTENSION_DESC = 7
@IntDef(value = [BY_NAME_ASC, BY_NAME_DESC, BY_TIME_ASC, BY_TIME_DESC, BY_SIZE_ASC, BY_SIZE_DESC, BY_EXTENSION_ASC, BY_EXTENSION_DESC])
@kotlin.annotation.Retention(AnnotationRetention.SOURCE)
@Retention(AnnotationRetention.SOURCE)
annotation class SortType
/**

Loading…
Cancel
Save