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()
}
} else if (currentItemAction == ItemsActions.MOVE){
mapFileForMove(itemsToProcess, itemsToProcess[0].explorerElement.parentPath)
checkPathOverwrite(itemsToProcess, currentDirectoryPath){ items ->
items?.let {
lifecycleScope.launch {
val failedItem = fileOperationService.moveElements(it.toMutableList() as ArrayList<OperationFile>)
if (failedItem == null) {
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)
itemsToProcess.forEach {
it.dstPath = PathUtils.pathJoin(currentDirectoryPath, it.explorerElement.name)
it.overwriteConfirmed = false // reset the field in case of a previous cancelled move
}
val toMove = ArrayList<OperationFile>(itemsToProcess.size)
val toClean = ArrayList<ExplorerElement>()
prepareFilesForMove(
itemsToProcess,
toMove,
toClean,
) {
lifecycleScope.launch {
val failedItem = fileOperationService.moveElements(toMove, toClean)
if (failedItem == null) {
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()
invalidateOptionsMenu()
@ -403,22 +410,85 @@ class ExplorerActivity : BaseExplorerActivity() {
}
}
private fun mapFileForMove(items: ArrayList<OperationFile>, srcDirectoryPath: String): ArrayList<OperationFile> {
val newItems = ArrayList<OperationFile>()
items.forEach {
if (it.explorerElement.isDirectory){
if (gocryptfsVolume.pathExists(PathUtils.pathJoin(currentDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, it.explorerElement.fullPath)))){
newItems.addAll(
mapFileForMove(
gocryptfsVolume.listDir(it.explorerElement.fullPath).map { e -> OperationFile.fromExplorerElement(e) } as ArrayList<OperationFile>,
srcDirectoryPath
/**
* Ask the user what to do if an item would overwrite another item in case of a move.
*
* All [OperationFile] must have a non-null [dstPath][OperationFile.dstPath].
*/
private fun checkMoveOverwrite(items: List<OperationFile>, callback: (List<OperationFile>?) -> Unit) {
for (item in items) {
if (gocryptfsVolume.pathExists(item.dstPath!!) && !item.overwriteConfirmed) {
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() {

View File

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