From cac264043fa5add42da517d133d86c6e5f35ef69 Mon Sep 17 00:00:00 2001 From: Hardcore Sushi Date: Sun, 9 Aug 2020 14:34:42 +0200 Subject: [PATCH] Move feature --- .../droidfs/explorers/BaseExplorerActivity.kt | 10 +- .../droidfs/explorers/ExplorerActivity.kt | 242 ++++++++++++------ .../droidfs/explorers/ExplorerActivityDrop.kt | 9 +- app/src/main/res/drawable/icon_cut.xml | 9 + app/src/main/res/menu/explorer.xml | 11 +- app/src/main/res/values/strings.xml | 32 ++- 6 files changed, 208 insertions(+), 105 deletions(-) create mode 100644 app/src/main/res/drawable/icon_cut.xml diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt index f9b2f52..97ed57c 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/BaseExplorerActivity.kt @@ -250,7 +250,7 @@ open class BaseExplorerActivity : BaseActivity() { dialog.show() } - protected fun checkFileOverwrite(path: String): String? { + protected fun checkPathOverwrite(path: String, isDirectory: Boolean): String? { var outputPath: String? = null if (gocryptfsVolume.pathExists(path)){ val fileName = File(path).name @@ -261,7 +261,7 @@ open class BaseExplorerActivity : BaseActivity() { runOnUiThread { val dialog = ColoredAlertDialogBuilder(this) .setTitle(R.string.warning) - .setMessage(getString(R.string.file_overwrite_question, fileName)) + .setMessage(getString(if (isDirectory){R.string.dir_overwrite_question} else {R.string.file_overwrite_question}, path)) .setNegativeButton(R.string.no) { _, _ -> val dialogEditTextView = layoutInflater.inflate(R.layout.dialog_edit_text, null) val dialogEditText = dialogEditTextView.findViewById(R.id.dialog_edit_text) @@ -269,15 +269,15 @@ open class BaseExplorerActivity : BaseActivity() { dialogEditText.selectAll() val dialog = ColoredAlertDialogBuilder(this) .setView(dialogEditTextView) - .setTitle(getString(R.string.enter_new_filename)) + .setTitle(getString(R.string.enter_new_name)) .setPositiveButton(R.string.ok) { _, _ -> - handler.sendMessage(Message().apply { obj = checkFileOverwrite(PathUtils.path_join(PathUtils.getParentPath(path), dialogEditText.text.toString())) }) + handler.sendMessage(Message().apply { obj = checkPathOverwrite(PathUtils.path_join(PathUtils.getParentPath(path), dialogEditText.text.toString()), isDirectory) }) } .setNegativeButton(R.string.cancel) { _, _ -> handler.sendMessage(Message().apply { obj = null }) } .create() dialogEditText.setOnEditorActionListener { _, _, _ -> dialog.dismiss() - handler.sendMessage(Message().apply { obj = checkFileOverwrite(PathUtils.path_join(PathUtils.getParentPath(path), dialogEditText.text.toString())) }) + handler.sendMessage(Message().apply { obj = checkPathOverwrite(PathUtils.path_join(PathUtils.getParentPath(path), dialogEditText.text.toString()), isDirectory) }) true } dialog.setOnCancelListener { handler.sendMessage(Message().apply { obj = null }) } diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt index 5c16d08..efd5d28 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivity.kt @@ -18,16 +18,18 @@ import sushi.hardcore.droidfs.adapters.IconTextDialogAdapter import sushi.hardcore.droidfs.util.* import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder import java.io.File -import kotlin.collections.ArrayList class ExplorerActivity : BaseExplorerActivity() { - private val PICK_DIRECTORY_REQUEST_CODE = 1 - private val PICK_FILES_REQUEST_CODE = 2 - private val PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE = 3 + companion object { + private const val PICK_DIRECTORY_REQUEST_CODE = 1 + private const val PICK_FILES_REQUEST_CODE = 2 + private const val PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE = 3 + private enum class ItemsActions {NONE, COPY, MOVE} + } private var usf_decrypt = false private var usf_share = false - private var modeSelectLocation = false - private val filesToCopy = ArrayList() + private var currentItemAction = ItemsActions.NONE + private val itemsToProcess = ArrayList() override fun init() { setContentView(R.layout.activity_explorer) usf_decrypt = sharedPrefs.getBoolean("usf_decrypt", false) @@ -35,7 +37,7 @@ class ExplorerActivity : BaseExplorerActivity() { } override fun onExplorerItemLongClick(position: Int) { - cancelCopy() + cancelItemAction() explorerAdapter.onItemLongClick(position) invalidateOptionsMenu() } @@ -44,7 +46,7 @@ class ExplorerActivity : BaseExplorerActivity() { if (fileName.isEmpty()) { Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() } else { - checkFileOverwrite(PathUtils.path_join(currentDirectoryPath, fileName))?.let { + checkPathOverwrite(PathUtils.path_join(currentDirectoryPath, fileName), false)?.let { val handleID = gocryptfsVolume.openWriteMode(it) if (handleID == -1) { ColoredAlertDialogBuilder(this) @@ -62,7 +64,7 @@ class ExplorerActivity : BaseExplorerActivity() { } fun onClickFAB(view: View) { - if (modeSelectLocation){ + if (currentItemAction != ItemsActions.NONE){ openDialogCreateFolder() } else { val adapter = IconTextDialogAdapter(this) @@ -152,7 +154,7 @@ class ExplorerActivity : BaseExplorerActivity() { Looper.prepare() var success = false for (uri in uris) { - val dstPath = checkFileOverwrite(PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(activity, uri))) + val dstPath = checkPathOverwrite(PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(activity, uri)), false) if (dstPath == null){ break } else { @@ -316,7 +318,7 @@ class ExplorerActivity : BaseExplorerActivity() { override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.explorer, menu) - if (modeSelectLocation) { + if (currentItemAction != ItemsActions.NONE) { menu.findItem(R.id.validate).isVisible = true menu.findItem(R.id.close).isVisible = false } else { @@ -328,6 +330,7 @@ class ExplorerActivity : BaseExplorerActivity() { menu.findItem(R.id.select_all).isVisible = anyItemSelected menu.findItem(R.id.delete).isVisible = anyItemSelected menu.findItem(R.id.copy).isVisible = anyItemSelected + menu.findItem(R.id.cut).isVisible = anyItemSelected menu.findItem(R.id.decrypt).isVisible = anyItemSelected && usf_decrypt if (anyItemSelected && usf_share){ var containsDir = false @@ -348,7 +351,7 @@ class ExplorerActivity : BaseExplorerActivity() { override fun onOptionsItemSelected(item: MenuItem): Boolean { return when (item.itemId) { android.R.id.home -> { - cancelCopy() + cancelItemAction() super.onOptionsItemSelected(item) } R.id.select_all -> { @@ -356,55 +359,99 @@ class ExplorerActivity : BaseExplorerActivity() { invalidateOptionsMenu() true } + R.id.cut -> { + for (i in explorerAdapter.selectedItems){ + itemsToProcess.add(explorerElements[i]) + } + currentItemAction = ItemsActions.MOVE + unselectAll() + true + } R.id.copy -> { for (i in explorerAdapter.selectedItems){ - filesToCopy.add(explorerElements[i]) + itemsToProcess.add(explorerElements[i]) } - modeSelectLocation = true + currentItemAction = ItemsActions.COPY unselectAll() true } R.id.validate -> { - object : LoadingTask(this, R.string.loading_msg_copy){ - override fun doTask(activity: AppCompatActivity) { - var failedItem: String? = null - Looper.prepare() - for (element in filesToCopy) { - failedItem = if (element.isDirectory) { - recursiveCopyDirectory(element.fullPath, currentDirectoryPath) - } else { - val dstPath = checkFileOverwrite(PathUtils.path_join(currentDirectoryPath, element.name)) - if (dstPath == null){ + if (currentItemAction == ItemsActions.COPY){ + object : LoadingTask(this, R.string.loading_msg_copy){ + override fun doTask(activity: AppCompatActivity) { + var failedItem: String? = null + Looper.prepare() + for (element in itemsToProcess) { + val dstPath = checkPathOverwrite(PathUtils.path_join(currentDirectoryPath, element.name), element.isDirectory) + failedItem = if (dstPath == null){ "" } else { - if (copyFile(element.fullPath, dstPath)) null else element.fullPath + if (element.isDirectory) { + recursiveCopyDirectory(element.fullPath, dstPath) + } else { + if (copyFile(element.fullPath, dstPath)) null else element.fullPath + } + } + if (failedItem != null){ + if (failedItem.isNotEmpty()) { + stopTask { + ColoredAlertDialogBuilder(activity) + .setTitle(R.string.error) + .setMessage(getString( + R.string.copy_failed, + failedItem + )) + .setPositiveButton(R.string.ok, null) + .show() + } + } + break } } - if (failedItem != null && failedItem.isNotEmpty()) { + if (failedItem == null) { stopTask { ColoredAlertDialogBuilder(activity) - .setTitle(R.string.error) - .setMessage(getString(R.string.copy_failed, failedItem)) + .setTitle(getString(R.string.copy_success)) + .setMessage(getString(R.string.copy_success_msg)) .setPositiveButton(R.string.ok, null) .show() } - break } } - if (failedItem == null) { - stopTask { - ColoredAlertDialogBuilder(activity) - .setTitle(getString(R.string.copy_success)) - .setMessage(getString(R.string.copy_success_msg)) - .setPositiveButton(R.string.ok, null) - .show() - } + override fun doFinally(activity: AppCompatActivity) { + cancelItemAction() + unselectAll() + setCurrentPath(currentDirectoryPath) } } - override fun doFinally(activity: AppCompatActivity) { - cancelCopy() - unselectAll() - setCurrentPath(currentDirectoryPath) + } else if (currentItemAction == ItemsActions.MOVE){ + object : LoadingTask(this, R.string.loading_msg_move){ + override fun doTask(activity: AppCompatActivity) { + Looper.prepare() + val failedItem = moveElements(itemsToProcess, currentDirectoryPath) + if (failedItem == null) { + stopTask { + ColoredAlertDialogBuilder(activity) + .setTitle(getString(R.string.move_success)) + .setMessage(getString(R.string.move_success_msg)) + .setPositiveButton(R.string.ok, null) + .show() + } + } else if (failedItem.isNotEmpty()){ + stopTask { + ColoredAlertDialogBuilder(activity) + .setTitle(R.string.error) + .setMessage(getString(R.string.move_failed, failedItem)) + .setPositiveButton(R.string.ok, null) + .show() + } + } + } + override fun doFinally(activity: AppCompatActivity) { + cancelItemAction() + unselectAll() + setCurrentPath(currentDirectoryPath) + } } } true @@ -443,16 +490,16 @@ class ExplorerActivity : BaseExplorerActivity() { } } - private fun cancelCopy() { - if (modeSelectLocation){ - modeSelectLocation = false - filesToCopy.clear() + private fun cancelItemAction() { + if (currentItemAction != ItemsActions.NONE){ + currentItemAction = ItemsActions.NONE + itemsToProcess.clear() } } override fun onBackPressed() { - if (modeSelectLocation) { - cancelCopy() + if (currentItemAction != ItemsActions.NONE) { + cancelItemAction() invalidateOptionsMenu() } else { super.onBackPressed() @@ -488,24 +535,24 @@ class ExplorerActivity : BaseExplorerActivity() { return success } - private fun recursiveCopyDirectory(srcDirectoryPath: String, outputPath: String): String? { + private fun recursiveCopyDirectory(srcDirectoryPath: String, dstDirectoryPath: String): String? { val mappedElements = gocryptfsVolume.recursiveMapFiles(srcDirectoryPath) - val dstDirectoryPath = PathUtils.path_join(outputPath, File(srcDirectoryPath).name) - if (!gocryptfsVolume.pathExists(dstDirectoryPath)) { + if (!gocryptfsVolume.pathExists(dstDirectoryPath)){ if (!gocryptfsVolume.mkdir(dstDirectoryPath)) { - return dstDirectoryPath + return srcDirectoryPath } } for (e in mappedElements) { - val dstPath = PathUtils.path_join(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, e.fullPath)) - if (e.isDirectory) { - if (!gocryptfsVolume.mkdir(dstPath)){ - return e.fullPath - } + val dstPath = checkPathOverwrite(PathUtils.path_join(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, e.fullPath)), e.isDirectory) + if (dstPath == null){ + return "" } else { - val checkedDstPath = checkFileOverwrite(dstPath) - if (checkedDstPath == null){ - return "" + if (e.isDirectory) { + if (!gocryptfsVolume.pathExists(dstPath)){ + if (!gocryptfsVolume.mkdir(dstPath)){ + return e.fullPath + } + } } else { if (!copyFile(e.fullPath, dstPath)) { return e.fullPath @@ -516,6 +563,38 @@ class ExplorerActivity : BaseExplorerActivity() { return null } + private fun moveDirectory(srcDirectoryPath: String, dstDirectoryPath: String): String? { + if (!gocryptfsVolume.pathExists(dstDirectoryPath)) { + if (!gocryptfsVolume.rename(srcDirectoryPath, dstDirectoryPath)) { + return srcDirectoryPath + } + } else { + moveElements(gocryptfsVolume.listDir(srcDirectoryPath), dstDirectoryPath) + gocryptfsVolume.rmdir(srcDirectoryPath) + } + return null + } + + private fun moveElements(elements: List, dstDirectoryPath: String): String? { + for (element in elements){ + val dstPath = checkPathOverwrite(PathUtils.path_join(dstDirectoryPath, element.name), element.isDirectory) + if (dstPath == null){ + return "" + } else { + if (element.isDirectory){ + moveDirectory(element.fullPath, dstPath)?.let{ + return it + } + } else { + if (!gocryptfsVolume.rename(element.fullPath, dstPath)){ + return element.fullPath + } + } + } + } + return null + } + private fun importFileFromOtherVolume(remoteGocryptfsVolume: GocryptfsVolume, srcPath: String, dstPath: String): Boolean { var success = true val srcHandleID = remoteGocryptfsVolume.openReadMode(srcPath) @@ -525,9 +604,7 @@ class ExplorerActivity : BaseExplorerActivity() { var length: Int val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS) var offset: Long = 0 - while (remoteGocryptfsVolume.readFile(srcHandleID, offset, ioBuffer) - .also { length = it } > 0 - ) { + while (remoteGocryptfsVolume.readFile(srcHandleID, offset, ioBuffer).also { length = it } > 0) { val written = gocryptfsVolume.writeFile(dstHandleID, offset, ioBuffer, length).toLong() if (written == length.toLong()) { @@ -545,35 +622,40 @@ class ExplorerActivity : BaseExplorerActivity() { } private fun safeImportFileFromOtherVolume(remoteGocryptfsVolume: GocryptfsVolume, srcPath: String, dstPath: String): String? { - val checkedDstPath = checkFileOverwrite(PathUtils.path_join(currentDirectoryPath, File(dstPath).name)) + val checkedDstPath = checkPathOverwrite(dstPath, false) return if (checkedDstPath == null){ "" } else { - if (importFileFromOtherVolume(remoteGocryptfsVolume, srcPath, checkedDstPath)) null else dstPath + if (importFileFromOtherVolume(remoteGocryptfsVolume, srcPath, checkedDstPath)) null else srcPath } } private fun recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, remote_directory_path: String, outputPath: String): String? { val mappedElements = gocryptfsVolume.recursiveMapFiles(remote_directory_path) - val dstDirectoryPath = PathUtils.path_join(outputPath, File(remote_directory_path).name) - if (!gocryptfsVolume.pathExists(dstDirectoryPath)) { - if (!gocryptfsVolume.mkdir(dstDirectoryPath)) { - return dstDirectoryPath - } - } - for (e in mappedElements) { - val dstPath = PathUtils.path_join(dstDirectoryPath, PathUtils.getRelativePath(remote_directory_path, e.fullPath)) - if (e.isDirectory) { - if (!gocryptfsVolume.mkdir(dstPath)){ - return e.fullPath + val dstDirectoryPath = checkPathOverwrite(PathUtils.path_join(outputPath, File(remote_directory_path).name), true) + if (dstDirectoryPath == null){ + return "" + } else { + if (!gocryptfsVolume.pathExists(dstDirectoryPath)) { + if (!gocryptfsVolume.mkdir(dstDirectoryPath)) { + return remote_directory_path } - } else { - val checkedDstPath = checkFileOverwrite(dstPath) - if (checkedDstPath == null){ + } + for (e in mappedElements) { + val dstPath = checkPathOverwrite(PathUtils.path_join(dstDirectoryPath, PathUtils.getRelativePath(remote_directory_path, e.fullPath)), e.isDirectory) + if (dstPath == null){ return "" } else { - if (!importFileFromOtherVolume(remote_gocryptfsVolume, e.fullPath, checkedDstPath)) { - return e.fullPath + if (e.isDirectory) { + if (!gocryptfsVolume.pathExists(dstPath)){ + if (!gocryptfsVolume.mkdir(dstPath)){ + return e.fullPath + } + } + } else { + if (!importFileFromOtherVolume(remote_gocryptfsVolume, e.fullPath, dstPath)) { + return e.fullPath + } } } } diff --git a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt index bad2e3a..836275d 100644 --- a/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt +++ b/app/src/main/java/sushi/hardcore/droidfs/explorers/ExplorerActivityDrop.kt @@ -45,25 +45,24 @@ class ExplorerActivityDrop : BaseExplorerActivity() { errorMsg = if (uri == null){ getString(R.string.share_intent_parsing_failed) } else { - val outputPathTest = PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(activity, uri)) - val outputPath = checkFileOverwrite(outputPathTest) + val outputPath = checkPathOverwrite(PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(activity, uri)), false) if (outputPath == null) { "" } else { - if (gocryptfsVolume.importFile(activity, uri, outputPath)) null else getString(R.string.import_failed, outputPath) + if (gocryptfsVolume.importFile(activity, uri, outputPath)) null else getString(R.string.import_failed, uri) } } } else if (intent.action == Intent.ACTION_SEND_MULTIPLE) { val uris = intent.getParcelableArrayListExtra(Intent.EXTRA_STREAM) if (uris != null){ for (uri in uris) { - val outputPath = checkFileOverwrite(PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(activity, uri))) + val outputPath = checkPathOverwrite(PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(activity, uri)), false) if (outputPath == null){ errorMsg = "" break } else { if (!gocryptfsVolume.importFile(activity, uri, outputPath)) { - errorMsg = getString(R.string.import_failed, outputPath) + errorMsg = getString(R.string.import_failed, uri) break } } diff --git a/app/src/main/res/drawable/icon_cut.xml b/app/src/main/res/drawable/icon_cut.xml new file mode 100644 index 0000000..733f8c5 --- /dev/null +++ b/app/src/main/res/drawable/icon_cut.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/menu/explorer.xml b/app/src/main/res/menu/explorer.xml index 9b98333..304c0be 100644 --- a/app/src/main/res/menu/explorer.xml +++ b/app/src/main/res/menu/explorer.xml @@ -9,10 +9,10 @@ android:icon="@drawable/icon_select_all"/> + android:icon="@drawable/icon_cut"/> + + Folder creation failed. Import successful ! The selected files have been successfully imported. - Import of %1$s failed. - Export of %1$s failed. + Import of %s failed. + Export of %s failed. Export successful ! The selected files have been successfully exported. - Deletion of %1$s failed + Deletion of %s failed Passwords don\'t match The selected directory isn\'t empty Volume successfully created ! @@ -40,24 +40,24 @@ Parent Folder Please enter the volume path Open with external app - Are you sure you want to delete %1$s ? - Are you sure you want to delete these %1$s items ? - Location: /%1$s - Total size: %1$s + Are you sure you want to delete %s ? + Are you sure you want to delete these %s items ? + Location: /%s + Total size: %s Import from another volume Failed to open this file. - Volume: %1$s + Volume: %s YES NO Do you want to wipe the original files ? - Wiping failed: %1$s + Wiping failed: %s Files successfully wiped ! The imported files have been successfully wiped from their original locations. Warning !\nThis password will be the only way to decrypt the volume and access the files inside.\nChoose a very strong password (not \"123456\" or \"password\"), do not lose it and keep it secure (preferably only in your mind).\n\nDroidFS cannot protect you from screen recording apps, keyloggers, apk backdooring, compromised root accesses, memory dumps etc.\nDo not type passwords in insecure environments. Warning !\nOpening volumes in insecure environments can lead to data leaks.\nDroidFS cannot protect you from screen recording apps, keyloggers, apk backdooring, compromised root accesses, memory dumps etc.\nDo not open volumes containing sensitive data unless you know exactly what you are doing. Rename New name: - Failed to rename %1$s + Failed to rename %s Remember volume path Sort order: Old password: @@ -137,16 +137,22 @@ Share Decrypt Copying selected items… - Copy of %1$s failed. + Copy of %s failed. The selected items have been successfully copied. Copy successful ! Add Take photo - Picture saved to %1$s + Picture saved to %s Failed to save this picture. N//A %s already exists, do you want to overwrite it ? - Enter new filename + %s already exists, do you want to merge its content ? + Enter new name Reset theme color Reset theme color to the default one + Copy + Moving selected items… + Move of %s failed. + The selected items have been successfully moved. + Move successful !