rewrite some file operations to work with FileDirItem instead of files

This commit is contained in:
tibbi 2018-02-14 23:27:36 +01:00
parent f73c3c36a6
commit 78c1578b89
7 changed files with 140 additions and 102 deletions

View file

@ -14,7 +14,6 @@ import android.support.v4.app.ActivityCompat
import android.support.v4.util.Pair
import android.support.v7.app.AppCompatActivity
import android.text.Html
import android.util.Log
import android.view.MenuItem
import android.view.WindowManager
import com.simplemobiletools.commons.R
@ -25,6 +24,7 @@ import com.simplemobiletools.commons.dialogs.WritePermissionDialog
import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.interfaces.CopyMoveListener
import com.simplemobiletools.commons.models.FileDirItem
import java.io.File
import java.util.*
@ -168,7 +168,6 @@ open class BaseSimpleActivity : AppCompatActivity() {
fun handleSAFDialog(file: File, callback: () -> Unit): Boolean {
return if (isShowingSAFDialog(file, baseConfig.treeUri, OPEN_DOCUMENT_TREE)) {
Log.e("DEBUG", "SAF dialog")
funAfterSAFPermission = callback
true
} else {
@ -177,7 +176,7 @@ open class BaseSimpleActivity : AppCompatActivity() {
}
}
fun copyMoveFilesTo(files: ArrayList<File>, source: String, destination: String, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean,
fun copyMoveFilesTo(fileDirItems: ArrayList<FileDirItem>, source: String, destination: String, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean,
copyHidden: Boolean, callback: () -> Unit) {
if (source == destination) {
toast(R.string.source_and_destination_same)
@ -193,31 +192,32 @@ open class BaseSimpleActivity : AppCompatActivity() {
handleSAFDialog(destinationFolder) {
copyMoveCallback = callback
if (isCopyOperation) {
startCopyMove(files, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden)
startCopyMove(fileDirItems, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden)
} else {
if (isPathOnSD(source) || isPathOnSD(destination) || files.first().isDirectory || isNougatPlus()) {
if (isPathOnSD(source) || isPathOnSD(destination) || fileDirItems.first().isDirectory || isNougatPlus()) {
handleSAFDialog(File(source)) {
startCopyMove(files, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden)
startCopyMove(fileDirItems, destinationFolder, isCopyOperation, copyPhotoVideoOnly, copyHidden)
}
} else {
toast(R.string.moving)
val updatedFiles = ArrayList<File>(files.size * 2)
updatedFiles.addAll(files)
val updatedFiles = ArrayList<FileDirItem>(fileDirItems.size * 2)
updatedFiles.addAll(fileDirItems)
try {
for (oldFile in files) {
val newFile = File(destinationFolder, oldFile.name)
if (!newFile.exists() && oldFile.renameTo(newFile)) {
for (oldFileDirItem in fileDirItems) {
val newFile = File(destinationFolder, oldFileDirItem.name)
if (!newFile.exists() && File(oldFileDirItem.path).renameTo(newFile)) {
if (!baseConfig.keepLastModified) {
newFile.setLastModified(System.currentTimeMillis())
}
updateInMediaStore(oldFile, newFile)
updatedFiles.add(newFile)
updateInMediaStore(oldFileDirItem.path, newFile.absolutePath)
updatedFiles.add(newFile.toFileDirItem())
}
}
scanFiles(updatedFiles) {
val updatedPaths = updatedFiles.map { it.path } as ArrayList<String>
scanPaths(updatedPaths) {
runOnUiThread {
copyMoveListener.copySucceeded(false, files.size * 2 == updatedFiles.size)
copyMoveListener.copySucceeded(false, fileDirItems.size * 2 == updatedFiles.size)
}
}
} catch (e: Exception) {
@ -228,7 +228,7 @@ open class BaseSimpleActivity : AppCompatActivity() {
}
}
private fun startCopyMove(files: ArrayList<File>, destinationFolder: File, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean, copyHidden: Boolean) {
private fun startCopyMove(files: ArrayList<FileDirItem>, destinationFolder: File, isCopyOperation: Boolean, copyPhotoVideoOnly: Boolean, copyHidden: Boolean) {
checkConflict(files, destinationFolder, 0, LinkedHashMap()) {
toast(if (isCopyOperation) R.string.copying else R.string.moving)
val pair = Pair(files, destinationFolder)
@ -236,7 +236,7 @@ open class BaseSimpleActivity : AppCompatActivity() {
}
}
private fun checkConflict(files: ArrayList<File>, destinationFolder: File, index: Int, conflictResolutions: LinkedHashMap<String, Int>,
private fun checkConflict(files: ArrayList<FileDirItem>, destinationFolder: File, index: Int, conflictResolutions: LinkedHashMap<String, Int>,
callback: (resolutions: LinkedHashMap<String, Int>) -> Unit) {
if (index == files.size) {
callback(conflictResolutions)

View file

@ -16,6 +16,7 @@ import com.simplemobiletools.commons.extensions.*
import com.simplemobiletools.commons.helpers.CONFLICT_OVERWRITE
import com.simplemobiletools.commons.helpers.CONFLICT_SKIP
import com.simplemobiletools.commons.interfaces.CopyMoveListener
import com.simplemobiletools.commons.models.FileDirItem
import java.io.File
import java.io.FileInputStream
import java.io.InputStream
@ -24,14 +25,14 @@ import java.lang.ref.WeakReference
import java.util.*
class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = false, val copyMediaOnly: Boolean, val conflictResolutions: LinkedHashMap<String, Int>,
listener: CopyMoveListener, val copyHidden: Boolean) : AsyncTask<Pair<ArrayList<File>, File>, Void, Boolean>() {
listener: CopyMoveListener, val copyHidden: Boolean) : AsyncTask<Pair<ArrayList<FileDirItem>, File>, Void, Boolean>() {
private val INITIAL_PROGRESS_DELAY = 3000L
private val PROGRESS_RECHECK_INTERVAL = 500L
private var mListener: WeakReference<CopyMoveListener>? = null
private var mMovedFiles: ArrayList<File> = ArrayList()
private var mMovedFiles: ArrayList<FileDirItem> = ArrayList()
private var mDocuments = LinkedHashMap<String, DocumentFile?>()
private lateinit var mFiles: ArrayList<File>
private lateinit var mFiles: ArrayList<FileDirItem>
private var mFileCountToCopy = 0
// progress indication
@ -49,7 +50,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal
mNotificationBuilder = NotificationCompat.Builder(activity)
}
override fun doInBackground(vararg params: Pair<ArrayList<File>, File>): Boolean? {
override fun doInBackground(vararg params: Pair<ArrayList<FileDirItem>, File>): Boolean? {
if (params.isEmpty()) {
return false
}
@ -62,7 +63,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal
for (file in mFiles) {
val newFile = File(pair.second, file.name)
if (!newFile.exists() || getConflictResolution(newFile) != CONFLICT_SKIP) {
mMaxSize += (file.getProperSize(copyHidden) / 1000).toInt()
mMaxSize += (File(file.path).getProperSize(copyHidden) / 1000).toInt()
}
}
@ -80,7 +81,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal
mFileCountToCopy--
continue
} else if (resolution == CONFLICT_OVERWRITE) {
activity.deleteFilesBg(arrayListOf(newFile), true)
activity.deleteFileBg(newFile.toFileDirItem(), true)
}
}
@ -95,7 +96,8 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal
activity.deleteFiles(mMovedFiles) {}
}
activity.scanFiles(mFiles) {}
val paths = mFiles.map { it.path } as ArrayList<String>
activity.scanPaths(paths) {}
return true
}
@ -151,7 +153,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal
}
}
private fun copy(source: File, destination: File) {
private fun copy(source: FileDirItem, destination: File) {
if (source.isDirectory) {
copyDirectory(source, destination)
} else {
@ -159,29 +161,29 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal
}
}
private fun copyDirectory(source: File, destination: File) {
private fun copyDirectory(source: FileDirItem, destination: File) {
if (!activity.createDirectorySync(destination)) {
val error = String.format(activity.getString(R.string.could_not_create_folder), destination.absolutePath)
activity.showErrorToast(error)
return
}
val children = source.list()
val children = File(source.path).list()
for (child in children) {
val newFile = File(destination, child)
if (newFile.exists()) {
continue
}
val oldFile = File(source, child)
copy(oldFile, newFile)
val oldFile = File(source.path, child)
copy(oldFile.toFileDirItem(), newFile)
}
mMovedFiles.add(source)
}
private fun copyFile(source: File, destination: File) {
if (copyMediaOnly && !source.absolutePath.isImageVideoGif()) {
mCurrentProgress += source.length()
private fun copyFile(source: FileDirItem, destination: File) {
if (copyMediaOnly && !source.path.isImageVideoGif()) {
mCurrentProgress += source.size
return
}
@ -189,7 +191,7 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal
if (!activity.createDirectorySync(directory)) {
val error = String.format(activity.getString(R.string.could_not_create_folder), directory.absolutePath)
activity.showErrorToast(error)
mCurrentProgress += source.length()
mCurrentProgress += source.size
return
}
@ -201,9 +203,9 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal
mDocuments[destination.parent] = activity.getFileDocument(destination.parent)
}
out = activity.getFileOutputStreamSync(destination.absolutePath, source.getMimeType(), mDocuments[destination.parent])
out = activity.getFileOutputStreamSync(destination.absolutePath, source.path.getMimeType(), mDocuments[destination.parent])
inputStream = FileInputStream(source)
inputStream = FileInputStream(File(source.path))
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
var bytes = inputStream.read(buffer)
@ -213,10 +215,10 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal
bytes = inputStream.read(buffer)
}
if (source.length() == destination.length()) {
if (source.size == destination.length()) {
mMovedFiles.add(source)
if (activity.baseConfig.keepLastModified) {
copyOldLastModified(source, destination)
copyOldLastModified(source.path, destination)
} else {
activity.scanFile(destination) {}
}
@ -229,13 +231,13 @@ class CopyMoveTask(val activity: BaseSimpleActivity, val copyOnly: Boolean = fal
}
}
private fun copyOldLastModified(source: File, destination: File) {
private fun copyOldLastModified(sourcePath: String, destination: File) {
val projection = arrayOf(
MediaStore.Images.Media.DATE_TAKEN,
MediaStore.Images.Media.DATE_MODIFIED)
val uri = MediaStore.Files.getContentUri("external")
val selection = "${MediaStore.MediaColumns.DATA} = ?"
var selectionArgs = arrayOf(source.absolutePath)
var selectionArgs = arrayOf(sourcePath)
val cursor = activity.applicationContext.contentResolver.query(uri, projection, selection, selectionArgs, null)
cursor?.use {

View file

@ -31,6 +31,7 @@ import com.simplemobiletools.commons.dialogs.SecurityDialog
import com.simplemobiletools.commons.dialogs.WhatsNewDialog
import com.simplemobiletools.commons.dialogs.WritePermissionDialog
import com.simplemobiletools.commons.helpers.*
import com.simplemobiletools.commons.models.FileDirItem
import com.simplemobiletools.commons.models.Release
import com.simplemobiletools.commons.models.SharedTheme
import com.simplemobiletools.commons.views.MyTextView
@ -297,7 +298,7 @@ fun BaseSimpleActivity.checkWhatsNew(releases: List<Release>, currVersion: Int)
baseConfig.lastVersion = currVersion
}
fun BaseSimpleActivity.deleteFolders(folders: ArrayList<File>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
fun BaseSimpleActivity.deleteFolders(folders: ArrayList<FileDirItem>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Thread {
deleteFoldersBg(folders, deleteMediaOnly, callback)
@ -307,12 +308,12 @@ fun BaseSimpleActivity.deleteFolders(folders: ArrayList<File>, deleteMediaOnly:
}
}
fun BaseSimpleActivity.deleteFoldersBg(folders: ArrayList<File>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
fun BaseSimpleActivity.deleteFoldersBg(folders: ArrayList<FileDirItem>, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
var wasSuccess = false
var needPermissionForPath = ""
for (file in folders) {
if (needsStupidWritePermissions(file.absolutePath) && baseConfig.treeUri.isEmpty()) {
needPermissionForPath = file.absolutePath
for (folder in folders) {
if (needsStupidWritePermissions(folder.path) && baseConfig.treeUri.isEmpty()) {
needPermissionForPath = folder.path
break
}
}
@ -331,7 +332,7 @@ fun BaseSimpleActivity.deleteFoldersBg(folders: ArrayList<File>, deleteMediaOnly
}
}
fun BaseSimpleActivity.deleteFolder(folder: File, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
fun BaseSimpleActivity.deleteFolder(folder: FileDirItem, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Thread {
deleteFolderBg(folder, deleteMediaOnly, callback)
@ -341,7 +342,8 @@ fun BaseSimpleActivity.deleteFolder(folder: File, deleteMediaOnly: Boolean = tru
}
}
fun BaseSimpleActivity.deleteFolderBg(folder: File, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
fun BaseSimpleActivity.deleteFolderBg(fileDirItem: FileDirItem, deleteMediaOnly: Boolean = true, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
val folder = File(fileDirItem.path)
if (folder.exists()) {
val filesArr = folder.listFiles()
if (filesArr == null) {
@ -352,17 +354,17 @@ fun BaseSimpleActivity.deleteFolderBg(folder: File, deleteMediaOnly: Boolean = t
val filesList = (filesArr as Array).toList()
val files = filesList.filter { !deleteMediaOnly || it.isImageVideoGif() }
for (file in files) {
deleteFileBg(file, false) { }
deleteFileBg(file.toFileDirItem(), false) { }
}
if (folder.listFiles()?.isEmpty() == true) {
deleteFileBg(folder, true) { }
deleteFileBg(fileDirItem, true) { }
}
}
callback?.invoke(true)
}
fun BaseSimpleActivity.deleteFiles(files: ArrayList<File>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
fun BaseSimpleActivity.deleteFiles(files: ArrayList<FileDirItem>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Thread {
deleteFilesBg(files, allowDeleteFolder, callback)
@ -372,18 +374,19 @@ fun BaseSimpleActivity.deleteFiles(files: ArrayList<File>, allowDeleteFolder: Bo
}
}
fun BaseSimpleActivity.deleteFilesBg(files: ArrayList<File>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
fun BaseSimpleActivity.deleteFilesBg(files: ArrayList<FileDirItem>, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
if (files.isEmpty()) {
callback?.invoke(true)
return
}
var wasSuccess = false
handleSAFDialog(files[0]) {
handleSAFDialog(File(files[0].path)) {
files.forEachIndexed { index, file ->
deleteFileBg(file, allowDeleteFolder) {
if (it)
if (it) {
wasSuccess = true
}
if (index == files.size - 1) {
callback?.invoke(wasSuccess)
@ -393,21 +396,22 @@ fun BaseSimpleActivity.deleteFilesBg(files: ArrayList<File>, allowDeleteFolder:
}
}
fun BaseSimpleActivity.deleteFile(file: File, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
fun BaseSimpleActivity.deleteFile(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
if (Looper.myLooper() == Looper.getMainLooper()) {
Thread {
deleteFileBg(file, allowDeleteFolder, callback)
deleteFileBg(fileDirItem, allowDeleteFolder, callback)
}.start()
} else {
deleteFileBg(file, allowDeleteFolder, callback)
deleteFileBg(fileDirItem, allowDeleteFolder, callback)
}
}
@SuppressLint("NewApi")
fun BaseSimpleActivity.deleteFileBg(file: File, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
var fileDeleted = !file.exists() || file.delete()
fun BaseSimpleActivity.deleteFileBg(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
val file = File(fileDirItem.path)
var fileDeleted = !isPathOnOTG(fileDirItem.path) && (!file.exists() || file.delete())
if (fileDeleted) {
rescanDeletedFile(file) {
rescanDeletedFile(fileDirItem) {
callback?.invoke(true)
}
} else {
@ -415,31 +419,40 @@ fun BaseSimpleActivity.deleteFileBg(file: File, allowDeleteFolder: Boolean = fal
fileDeleted = deleteRecursively(file)
}
if (!fileDeleted && isPathOnSD(file.absolutePath)) {
handleSAFDialog(file) {
fileDeleted = tryFastDocumentDelete(file, allowDeleteFolder)
if (!fileDeleted) {
val document = getFileDocument(file.absolutePath)
if (document != null && (file.isDirectory == document.isDirectory)) {
fileDeleted = (document.isFile == true || allowDeleteFolder) && DocumentsContract.deleteDocument(applicationContext.contentResolver, document.uri)
}
}
if (fileDeleted) {
rescanDeletedFile(file) {
callback?.invoke(true)
}
if (isPathOnSD(fileDirItem.path)) {
handleSAFDialog(file) {
trySAFFileDelete(fileDirItem, allowDeleteFolder, callback)
}
} else if (isPathOnOTG(fileDirItem.path)) {
trySAFFileDelete(fileDirItem, allowDeleteFolder, callback)
}
}
}
}
fun BaseSimpleActivity.rescanDeletedFile(file: File, callback: (() -> Unit)? = null) {
if (deleteFromMediaStore(file)) {
@SuppressLint("NewApi")
fun BaseSimpleActivity.trySAFFileDelete(fileDirItem: FileDirItem, allowDeleteFolder: Boolean = false, callback: ((wasSuccess: Boolean) -> Unit)? = null) {
var fileDeleted = tryFastDocumentDelete(fileDirItem, allowDeleteFolder)
if (!fileDeleted) {
val document = getFileDocument(fileDirItem.path)
if (document != null && (fileDirItem.isDirectory == document.isDirectory)) {
fileDeleted = (document.isFile == true || allowDeleteFolder) && DocumentsContract.deleteDocument(applicationContext.contentResolver, document.uri)
}
if (fileDeleted) {
rescanDeletedFile(fileDirItem) {
callback?.invoke(true)
}
}
}
}
fun BaseSimpleActivity.rescanDeletedFile(fileDirItem: FileDirItem, callback: (() -> Unit)? = null) {
if (deleteFromMediaStore(fileDirItem.path)) {
callback?.invoke()
} else {
MediaScannerConnection.scanFile(applicationContext, arrayOf(file.absolutePath), null, { s, uri ->
MediaScannerConnection.scanFile(applicationContext, arrayOf(fileDirItem.path), null, { s, uri ->
try {
applicationContext.contentResolver.delete(uri, null, null)
} catch (e: Exception) {
@ -493,7 +506,7 @@ fun BaseSimpleActivity.renameFile(oldFile: File, newFile: File, callback: ((succ
try {
val uri = DocumentsContract.renameDocument(applicationContext.contentResolver, document.uri, newFile.name)
if (document.uri != uri) {
updateInMediaStore(oldFile, newFile)
updateInMediaStore(oldFile.absolutePath, newFile.absolutePath)
scanFiles(arrayListOf(oldFile, newFile)) {
if (!baseConfig.keepLastModified) {
updateLastModified(newFile, System.currentTimeMillis())
@ -510,7 +523,7 @@ fun BaseSimpleActivity.renameFile(oldFile: File, newFile: File, callback: ((succ
}
} else if (oldFile.renameTo(newFile)) {
if (newFile.isDirectory) {
deleteFromMediaStore(oldFile)
deleteFromMediaStore(oldFile.path)
scanFile(newFile) {
callback?.invoke(true)
}
@ -518,7 +531,7 @@ fun BaseSimpleActivity.renameFile(oldFile: File, newFile: File, callback: ((succ
if (!baseConfig.keepLastModified) {
newFile.setLastModified(System.currentTimeMillis())
}
updateInMediaStore(oldFile, newFile)
updateInMediaStore(oldFile.absolutePath, newFile.absolutePath)
scanFile(newFile) {
callback?.invoke(true)
}

View file

@ -109,6 +109,8 @@ fun Context.isPathOnSD(path: String) = sdCardPath.isNotEmpty() && path.startsWit
fun Context.isPathOnOTG(path: String) = path.startsWith(OTG_PATH)
fun Context.isFileDirItemOnOTG(fileDirItem: FileDirItem) = fileDirItem.path.startsWith(OTG_PATH)
fun Context.needsStupidWritePermissions(path: String) = isPathOnSD(path) && isLollipopPlus()
@SuppressLint("NewApi")
@ -134,8 +136,8 @@ fun Context.getMyFileUri(file: File): Uri {
}
@SuppressLint("NewApi")
fun Context.tryFastDocumentDelete(file: File, allowDeleteFolder: Boolean): Boolean {
val document = getFastDocument(file)
fun Context.tryFastDocumentDelete(fileDirItem: FileDirItem, allowDeleteFolder: Boolean): Boolean {
val document = getFastDocument(fileDirItem)
return if (document?.isFile == true || allowDeleteFolder) {
DocumentsContract.deleteDocument(contentResolver, document?.uri)
} else {
@ -144,13 +146,23 @@ fun Context.tryFastDocumentDelete(file: File, allowDeleteFolder: Boolean): Boole
}
@SuppressLint("NewApi")
fun Context.getFastDocument(file: File): DocumentFile? {
if (!isLollipopPlus() || baseConfig.sdCardPath.isEmpty())
fun Context.getFastDocument(fileDirItem: FileDirItem): DocumentFile? {
if (!isLollipopPlus()) {
return null
}
val relativePath = Uri.encode(file.absolutePath.substring(baseConfig.sdCardPath.length).trim('/'))
val sdCardPathPart = baseConfig.sdCardPath.split("/").filter(String::isNotEmpty).last().trim('/')
val fullUri = "${baseConfig.treeUri}/document/$sdCardPathPart%3A$relativePath"
val isOTG = isFileDirItemOnOTG(fileDirItem)
if (!isOTG && baseConfig.sdCardPath.isEmpty()) {
return null
}
val startString = if (isOTG) OTG_PATH else baseConfig.sdCardPath
val basePath = if (isOTG) baseConfig.OTGBasePath else baseConfig.sdCardPath
val treeUri = if (isOTG) baseConfig.OTGTreeUri else baseConfig.treeUri
val relativePath = Uri.encode(fileDirItem.path.substring(startString.length).trim('/'))
val externalPathPart = basePath.split("/").last(String::isNotEmpty).trim('/')
val fullUri = "$treeUri/document/$externalPathPart%3A$relativePath"
return DocumentFile.fromSingleUri(this, Uri.parse(fullUri))
}
@ -198,33 +210,33 @@ fun getPaths(file: File): ArrayList<String> {
return paths
}
fun Context.getFileUri(file: File) = when {
file.isImageSlow() -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
file.isVideoSlow() -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
fun Context.getFileUri(path: String) = when {
path.isImageSlow() -> MediaStore.Images.Media.EXTERNAL_CONTENT_URI
path.isVideoSlow() -> MediaStore.Video.Media.EXTERNAL_CONTENT_URI
else -> MediaStore.Files.getContentUri("external")
}
// these functions update the mediastore instantly, MediaScannerConnection.scanFile takes some time to really get applied
fun Context.deleteFromMediaStore(file: File): Boolean {
fun Context.deleteFromMediaStore(path: String): Boolean {
return try {
val where = "${MediaStore.MediaColumns.DATA} = ?"
val args = arrayOf(file.absolutePath)
contentResolver.delete(getFileUri(file), where, args) == 1
val args = arrayOf(path)
contentResolver.delete(getFileUri(path), where, args) == 1
} catch (e: Exception) {
false
}
}
fun Context.updateInMediaStore(oldFile: File, newFile: File) {
fun Context.updateInMediaStore(oldPath: String, newPath: String) {
Thread {
val values = ContentValues().apply {
put(MediaStore.MediaColumns.DATA, newFile.absolutePath)
put(MediaStore.MediaColumns.DISPLAY_NAME, newFile.name)
put(MediaStore.MediaColumns.TITLE, newFile.name)
put(MediaStore.MediaColumns.DATA, newPath)
put(MediaStore.MediaColumns.DISPLAY_NAME, newPath.getFilenameFromPath())
put(MediaStore.MediaColumns.TITLE, newPath.getFilenameFromPath())
}
val uri = getFileUri(oldFile)
val uri = getFileUri(oldPath)
val selection = "${MediaStore.MediaColumns.DATA} = ?"
val selectionArgs = arrayOf(oldFile.absolutePath)
val selectionArgs = arrayOf(oldPath)
try {
contentResolver.update(uri, values, selection, selectionArgs)
@ -238,7 +250,7 @@ fun Context.updateLastModified(file: File, lastModified: Long) {
put(MediaStore.MediaColumns.DATE_MODIFIED, lastModified)
}
file.setLastModified(lastModified)
val uri = getFileUri(file)
val uri = getFileUri(file.absolutePath)
val selection = "${MediaStore.MediaColumns.DATA} = ?"
val selectionArgs = arrayOf(file.absolutePath)
@ -271,12 +283,15 @@ fun Context.getOTGItems(path: String, callback: (ArrayList<FileDirItem>) -> Unit
if (first != null) {
val fullPath = first.uri.toString()
val nameStartIndex = fullPath.lastIndexOf(first.name)
val basePath = fullPath.substring(0, nameStartIndex)
var basePath = fullPath.substring(0, nameStartIndex)
if (basePath.endsWith("%3A")) {
basePath = basePath.substring(0, basePath.length - 3)
}
baseConfig.OTGBasePath = basePath
}
}
val basePath = baseConfig.OTGBasePath
val basePath = "${baseConfig.OTGBasePath}%3A"
for (file in files) {
if (file.exists()) {
val filePath = file.uri.toString().substring(basePath.length)

View file

@ -4,6 +4,7 @@ import android.graphics.Bitmap
import android.graphics.BitmapFactory
import android.graphics.Point
import android.media.MediaMetadataRetriever
import com.simplemobiletools.commons.models.FileDirItem
import java.io.File
fun File.isImageVideoGif() = absolutePath.isImageFast() || absolutePath.isVideoFast() || absolutePath.isGif()
@ -147,3 +148,5 @@ private fun getDirectoryFileCount(dir: File, countHiddenItems: Boolean): Int {
}
return count
}
fun File.toFileDirItem() = FileDirItem(absolutePath, name, isDirectory, 0, 0L)

View file

@ -10,6 +10,8 @@ fun String.getFilenameFromPath() = substring(lastIndexOf("/") + 1)
fun String.getFilenameExtension() = substring(lastIndexOf(".") + 1)
fun String.getMimeType() = getMimeTypeFromPath()
fun String.getBasePath(context: Context): String {
return if (startsWith(context.internalStoragePath)) {
context.internalStoragePath
@ -47,10 +49,13 @@ fun String.isDng() = endsWith(".dng", true)
// fast extension checks, not guaranteed to be accurate
fun String.isVideoFast() = videoExtensions.any { endsWith(it, true) }
fun String.isImageFast() = photoExtensions.any { endsWith(it, true) }
fun String.isAudioFast() = audioExtensions.any { endsWith(it, true) }
fun String.isImageSlow() = isImageFast() || getMimeType().startsWith("image")
fun String.isVideoSlow() = isVideoFast() || getMimeType().startsWith("video")
fun String.isAudioSlow() = isAudioFast() || getMimeType().startsWith("audio")
fun String.areDigitsOnly() = matches(Regex("[0-9]+"))
fun String.getExifProperties(exif: ExifInterface): String {

View file

@ -5,7 +5,7 @@ import com.simplemobiletools.commons.extensions.formatSize
import com.simplemobiletools.commons.helpers.*
import java.io.File
data class FileDirItem(val path: String, val name: String, val isDirectory: Boolean, var children: Int, var size: Long) :
data class FileDirItem(val path: String, val name: String = "", val isDirectory: Boolean = false, var children: Int = 0, var size: Long = 0L) :
Comparable<FileDirItem> {
companion object {
var sorting: Int = 0