pull/2437/head
parent
6265daa5d9
commit
a51348f70f
@ -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}") |
||||||
|
} |
Loading…
Reference in new issue