Renaming on SDK 30
- handle renaming files with SAF except files in the Download directory - handle renaming files in the Download directory using MediaStore.createWrite request, then duplicating the file with the new name - handle renaming files in the root of internal storage using MediaStore.createWrite request, then using contentResolver to update the display name - this methods do not work for files in the root of SD card and all files in OTG media
This commit is contained in:
parent
557d5bee02
commit
dc0ca635c5
8 changed files with 339 additions and 99 deletions
|
@ -207,6 +207,15 @@ class FilePickerDialog(
|
||||||
if ((pickFile && fileDocument.isFile) || (!pickFile && fileDocument.isDirectory)) {
|
if ((pickFile && fileDocument.isFile) || (!pickFile && fileDocument.isDirectory)) {
|
||||||
sendSuccess()
|
sendSuccess()
|
||||||
}
|
}
|
||||||
|
} else if (activity.isAccessibleWithSAFSdk30(currPath)) {
|
||||||
|
activity.handleSAFDialogSdk30(currPath) {
|
||||||
|
if (it) {
|
||||||
|
val document = activity.getSomeDocumentSdk30(currPath) ?: return@handleSAFDialogSdk30
|
||||||
|
if ((pickFile && document.isFile) || (!pickFile && document.isDirectory)) {
|
||||||
|
sendSuccess()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
val file = File(currPath)
|
val file = File(currPath)
|
||||||
if ((pickFile && file.isFile) || (!pickFile && file.isDirectory)) {
|
if ((pickFile && file.isFile) || (!pickFile && file.isDirectory)) {
|
||||||
|
|
|
@ -0,0 +1,74 @@
|
||||||
|
package com.simplemobiletools.commons.extensions
|
||||||
|
|
||||||
|
import android.content.ContentValues
|
||||||
|
import android.provider.MediaStore
|
||||||
|
import com.simplemobiletools.commons.R
|
||||||
|
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||||
|
import com.simplemobiletools.commons.models.FileDirItem
|
||||||
|
import java.io.File
|
||||||
|
import java.io.InputStream
|
||||||
|
import java.io.OutputStream
|
||||||
|
|
||||||
|
fun BaseSimpleActivity.copySingleFileSdk30(source: FileDirItem, destination: FileDirItem): Boolean {
|
||||||
|
val directory = destination.getParentPath()
|
||||||
|
if (!createDirectorySync(directory)) {
|
||||||
|
val error = String.format(getString(R.string.could_not_create_folder), directory)
|
||||||
|
showErrorToast(error)
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
var inputStream: InputStream? = null
|
||||||
|
var out: OutputStream? = null
|
||||||
|
try {
|
||||||
|
|
||||||
|
out = getFileOutputStreamSync(destination.path, source.path.getMimeType())
|
||||||
|
inputStream = getFileInputStreamSync(source.path)!!
|
||||||
|
|
||||||
|
var copiedSize = 0L
|
||||||
|
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
||||||
|
var bytes = inputStream.read(buffer)
|
||||||
|
while (bytes >= 0) {
|
||||||
|
out!!.write(buffer, 0, bytes)
|
||||||
|
copiedSize += bytes
|
||||||
|
bytes = inputStream.read(buffer)
|
||||||
|
}
|
||||||
|
|
||||||
|
out?.flush()
|
||||||
|
|
||||||
|
return if (source.size == copiedSize && getDoesFilePathExist(destination.path)) {
|
||||||
|
if (baseConfig.keepLastModified) {
|
||||||
|
copyOldLastModified(source.path, destination.path)
|
||||||
|
File(destination.path).setLastModified(File(source.path).lastModified())
|
||||||
|
}
|
||||||
|
true
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
inputStream?.close()
|
||||||
|
out?.close()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun BaseSimpleActivity.copyOldLastModified(sourcePath: String, destinationPath: String) {
|
||||||
|
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(sourcePath)
|
||||||
|
val cursor = applicationContext.contentResolver.query(uri, projection, selection, selectionArgs, null)
|
||||||
|
|
||||||
|
cursor?.use {
|
||||||
|
if (cursor.moveToFirst()) {
|
||||||
|
val dateTaken = cursor.getLongValue(MediaStore.Images.Media.DATE_TAKEN)
|
||||||
|
val dateModified = cursor.getIntValue(MediaStore.Images.Media.DATE_MODIFIED)
|
||||||
|
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(MediaStore.Images.Media.DATE_TAKEN, dateTaken)
|
||||||
|
put(MediaStore.Images.Media.DATE_MODIFIED, dateModified)
|
||||||
|
}
|
||||||
|
|
||||||
|
selectionArgs = arrayOf(destinationPath)
|
||||||
|
applicationContext.contentResolver.update(uri, values, selection, selectionArgs)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
|
@ -834,26 +834,49 @@ fun BaseSimpleActivity.renameFile(
|
||||||
oldPath: String,
|
oldPath: String,
|
||||||
newPath: String,
|
newPath: String,
|
||||||
isRenamingMultipleFiles: Boolean,
|
isRenamingMultipleFiles: Boolean,
|
||||||
callback: ((success: Boolean, useAndroid30Way: Boolean) -> Unit)? = null
|
callback: ((success: Boolean, android30RenameFormat: Android30RenameFormat) -> Unit)? = null
|
||||||
) {
|
) {
|
||||||
if (isRestrictedSAFOnlyRoot(oldPath)) {
|
if (isRestrictedSAFOnlyRoot(oldPath)) {
|
||||||
handleAndroidSAFDialog(oldPath) {
|
handleAndroidSAFDialog(oldPath) {
|
||||||
if (!it) {
|
if (!it) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
callback?.invoke(false, false)
|
callback?.invoke(false, Android30RenameFormat.NONE)
|
||||||
}
|
}
|
||||||
return@handleAndroidSAFDialog
|
return@handleAndroidSAFDialog
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
val success = renameAndroidSAFDocument(oldPath, newPath)
|
ensureBackgroundThread {
|
||||||
runOnUiThread {
|
val success = renameAndroidSAFDocument(oldPath, newPath)
|
||||||
callback?.invoke(success, false)
|
runOnUiThread {
|
||||||
|
callback?.invoke(success, Android30RenameFormat.NONE)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showErrorToast(e)
|
showErrorToast(e)
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
callback?.invoke(false, false)
|
callback?.invoke(false, Android30RenameFormat.NONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else if (isAccessibleWithSAFSdk30(oldPath)) {
|
||||||
|
|
||||||
|
handleSAFDialogSdk30(oldPath) {
|
||||||
|
if (!it) {
|
||||||
|
return@handleSAFDialogSdk30
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
ensureBackgroundThread {
|
||||||
|
val success = renameDocumentSdk30(oldPath, newPath)
|
||||||
|
runOnUiThread {
|
||||||
|
callback?.invoke(success, Android30RenameFormat.NONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
showErrorToast(e)
|
||||||
|
runOnUiThread {
|
||||||
|
callback?.invoke(false, Android30RenameFormat.NONE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -866,7 +889,7 @@ fun BaseSimpleActivity.renameFile(
|
||||||
val document = getSomeDocumentFile(oldPath)
|
val document = getSomeDocumentFile(oldPath)
|
||||||
if (document == null || (File(oldPath).isDirectory != document.isDirectory)) {
|
if (document == null || (File(oldPath).isDirectory != document.isDirectory)) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
callback?.invoke(false, false)
|
callback?.invoke(false, Android30RenameFormat.NONE)
|
||||||
}
|
}
|
||||||
return@handleSAFDialog
|
return@handleSAFDialog
|
||||||
}
|
}
|
||||||
|
@ -879,7 +902,7 @@ fun BaseSimpleActivity.renameFile(
|
||||||
// FileNotFoundException is thrown in some weird cases, but renaming works just fine
|
// FileNotFoundException is thrown in some weird cases, but renaming works just fine
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showErrorToast(e)
|
showErrorToast(e)
|
||||||
callback?.invoke(false, false)
|
callback?.invoke(false, Android30RenameFormat.NONE)
|
||||||
return@ensureBackgroundThread
|
return@ensureBackgroundThread
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -890,14 +913,14 @@ fun BaseSimpleActivity.renameFile(
|
||||||
}
|
}
|
||||||
deleteFromMediaStore(oldPath)
|
deleteFromMediaStore(oldPath)
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
callback?.invoke(true, false)
|
callback?.invoke(true, Android30RenameFormat.NONE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showErrorToast(e)
|
showErrorToast(e)
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
callback?.invoke(false, false)
|
callback?.invoke(false, Android30RenameFormat.NONE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -910,7 +933,7 @@ fun BaseSimpleActivity.renameFile(
|
||||||
if (isRPlus() && exception is java.nio.file.FileSystemException) {
|
if (isRPlus() && exception is java.nio.file.FileSystemException) {
|
||||||
// if we are renaming multiple files at once, we should give the Android 30+ permission dialog all uris together, not one by one
|
// if we are renaming multiple files at once, we should give the Android 30+ permission dialog all uris together, not one by one
|
||||||
if (isRenamingMultipleFiles) {
|
if (isRenamingMultipleFiles) {
|
||||||
callback?.invoke(false, true)
|
callback?.invoke(false, Android30RenameFormat.CONTENT_RESOLVER)
|
||||||
} else {
|
} else {
|
||||||
val fileUris = getFileUrisFromFileDirItems(arrayListOf(File(oldPath).toFileDirItem(this))).second
|
val fileUris = getFileUrisFromFileDirItems(arrayListOf(File(oldPath).toFileDirItem(this))).second
|
||||||
updateSDK30Uris(fileUris) { success ->
|
updateSDK30Uris(fileUris) { success ->
|
||||||
|
@ -921,19 +944,19 @@ fun BaseSimpleActivity.renameFile(
|
||||||
|
|
||||||
try {
|
try {
|
||||||
contentResolver.update(fileUris.first(), values, null, null)
|
contentResolver.update(fileUris.first(), values, null, null)
|
||||||
callback?.invoke(true, false)
|
callback?.invoke(true, Android30RenameFormat.NONE)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
showErrorToast(e)
|
showErrorToast(e)
|
||||||
callback?.invoke(false, false)
|
callback?.invoke(false, Android30RenameFormat.NONE)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
callback?.invoke(false, false)
|
callback?.invoke(false, Android30RenameFormat.NONE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
showErrorToast(exception)
|
showErrorToast(exception)
|
||||||
callback?.invoke(false, false)
|
callback?.invoke(false, Android30RenameFormat.NONE)
|
||||||
}
|
}
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -945,7 +968,7 @@ fun BaseSimpleActivity.renameFile(
|
||||||
updateInMediaStore(oldPath, newPath)
|
updateInMediaStore(oldPath, newPath)
|
||||||
rescanPath(newPath) {
|
rescanPath(newPath) {
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
callback?.invoke(true, false)
|
callback?.invoke(true, Android30RenameFormat.NONE)
|
||||||
}
|
}
|
||||||
deleteFromMediaStore(oldPath)
|
deleteFromMediaStore(oldPath)
|
||||||
scanPathRecursively(newPath)
|
scanPathRecursively(newPath)
|
||||||
|
@ -958,14 +981,48 @@ fun BaseSimpleActivity.renameFile(
|
||||||
scanPathsRecursively(arrayListOf(newPath)) {
|
scanPathsRecursively(arrayListOf(newPath)) {
|
||||||
deleteFromMediaStore(oldPath)
|
deleteFromMediaStore(oldPath)
|
||||||
runOnUiThread {
|
runOnUiThread {
|
||||||
callback?.invoke(true, false)
|
callback?.invoke(true, Android30RenameFormat.NONE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
tempFile.delete()
|
tempFile.delete()
|
||||||
runOnUiThread {
|
newFile.delete()
|
||||||
callback?.invoke(false, false)
|
if (isRPlus()) {
|
||||||
|
// if we are renaming multiple files at once, we should give the Android 30+ permission dialog all uris together, not one by one
|
||||||
|
if (isRenamingMultipleFiles) {
|
||||||
|
callback?.invoke(false, Android30RenameFormat.SAF)
|
||||||
|
} else {
|
||||||
|
val fileUris = getFileUrisFromFileDirItems(arrayListOf(File(oldPath).toFileDirItem(this))).second
|
||||||
|
updateSDK30Uris(fileUris) { success ->
|
||||||
|
if (!success) {
|
||||||
|
return@updateSDK30Uris
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
val sourceFile = File(oldPath).toFileDirItem(this)
|
||||||
|
val destinationFile = sourceFile.copy(path = newPath, name = newPath.getFilenameFromPath())
|
||||||
|
val copySuccessful = copySingleFileSdk30(sourceFile, destinationFile)
|
||||||
|
if (copySuccessful) {
|
||||||
|
if (!baseConfig.keepLastModified) {
|
||||||
|
newFile.setLastModified(System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
contentResolver.delete(fileUris.first(), null)
|
||||||
|
updateInMediaStore(oldPath, newPath)
|
||||||
|
scanPathsRecursively(arrayListOf(newPath)) {
|
||||||
|
runOnUiThread {
|
||||||
|
callback?.invoke(true, Android30RenameFormat.NONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (e: Exception) {
|
||||||
|
showErrorToast(e)
|
||||||
|
callback?.invoke(false, Android30RenameFormat.NONE)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
callback?.invoke(false, Android30RenameFormat.NONE)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -153,6 +153,18 @@ fun Context.deleteDocumentWithSAFSdk30(fileDirItem: FileDirItem, allowDeleteFold
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun Context.renameDocumentSdk30(oldPath: String, newPath: String): Boolean {
|
||||||
|
return try {
|
||||||
|
val treeUri = createFirstParentTreeUri(oldPath)
|
||||||
|
val documentId = getSAFDocumentId(oldPath)
|
||||||
|
val parentUri = DocumentsContract.buildDocumentUriUsingTree(treeUri, documentId)
|
||||||
|
DocumentsContract.renameDocument(contentResolver, parentUri, newPath.getFilenameFromPath()) != null
|
||||||
|
} catch (e: IllegalStateException) {
|
||||||
|
showErrorToast(e)
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fun Context.hasProperStoredDocumentUriSdk30(path: String): Boolean {
|
fun Context.hasProperStoredDocumentUriSdk30(path: String): Boolean {
|
||||||
val documentUri = buildDocumentUriSdk30(path)
|
val documentUri = buildDocumentUriSdk30(path)
|
||||||
return contentResolver.persistedUriPermissions.any { it.uri.toString() == documentUri.toString() }
|
return contentResolver.persistedUriPermissions.any { it.uri.toString() == documentUri.toString() }
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
package com.simplemobiletools.commons.models
|
||||||
|
|
||||||
|
enum class Android30RenameFormat {
|
||||||
|
SAF,
|
||||||
|
CONTENT_RESOLVER,
|
||||||
|
NONE
|
||||||
|
}
|
|
@ -7,7 +7,7 @@ import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.*
|
import com.simplemobiletools.commons.helpers.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
open class FileDirItem(
|
data class FileDirItem(
|
||||||
val path: String,
|
val path: String,
|
||||||
val name: String = "",
|
val name: String = "",
|
||||||
var isDirectory: Boolean = false,
|
var isDirectory: Boolean = false,
|
||||||
|
|
|
@ -12,6 +12,7 @@ import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.helpers.isNougatPlus
|
import com.simplemobiletools.commons.helpers.isNougatPlus
|
||||||
import com.simplemobiletools.commons.interfaces.RenameTab
|
import com.simplemobiletools.commons.interfaces.RenameTab
|
||||||
|
import com.simplemobiletools.commons.models.Android30RenameFormat
|
||||||
import kotlinx.android.synthetic.main.dialog_rename_items_pattern.view.*
|
import kotlinx.android.synthetic.main.dialog_rename_items_pattern.view.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
|
@ -50,8 +51,9 @@ class RenamePatternTab(context: Context, attrs: AttributeSet) : RelativeLayout(c
|
||||||
}
|
}
|
||||||
|
|
||||||
val validPaths = paths.filter { activity?.getDoesFilePathExist(it) == true }
|
val validPaths = paths.filter { activity?.getDoesFilePathExist(it) == true }
|
||||||
val sdFilePath = validPaths.firstOrNull { activity?.isPathOnSD(it) == true } ?: validPaths.firstOrNull()
|
val firstPath = validPaths.firstOrNull()
|
||||||
if (sdFilePath == null) {
|
val sdFilePath = validPaths.firstOrNull { activity?.isPathOnSD(it) == true } ?: firstPath
|
||||||
|
if (firstPath == null || sdFilePath == null) {
|
||||||
activity?.toast(R.string.unknown_error_occurred)
|
activity?.toast(R.string.unknown_error_occurred)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -62,39 +64,44 @@ class RenamePatternTab(context: Context, attrs: AttributeSet) : RelativeLayout(c
|
||||||
return@handleSAFDialog
|
return@handleSAFDialog
|
||||||
}
|
}
|
||||||
|
|
||||||
ignoreClicks = true
|
activity?.handleSAFDialogSdk30(firstPath) {
|
||||||
var pathsCnt = validPaths.size
|
if (!it) {
|
||||||
numbersCnt = pathsCnt.toString().length
|
return@handleSAFDialogSdk30
|
||||||
for (path in validPaths) {
|
|
||||||
if (stopLooping) {
|
|
||||||
return@handleSAFDialog
|
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
ignoreClicks = true
|
||||||
val newPath = getNewPath(path, useMediaFileExtension) ?: continue
|
var pathsCnt = validPaths.size
|
||||||
activity?.renameFile(path, newPath, true) { success, useAndroid30Way ->
|
numbersCnt = pathsCnt.toString().length
|
||||||
if (success) {
|
for (path in validPaths) {
|
||||||
pathsCnt--
|
if (stopLooping) {
|
||||||
if (pathsCnt == 0) {
|
return@handleSAFDialogSdk30
|
||||||
callback(true)
|
}
|
||||||
}
|
|
||||||
} else {
|
try {
|
||||||
ignoreClicks = false
|
val newPath = getNewPath(path, useMediaFileExtension) ?: continue
|
||||||
if (useAndroid30Way) {
|
activity?.renameFile(path, newPath, true) { success, android30Format ->
|
||||||
currentIncrementalNumber = 1
|
if (success) {
|
||||||
stopLooping = true
|
pathsCnt--
|
||||||
renameAllFiles(validPaths, useMediaFileExtension, callback)
|
if (pathsCnt == 0) {
|
||||||
|
callback(true)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
activity?.toast(R.string.unknown_error_occurred)
|
ignoreClicks = false
|
||||||
|
if (android30Format != Android30RenameFormat.NONE) {
|
||||||
|
currentIncrementalNumber = 1
|
||||||
|
stopLooping = true
|
||||||
|
renameAllFiles(validPaths, useMediaFileExtension, android30Format, callback)
|
||||||
|
} else {
|
||||||
|
activity?.toast(R.string.unknown_error_occurred)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
activity?.showErrorToast(e)
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
|
||||||
activity?.showErrorToast(e)
|
|
||||||
}
|
}
|
||||||
|
stopLooping = false
|
||||||
}
|
}
|
||||||
|
|
||||||
stopLooping = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -167,26 +174,59 @@ class RenamePatternTab(context: Context, attrs: AttributeSet) : RelativeLayout(c
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renameAllFiles(paths: List<String>, useMediaFileExtension: Boolean, callback: (success: Boolean) -> Unit) {
|
private fun renameAllFiles(
|
||||||
|
paths: List<String>,
|
||||||
|
useMediaFileExtension: Boolean,
|
||||||
|
android30Format: Android30RenameFormat,
|
||||||
|
callback: (success: Boolean) -> Unit
|
||||||
|
) {
|
||||||
val fileDirItems = paths.map { File(it).toFileDirItem(context) }
|
val fileDirItems = paths.map { File(it).toFileDirItem(context) }
|
||||||
val uriPairs = context.getFileUrisFromFileDirItems(fileDirItems)
|
val uriPairs = context.getFileUrisFromFileDirItems(fileDirItems)
|
||||||
val validPaths = uriPairs.first
|
val validPaths = uriPairs.first
|
||||||
val uris = uriPairs.second
|
val uris = uriPairs.second
|
||||||
|
val activity = activity
|
||||||
activity?.updateSDK30Uris(uris) { success ->
|
activity?.updateSDK30Uris(uris) { success ->
|
||||||
if (success) {
|
if (success) {
|
||||||
try {
|
try {
|
||||||
uris.forEachIndexed { index, uri ->
|
uris.forEachIndexed { index, uri ->
|
||||||
val path = validPaths[index]
|
val path = validPaths[index]
|
||||||
val newFileName = getNewPath(path, useMediaFileExtension)?.getFilenameFromPath() ?: return@forEachIndexed
|
val newFileName = getNewPath(path, useMediaFileExtension)?.getFilenameFromPath() ?: return@forEachIndexed
|
||||||
val values = ContentValues().apply {
|
when (android30Format) {
|
||||||
put(MediaStore.Images.Media.DISPLAY_NAME, newFileName)
|
Android30RenameFormat.SAF -> {
|
||||||
|
val sourceFile = File(path).toFileDirItem(context)
|
||||||
|
val newPath = "${path.getParentPath()}/$newFileName"
|
||||||
|
val destinationFile = sourceFile.copy(path = newPath, name = newFileName)
|
||||||
|
if (activity.copySingleFileSdk30(sourceFile, destinationFile)) {
|
||||||
|
if (!activity.baseConfig.keepLastModified) {
|
||||||
|
File(newPath).setLastModified(System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
activity.contentResolver.delete(uri, null)
|
||||||
|
activity.updateInMediaStore(path, newPath)
|
||||||
|
activity.scanPathsRecursively(arrayListOf(newPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Android30RenameFormat.CONTENT_RESOLVER -> {
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(MediaStore.Images.Media.DISPLAY_NAME, newFileName)
|
||||||
|
}
|
||||||
|
context.contentResolver.update(uri, values, null, null)
|
||||||
|
}
|
||||||
|
Android30RenameFormat.NONE -> {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
callback(true)
|
||||||
|
}
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.contentResolver.update(uri, values, null, null)
|
|
||||||
}
|
}
|
||||||
callback(true)
|
activity.runOnUiThread {
|
||||||
|
callback(true)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
callback(false)
|
activity.runOnUiThread {
|
||||||
|
activity.showErrorToast(e)
|
||||||
|
callback(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,6 +9,7 @@ import com.simplemobiletools.commons.R
|
||||||
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
import com.simplemobiletools.commons.activities.BaseSimpleActivity
|
||||||
import com.simplemobiletools.commons.extensions.*
|
import com.simplemobiletools.commons.extensions.*
|
||||||
import com.simplemobiletools.commons.interfaces.RenameTab
|
import com.simplemobiletools.commons.interfaces.RenameTab
|
||||||
|
import com.simplemobiletools.commons.models.Android30RenameFormat
|
||||||
import kotlinx.android.synthetic.main.tab_rename_simple.view.*
|
import kotlinx.android.synthetic.main.tab_rename_simple.view.*
|
||||||
import java.io.File
|
import java.io.File
|
||||||
|
|
||||||
|
@ -44,8 +45,9 @@ class RenameSimpleTab(context: Context, attrs: AttributeSet) : RelativeLayout(co
|
||||||
}
|
}
|
||||||
|
|
||||||
val validPaths = paths.filter { activity?.getDoesFilePathExist(it) == true }
|
val validPaths = paths.filter { activity?.getDoesFilePathExist(it) == true }
|
||||||
val sdFilePath = validPaths.firstOrNull { activity?.isPathOnSD(it) == true } ?: validPaths.firstOrNull()
|
val firstPath = validPaths.firstOrNull()
|
||||||
if (sdFilePath == null) {
|
val sdFilePath = validPaths.firstOrNull { activity?.isPathOnSD(it) == true } ?: firstPath
|
||||||
|
if (firstPath == null || sdFilePath == null) {
|
||||||
activity?.toast(R.string.unknown_error_occurred)
|
activity?.toast(R.string.unknown_error_occurred)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
@ -55,61 +57,73 @@ class RenameSimpleTab(context: Context, attrs: AttributeSet) : RelativeLayout(co
|
||||||
return@handleSAFDialog
|
return@handleSAFDialog
|
||||||
}
|
}
|
||||||
|
|
||||||
ignoreClicks = true
|
activity?.handleSAFDialogSdk30(firstPath) {
|
||||||
var pathsCnt = validPaths.size
|
if (!it) {
|
||||||
for (path in validPaths) {
|
return@handleSAFDialogSdk30
|
||||||
if (stopLooping) {
|
|
||||||
return@handleSAFDialog
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val fullName = path.getFilenameFromPath()
|
ignoreClicks = true
|
||||||
var dotAt = fullName.lastIndexOf(".")
|
var pathsCnt = validPaths.size
|
||||||
if (dotAt == -1) {
|
for (path in validPaths) {
|
||||||
dotAt = fullName.length
|
if (stopLooping) {
|
||||||
}
|
return@handleSAFDialogSdk30
|
||||||
|
}
|
||||||
|
|
||||||
val name = fullName.substring(0, dotAt)
|
val fullName = path.getFilenameFromPath()
|
||||||
val extension = if (fullName.contains(".")) ".${fullName.getFilenameExtension()}" else ""
|
var dotAt = fullName.lastIndexOf(".")
|
||||||
|
if (dotAt == -1) {
|
||||||
|
dotAt = fullName.length
|
||||||
|
}
|
||||||
|
|
||||||
val newName = if (append) {
|
val name = fullName.substring(0, dotAt)
|
||||||
"$name$valueToAdd$extension"
|
val extension = if (fullName.contains(".")) ".${fullName.getFilenameExtension()}" else ""
|
||||||
} else {
|
|
||||||
"$valueToAdd$fullName"
|
|
||||||
}
|
|
||||||
|
|
||||||
val newPath = "${path.getParentPath()}/$newName"
|
val newName = if (append) {
|
||||||
|
"$name$valueToAdd$extension"
|
||||||
if (activity?.getDoesFilePathExist(newPath) == true) {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
|
|
||||||
activity?.renameFile(path, newPath, true) { success, useAndroid30Way ->
|
|
||||||
if (success) {
|
|
||||||
pathsCnt--
|
|
||||||
if (pathsCnt == 0) {
|
|
||||||
callback(true)
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
ignoreClicks = false
|
"$valueToAdd$fullName"
|
||||||
if (useAndroid30Way) {
|
}
|
||||||
stopLooping = true
|
|
||||||
renameAllFiles(validPaths, append, valueToAdd, callback)
|
val newPath = "${path.getParentPath()}/$newName"
|
||||||
|
|
||||||
|
if (activity?.getDoesFilePathExist(newPath) == true) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
activity?.renameFile(path, newPath, true) { success, android30Format ->
|
||||||
|
if (success) {
|
||||||
|
pathsCnt--
|
||||||
|
if (pathsCnt == 0) {
|
||||||
|
callback(true)
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
activity?.toast(R.string.unknown_error_occurred)
|
ignoreClicks = false
|
||||||
|
if (android30Format != Android30RenameFormat.NONE) {
|
||||||
|
stopLooping = true
|
||||||
|
renameAllFiles(validPaths, append, valueToAdd, android30Format, callback)
|
||||||
|
} else {
|
||||||
|
activity?.toast(R.string.unknown_error_occurred)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
stopLooping = false
|
||||||
}
|
}
|
||||||
|
|
||||||
stopLooping = false
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun renameAllFiles(paths: List<String>, appendString: Boolean, stringToAdd: String, callback: (success: Boolean) -> Unit) {
|
private fun renameAllFiles(
|
||||||
|
paths: List<String>,
|
||||||
|
appendString: Boolean,
|
||||||
|
stringToAdd: String,
|
||||||
|
android30Format: Android30RenameFormat,
|
||||||
|
callback: (success: Boolean) -> Unit
|
||||||
|
) {
|
||||||
val fileDirItems = paths.map { File(it).toFileDirItem(context) }
|
val fileDirItems = paths.map { File(it).toFileDirItem(context) }
|
||||||
val uriPairs = context.getFileUrisFromFileDirItems(fileDirItems)
|
val uriPairs = context.getFileUrisFromFileDirItems(fileDirItems)
|
||||||
val validPaths = uriPairs.first
|
val validPaths = uriPairs.first
|
||||||
val uris = uriPairs.second
|
val uris = uriPairs.second
|
||||||
|
val activity = activity
|
||||||
activity?.updateSDK30Uris(uris) { success ->
|
activity?.updateSDK30Uris(uris) { success ->
|
||||||
if (success) {
|
if (success) {
|
||||||
try {
|
try {
|
||||||
|
@ -131,15 +145,42 @@ class RenameSimpleTab(context: Context, attrs: AttributeSet) : RelativeLayout(co
|
||||||
"$stringToAdd$fullName"
|
"$stringToAdd$fullName"
|
||||||
}
|
}
|
||||||
|
|
||||||
val values = ContentValues().apply {
|
when (android30Format) {
|
||||||
put(MediaStore.Images.Media.DISPLAY_NAME, newName)
|
Android30RenameFormat.SAF -> {
|
||||||
|
val sourceFile = File(path).toFileDirItem(activity)
|
||||||
|
val newPath = "${path.getParentPath()}/$newName"
|
||||||
|
val destinationFile = sourceFile.copy(path = newPath, name = newName)
|
||||||
|
if (activity.copySingleFileSdk30(sourceFile, destinationFile)) {
|
||||||
|
if (!activity.baseConfig.keepLastModified) {
|
||||||
|
File(newPath).setLastModified(System.currentTimeMillis())
|
||||||
|
}
|
||||||
|
activity.contentResolver.delete(uri, null)
|
||||||
|
activity.updateInMediaStore(path, newPath)
|
||||||
|
activity.scanPathsRecursively(arrayListOf(newPath))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Android30RenameFormat.CONTENT_RESOLVER -> {
|
||||||
|
val values = ContentValues().apply {
|
||||||
|
put(MediaStore.Images.Media.DISPLAY_NAME, newName)
|
||||||
|
}
|
||||||
|
context.contentResolver.update(uri, values, null, null)
|
||||||
|
}
|
||||||
|
Android30RenameFormat.NONE -> {
|
||||||
|
activity.runOnUiThread {
|
||||||
|
callback(true)
|
||||||
|
}
|
||||||
|
return@forEachIndexed
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
context.contentResolver.update(uri, values, null, null)
|
|
||||||
}
|
}
|
||||||
callback(true)
|
activity.runOnUiThread {
|
||||||
|
callback(true)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
callback(false)
|
activity.runOnUiThread {
|
||||||
|
activity.showErrorToast(e)
|
||||||
|
callback(false)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue