Fix move operation

This commit is contained in:
Matéo Duparc 2022-04-21 14:06:36 +02:00
parent b6b8bba666
commit cba1418417
Signed by untrusted user: hardcoresushi
GPG Key ID: AFE384344A45E13A
2 changed files with 114 additions and 50 deletions

View File

@ -344,22 +344,29 @@ class ExplorerActivity : BaseExplorerActivity() {
invalidateOptionsMenu() invalidateOptionsMenu()
} }
} else if (currentItemAction == ItemsActions.MOVE){ } else if (currentItemAction == ItemsActions.MOVE){
mapFileForMove(itemsToProcess, itemsToProcess[0].explorerElement.parentPath) itemsToProcess.forEach {
checkPathOverwrite(itemsToProcess, currentDirectoryPath){ items -> it.dstPath = PathUtils.pathJoin(currentDirectoryPath, it.explorerElement.name)
items?.let { it.overwriteConfirmed = false // reset the field in case of a previous cancelled move
lifecycleScope.launch { }
val failedItem = fileOperationService.moveElements(it.toMutableList() as ArrayList<OperationFile>) val toMove = ArrayList<OperationFile>(itemsToProcess.size)
if (failedItem == null) { val toClean = ArrayList<ExplorerElement>()
Toast.makeText(this@ExplorerActivity, R.string.move_success, Toast.LENGTH_SHORT).show() prepareFilesForMove(
} else { itemsToProcess,
CustomAlertDialogBuilder(this@ExplorerActivity, themeValue) toMove,
.setTitle(R.string.error) toClean,
.setMessage(getString(R.string.move_failed, failedItem)) ) {
.setPositiveButton(R.string.ok, null) lifecycleScope.launch {
.show() val failedItem = fileOperationService.moveElements(toMove, toClean)
} if (failedItem == null) {
setCurrentPath(currentDirectoryPath) Toast.makeText(this@ExplorerActivity, R.string.move_success, Toast.LENGTH_SHORT).show()
} else {
CustomAlertDialogBuilder(this@ExplorerActivity, themeValue)
.setTitle(R.string.error)
.setMessage(getString(R.string.move_failed, failedItem))
.setPositiveButton(R.string.ok, null)
.show()
} }
setCurrentPath(currentDirectoryPath)
} }
cancelItemAction() cancelItemAction()
invalidateOptionsMenu() invalidateOptionsMenu()
@ -403,22 +410,85 @@ class ExplorerActivity : BaseExplorerActivity() {
} }
} }
private fun mapFileForMove(items: ArrayList<OperationFile>, srcDirectoryPath: String): ArrayList<OperationFile> { /**
val newItems = ArrayList<OperationFile>() * Ask the user what to do if an item would overwrite another item in case of a move.
items.forEach { *
if (it.explorerElement.isDirectory){ * All [OperationFile] must have a non-null [dstPath][OperationFile.dstPath].
if (gocryptfsVolume.pathExists(PathUtils.pathJoin(currentDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, it.explorerElement.fullPath)))){ */
newItems.addAll( private fun checkMoveOverwrite(items: List<OperationFile>, callback: (List<OperationFile>?) -> Unit) {
mapFileForMove( for (item in items) {
gocryptfsVolume.listDir(it.explorerElement.fullPath).map { e -> OperationFile.fromExplorerElement(e) } as ArrayList<OperationFile>, if (gocryptfsVolume.pathExists(item.dstPath!!) && !item.overwriteConfirmed) {
srcDirectoryPath CustomAlertDialogBuilder(this, themeValue)
.setTitle(R.string.warning)
.setMessage(
getString(
if (item.explorerElement.isDirectory) {
R.string.dir_overwrite_question
} else {
R.string.file_overwrite_question
},
item.dstPath!!
) )
) )
.setPositiveButton(R.string.yes) {_, _ ->
item.overwriteConfirmed = true
checkMoveOverwrite(items, callback)
}
.setNegativeButton(R.string.no) { _, _ ->
with(EditTextDialog(this, R.string.enter_new_name) {
item.dstPath = PathUtils.pathJoin(PathUtils.getParentPath(item.dstPath!!), it)
checkMoveOverwrite(items, callback)
}) {
setSelectedText(item.explorerElement.name)
show()
}
}
.show()
return
}
}
callback(items)
}
/**
* Check for destination overwriting in case of a move operation.
*
* If the user decides to merge the content of a folder, the function recursively tests all
* children of the source folder to see if they will overwrite.
*
* The items to be moved are stored in [toMove]. We also need to keep track of the merged
* folders to delete them after the move. These folders are stored in [toClean].
*/
private fun prepareFilesForMove(
items: List<OperationFile>,
toMove: ArrayList<OperationFile>,
toClean: ArrayList<ExplorerElement>,
onReady: () -> Unit
) {
checkMoveOverwrite(items) { checkedItems ->
checkedItems?.let {
for (item in checkedItems) {
if (!item.overwriteConfirmed || !item.explorerElement.isDirectory) {
toMove.add(item)
}
}
val toCheck = mutableListOf<OperationFile>()
for (item in checkedItems) {
if (item.overwriteConfirmed && item.explorerElement.isDirectory) {
val children = gocryptfsVolume.listDir(item.explorerElement.fullPath)
toCheck.addAll(children.map {
OperationFile(it, PathUtils.pathJoin(item.dstPath!!, it.name))
})
toClean.add(item.explorerElement)
}
}
if (toCheck.isEmpty()) {
onReady()
} else {
prepareFilesForMove(toCheck, toMove, toClean, onReady)
} }
} }
} }
items.addAll(newItems)
return items
} }
private fun cancelItemAction() { private fun cancelItemAction() {

View File

@ -183,32 +183,26 @@ class FileOperationService : Service() {
waitForTask(notification, task).failedItem waitForTask(notification, task).failedItem
} }
suspend fun moveElements(items: ArrayList<OperationFile>): String? = coroutineScope { suspend fun moveElements(toMove: List<OperationFile>, toClean: List<ExplorerElement>): String? = coroutineScope {
val notification = showNotification(R.string.file_op_move_msg, items.size) val notification = showNotification(R.string.file_op_move_msg, toMove.size)
val task = async { val task = async(Dispatchers.IO) {
val total = toMove.size+toClean.size
var failedItem: String? = null var failedItem: String? = null
withContext(Dispatchers.IO) { for ((i, item) in toMove.withIndex()) {
val mergedFolders = ArrayList<String>() if (!gocryptfsVolume.rename(item.explorerElement.fullPath, item.dstPath!!)) {
for (i in 0 until items.size) { failedItem = item.explorerElement.fullPath
if (items[i].explorerElement.isDirectory && gocryptfsVolume.pathExists(items[i].dstPath!!)) { //folder will be merged break
mergedFolders.add(items[i].explorerElement.fullPath) } else {
} else { updateNotificationProgress(notification, i+1, total)
if (!gocryptfsVolume.rename(items[i].explorerElement.fullPath, items[i].dstPath!!)) {
failedItem = items[i].explorerElement.fullPath
break
} else {
updateNotificationProgress(notification, i+1, items.size)
}
}
} }
if (failedItem == null) { }
for (i in 0 until mergedFolders.size) { if (failedItem == null) {
if (!gocryptfsVolume.rmdir(mergedFolders[i])) { for ((i, folder) in toClean.asReversed().withIndex()) {
failedItem = mergedFolders[i] if (!gocryptfsVolume.rmdir(folder.fullPath)) {
break failedItem = folder.fullPath
} else { break
updateNotificationProgress(notification, items.size-(mergedFolders.size-i), items.size) } else {
} updateNotificationProgress(notification, toMove.size+i+1, total)
} }
} }
} }