FileOperationService

This commit is contained in:
Matéo Duparc 2020-12-29 17:05:02 +01:00
parent cab0c3bf30
commit 1a6c1d2901
Signed by untrusted user: hardcoresushi
GPG Key ID: 007F84120107191E
12 changed files with 545 additions and 539 deletions

View File

@ -86,6 +86,8 @@
android:name=".file_viewers.TextEditor" android:name=".file_viewers.TextEditor"
android:configChanges="screenSize|orientation" /> android:configChanges="screenSize|orientation" />
<service android:name=".FileOperationService" android:exported="false"/>
<provider <provider
android:name=".provider.RestrictedFileProvider" android:name=".provider.RestrictedFileProvider"
android:authorities="${applicationId}.temporary_provider" android:authorities="${applicationId}.temporary_provider"

View File

@ -0,0 +1,194 @@
package sushi.hardcore.droidfs
import android.app.Service
import android.content.Intent
import android.net.Uri
import android.os.*
import androidx.documentfile.provider.DocumentFile
import sushi.hardcore.droidfs.explorers.ExplorerElement
import sushi.hardcore.droidfs.file_operations.OperationFile
import sushi.hardcore.droidfs.util.GocryptfsVolume
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.util.Wiper
import java.io.File
import java.io.FileNotFoundException
class FileOperationService : Service() {
private val binder = LocalBinder()
private lateinit var gocryptfsVolume: GocryptfsVolume
inner class LocalBinder : Binder() {
fun getService(): FileOperationService = this@FileOperationService
fun setGocryptfsVolume(g: GocryptfsVolume) {
gocryptfsVolume = g
}
}
override fun onBind(p0: Intent?): IBinder {
return binder
}
private fun copyFile(srcPath: String, dstPath: String, remoteGocryptfsVolume: GocryptfsVolume = gocryptfsVolume): Boolean {
var success = true
val srcHandleId = remoteGocryptfsVolume.openReadMode(srcPath)
if (srcHandleId != -1){
val dstHandleId = gocryptfsVolume.openWriteMode(dstPath)
if (dstHandleId != -1){
var offset: Long = 0
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
var length: Int
while (remoteGocryptfsVolume.readFile(srcHandleId, offset, ioBuffer).also { length = it } > 0) {
val written = gocryptfsVolume.writeFile(dstHandleId, offset, ioBuffer, length).toLong()
if (written == length.toLong()) {
offset += written
} else {
success = false
break
}
}
gocryptfsVolume.closeFile(dstHandleId)
} else {
success = false
}
remoteGocryptfsVolume.closeFile(srcHandleId)
} else {
success = false
}
return success
}
fun copyElements(items: ArrayList<OperationFile>, remoteGocryptfsVolume: GocryptfsVolume = gocryptfsVolume, callback: (String?) -> Unit){
Thread {
var failedItem: String? = null
for (item in items){
if (item.explorerElement.isDirectory){
if (!gocryptfsVolume.pathExists(item.dstPath!!)) {
if (!gocryptfsVolume.mkdir(item.dstPath!!)) {
failedItem = item.explorerElement.fullPath
}
}
} else {
if (!copyFile(item.explorerElement.fullPath, item.dstPath!!, remoteGocryptfsVolume)){
failedItem = item.explorerElement.fullPath
}
}
if (failedItem != null){
break
}
}
callback(failedItem)
}.start()
}
fun moveElements(items: ArrayList<OperationFile>, callback: (String?) -> Unit){
Thread {
val mergedFolders = ArrayList<String>()
var failedItem: String? = null
for (item in items){
if (item.explorerElement.isDirectory && gocryptfsVolume.pathExists(item.dstPath!!)){ //folder will be merged
mergedFolders.add(item.explorerElement.fullPath)
} else {
if (!gocryptfsVolume.rename(item.explorerElement.fullPath, item.dstPath!!)){
failedItem = item.explorerElement.fullPath
break
}
}
}
if (failedItem == null){
for (path in mergedFolders) {
if (!gocryptfsVolume.rmdir(path)){
failedItem = path
break
}
}
}
callback(failedItem)
}.start()
}
fun importFilesFromUris(items: ArrayList<OperationFile>, uris: List<Uri>, callback: (String?) -> Unit){
Thread {
var failedIndex = -1
for (i in 0 until items.size) {
try {
if (!gocryptfsVolume.importFile(this, uris[i], items[i].dstPath!!)){
failedIndex = i
}
} catch (e: FileNotFoundException){
failedIndex = i
}
if (failedIndex != -1){
callback(uris[failedIndex].toString())
break
}
}
if (failedIndex == -1){
callback(null)
}
}.start()
}
fun wipeUris(uris: List<Uri>, callback: (String?) -> Unit){
Thread {
var errorMsg: String? = null
for (uri in uris) {
errorMsg = Wiper.wipe(this, uri)
if (errorMsg != null) {
break
}
}
callback(errorMsg)
}.start()
}
private fun exportFileInto(srcPath: String, treeDocumentFile: DocumentFile): Boolean {
val outputStream = treeDocumentFile.createFile("*/*", File(srcPath).name)?.uri?.let {
contentResolver.openOutputStream(it)
}
return if (outputStream != null){
gocryptfsVolume.exportFile(srcPath, outputStream)
} else {
false
}
}
private fun recursiveExportDirectory(plain_directory_path: String, treeDocumentFile: DocumentFile): String? {
treeDocumentFile.createDirectory(File(plain_directory_path).name)?.let { childTree ->
val explorerElements = gocryptfsVolume.listDir(plain_directory_path)
for (e in explorerElements) {
val fullPath = PathUtils.pathJoin(plain_directory_path, e.name)
if (e.isDirectory) {
val failedItem = recursiveExportDirectory(fullPath, childTree)
failedItem?.let { return it }
} else {
if (!exportFileInto(fullPath, childTree)){
return fullPath
}
}
}
return null
}
return treeDocumentFile.name
}
fun exportFiles(uri: Uri, items: List<ExplorerElement>, callback: (String?) -> Unit){
Thread {
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
DocumentFile.fromTreeUri(this, uri)?.let { treeDocumentFile ->
var failedItem: String? = null
for (element in items) {
failedItem = if (element.isDirectory) {
recursiveExportDirectory(element.fullPath, treeDocumentFile)
} else {
if (exportFileInto(element.fullPath, treeDocumentFile)) null else element.fullPath
}
if (failedItem != null) {
break
}
}
callback(failedItem)
}
}.start()
}
}

View File

@ -1,12 +1,12 @@
package sushi.hardcore.droidfs.explorers package sushi.hardcore.droidfs.explorers
import android.content.DialogInterface import android.content.ComponentName
import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.ServiceConnection
import android.net.Uri import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler import android.os.IBinder
import android.os.Looper
import android.os.Message
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
@ -26,10 +26,12 @@ import sushi.hardcore.droidfs.ConstValues.Companion.isAudio
import sushi.hardcore.droidfs.ConstValues.Companion.isImage import sushi.hardcore.droidfs.ConstValues.Companion.isImage
import sushi.hardcore.droidfs.ConstValues.Companion.isText import sushi.hardcore.droidfs.ConstValues.Companion.isText
import sushi.hardcore.droidfs.ConstValues.Companion.isVideo import sushi.hardcore.droidfs.ConstValues.Companion.isVideo
import sushi.hardcore.droidfs.FileOperationService
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.adapters.DialogSingleChoiceAdapter import sushi.hardcore.droidfs.adapters.DialogSingleChoiceAdapter
import sushi.hardcore.droidfs.adapters.ExplorerElementAdapter import sushi.hardcore.droidfs.adapters.ExplorerElementAdapter
import sushi.hardcore.droidfs.adapters.OpenAsDialogAdapter import sushi.hardcore.droidfs.adapters.OpenAsDialogAdapter
import sushi.hardcore.droidfs.file_operations.OperationFile
import sushi.hardcore.droidfs.file_viewers.AudioPlayer import sushi.hardcore.droidfs.file_viewers.AudioPlayer
import sushi.hardcore.droidfs.file_viewers.ImageViewer import sushi.hardcore.droidfs.file_viewers.ImageViewer
import sushi.hardcore.droidfs.file_viewers.TextEditor import sushi.hardcore.droidfs.file_viewers.TextEditor
@ -37,11 +39,8 @@ import sushi.hardcore.droidfs.file_viewers.VideoPlayer
import sushi.hardcore.droidfs.provider.RestrictedFileProvider import sushi.hardcore.droidfs.provider.RestrictedFileProvider
import sushi.hardcore.droidfs.util.ExternalProvider import sushi.hardcore.droidfs.util.ExternalProvider
import sushi.hardcore.droidfs.util.GocryptfsVolume import sushi.hardcore.droidfs.util.GocryptfsVolume
import sushi.hardcore.droidfs.util.LoadingTask
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import java.io.File
import java.io.FileNotFoundException
open class BaseExplorerActivity : BaseActivity() { open class BaseExplorerActivity : BaseActivity() {
private lateinit var sortOrderEntries: Array<String> private lateinit var sortOrderEntries: Array<String>
@ -55,6 +54,7 @@ open class BaseExplorerActivity : BaseActivity() {
field = value field = value
explorerViewModel.currentDirectoryPath = value explorerViewModel.currentDirectoryPath = value
} }
protected lateinit var fileOperationService: FileOperationService
protected lateinit var explorerElements: MutableList<ExplorerElement> protected lateinit var explorerElements: MutableList<ExplorerElement>
protected lateinit var explorerAdapter: ExplorerElementAdapter protected lateinit var explorerAdapter: ExplorerElementAdapter
private var isCreating = true private var isCreating = true
@ -87,6 +87,7 @@ open class BaseExplorerActivity : BaseActivity() {
setCurrentPath(currentDirectoryPath) setCurrentPath(currentDirectoryPath)
refresher.isRefreshing = false refresher.isRefreshing = false
} }
bindFileOperationService()
} }
class ExplorerViewModel: ViewModel() { class ExplorerViewModel: ViewModel() {
@ -97,6 +98,21 @@ open class BaseExplorerActivity : BaseActivity() {
setContentView(R.layout.activity_explorer_base) setContentView(R.layout.activity_explorer_base)
} }
protected open fun bindFileOperationService(){
Intent(this, FileOperationService::class.java).also {
bindService(it, object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
val binder = service as FileOperationService.LocalBinder
fileOperationService = binder.getService()
binder.setGocryptfsVolume(gocryptfsVolume)
}
override fun onServiceDisconnected(arg0: ComponentName) {
}
}, Context.BIND_AUTO_CREATE)
}
}
private fun startFileViewer(cls: Class<*>, filePath: String, sortOrder: String = ""){ private fun startFileViewer(cls: Class<*>, filePath: String, sortOrder: String = ""){
val intent = Intent(this, cls) val intent = Intent(this, cls)
intent.putExtra("path", filePath) intent.putExtra("path", filePath)
@ -274,97 +290,97 @@ open class BaseExplorerActivity : BaseActivity() {
dialog.show() dialog.show()
} }
protected fun checkPathOverwrite(path: String, isDirectory: Boolean): String? { protected fun checkPathOverwrite(items: ArrayList<OperationFile>, dstDirectoryPath: String, callback: (ArrayList<OperationFile>?) -> Unit) {
var outputPath: String? = null val srcDirectoryPath = items[0].explorerElement.parentPath
if (gocryptfsVolume.pathExists(path)){ var ready = true
val fileName = File(path).name for (i in 0 until items.size) {
val handler = Handler{ msg -> val testDstPath: String
outputPath = msg.obj as String? if (items[i].dstPath == null){
throw RuntimeException() testDstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].explorerElement.fullPath))
if (gocryptfsVolume.pathExists(testDstPath)){
ready = false
} else {
items[i].dstPath = testDstPath
}
} else {
testDstPath = items[i].dstPath!!
if (gocryptfsVolume.pathExists(testDstPath) && !items[i].overwriteConfirmed){
ready = false
}
} }
runOnUiThread { if (!ready){
val dialog = ColoredAlertDialogBuilder(this) ColoredAlertDialogBuilder(this)
.setTitle(R.string.warning) .setTitle(R.string.warning)
.setMessage(getString(if (isDirectory){R.string.dir_overwrite_question} else {R.string.file_overwrite_question}, path)) .setMessage(getString(if (items[i].explorerElement.isDirectory){R.string.dir_overwrite_question} else {R.string.file_overwrite_question}, testDstPath))
.setPositiveButton(R.string.yes) {_, _ ->
items[i].dstPath = testDstPath
items[i].overwriteConfirmed = true
checkPathOverwrite(items, dstDirectoryPath, callback)
}
.setNegativeButton(R.string.no) { _, _ -> .setNegativeButton(R.string.no) { _, _ ->
val dialogEditTextView = layoutInflater.inflate(R.layout.dialog_edit_text, null) val dialogEditTextView = layoutInflater.inflate(R.layout.dialog_edit_text, null)
val dialogEditText = dialogEditTextView.findViewById<EditText>(R.id.dialog_edit_text) val dialogEditText = dialogEditTextView.findViewById<EditText>(R.id.dialog_edit_text)
dialogEditText.setText(fileName) dialogEditText.setText(items[i].explorerElement.name)
dialogEditText.selectAll() dialogEditText.selectAll()
val dialog = ColoredAlertDialogBuilder(this) val dialog = ColoredAlertDialogBuilder(this)
.setView(dialogEditTextView) .setView(dialogEditTextView)
.setTitle(R.string.enter_new_name) .setTitle(R.string.enter_new_name)
.setPositiveButton(R.string.ok) { _, _ -> .setPositiveButton(R.string.ok) { _, _ ->
handler.sendMessage(Message().apply { obj = checkPathOverwrite(PathUtils.pathJoin(PathUtils.getParentPath(path), dialogEditText.text.toString()), isDirectory) }) items[i].dstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].explorerElement.parentPath), dialogEditText.text.toString())
} checkPathOverwrite(items, dstDirectoryPath, callback)
.setNegativeButton(R.string.cancel) { _, _ -> handler.sendMessage(Message().apply { obj = null }) } }
.create() .setOnCancelListener{
callback(null)
}
.create()
dialogEditText.setOnEditorActionListener { _, _, _ -> dialogEditText.setOnEditorActionListener { _, _, _ ->
dialog.dismiss() dialog.dismiss()
handler.sendMessage(Message().apply { obj = checkPathOverwrite(PathUtils.pathJoin(PathUtils.getParentPath(path), dialogEditText.text.toString()), isDirectory) }) items[i].dstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].explorerElement.parentPath), dialogEditText.text.toString())
checkPathOverwrite(items, dstDirectoryPath, callback)
true true
} }
dialog.setOnCancelListener { handler.sendMessage(Message().apply { obj = null }) }
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE) dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
dialog.show() dialog.show()
} }
.setPositiveButton(R.string.yes) { _, _ -> handler.sendMessage(Message().apply { obj = path }) } .setOnCancelListener{
.create() callback(null)
dialog.setOnCancelListener { handler.sendMessage(Message().apply { obj = null }) } }
dialog.show() .show()
break
} }
try { Looper.loop() }
catch (e: RuntimeException) {}
} else {
outputPath = path
} }
return outputPath if (ready){
callback(items)
}
} }
protected fun importFilesFromUris(uris: List<Uri>, task: LoadingTask, callback: (DialogInterface.OnClickListener)? = null): Boolean { protected fun importFilesFromUris(uris: List<Uri>, callback: (String?) -> Unit) {
var success = false val items = ArrayList<OperationFile>()
for (uri in uris) { for (uri in uris) {
val fileName = PathUtils.getFilenameFromURI(task.activity, uri) val fileName = PathUtils.getFilenameFromURI(this, uri)
if (fileName == null){ if (fileName == null) {
task.stopTask { ColoredAlertDialogBuilder(this)
ColoredAlertDialogBuilder(task.activity)
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(getString(R.string.error_retrieving_filename, uri)) .setMessage(getString(R.string.error_retrieving_filename, uri))
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show() .show()
} items.clear()
success = false
break break
} else { } else {
val dstPath = checkPathOverwrite(PathUtils.pathJoin(currentDirectoryPath, fileName), false) items.add(OperationFile.fromExplorerElement(ExplorerElement(fileName, 1, -1, -1, currentDirectoryPath)))
if (dstPath == null){ }
break }
} else { if (items.size > 0) {
var message: String? = null checkPathOverwrite(items, currentDirectoryPath) { checkedItems ->
try { checkedItems?.let {
success = gocryptfsVolume.importFile(task.activity, uri, dstPath) fileOperationService.importFilesFromUris(checkedItems, uris){ failedItem ->
} catch (e: FileNotFoundException){ runOnUiThread {
message = if (e.message != null){ callback(failedItem)
e.message!!+"\n"
} else {
""
} }
} }
if (!success || message != null) {
task.stopTask {
ColoredAlertDialogBuilder(task.activity)
.setTitle(R.string.error)
.setMessage((message ?: "")+getString(R.string.import_failed, uri))
.setCancelable(callback == null)
.setPositiveButton(R.string.ok, callback)
.show()
}
break
}
} }
} }
} }
return success
} }
protected fun rename(old_name: String, new_name: String){ protected fun rename(old_name: String, new_name: String){

View File

@ -3,20 +3,20 @@ package sushi.hardcore.droidfs.explorers
import android.app.Activity import android.app.Activity
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Looper
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.EditText import android.widget.EditText
import android.widget.Toast import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import androidx.documentfile.provider.DocumentFile
import sushi.hardcore.droidfs.CameraActivity import sushi.hardcore.droidfs.CameraActivity
import sushi.hardcore.droidfs.OpenActivity import sushi.hardcore.droidfs.OpenActivity
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.adapters.IconTextDialogAdapter import sushi.hardcore.droidfs.adapters.IconTextDialogAdapter
import sushi.hardcore.droidfs.util.* import sushi.hardcore.droidfs.file_operations.OperationFile
import sushi.hardcore.droidfs.util.ExternalProvider
import sushi.hardcore.droidfs.util.GocryptfsVolume
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
import java.io.File import java.io.File
@ -30,7 +30,7 @@ class ExplorerActivity : BaseExplorerActivity() {
private var usf_decrypt = false private var usf_decrypt = false
private var usf_share = false private var usf_share = false
private var currentItemAction = ItemsActions.NONE private var currentItemAction = ItemsActions.NONE
private val itemsToProcess = ArrayList<ExplorerElement>() private val itemsToProcess = ArrayList<OperationFile>()
override fun init() { override fun init() {
setContentView(R.layout.activity_explorer) setContentView(R.layout.activity_explorer)
usf_decrypt = sharedPrefs.getBoolean("usf_decrypt", false) usf_decrypt = sharedPrefs.getBoolean("usf_decrypt", false)
@ -47,19 +47,17 @@ class ExplorerActivity : BaseExplorerActivity() {
if (fileName.isEmpty()) { if (fileName.isEmpty()) {
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show() Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
} else { } else {
checkPathOverwrite(PathUtils.pathJoin(currentDirectoryPath, fileName), false)?.let { val handleID = gocryptfsVolume.openWriteMode(fileName) //don't check overwrite because openWriteMode open in read-write (doesn't erase content)
val handleID = gocryptfsVolume.openWriteMode(it) if (handleID == -1) {
if (handleID == -1) { ColoredAlertDialogBuilder(this)
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error) .setTitle(R.string.error)
.setMessage(R.string.file_creation_failed) .setMessage(R.string.file_creation_failed)
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show() .show()
} else { } else {
gocryptfsVolume.closeFile(handleID) gocryptfsVolume.closeFile(handleID)
setCurrentPath(currentDirectoryPath) setCurrentPath(currentDirectoryPath)
invalidateOptionsMenu() invalidateOptionsMenu()
}
} }
} }
} }
@ -138,162 +136,137 @@ class ExplorerActivity : BaseExplorerActivity() {
super.onActivityResult(requestCode, resultCode, data) super.onActivityResult(requestCode, resultCode, data)
if (requestCode == PICK_FILES_REQUEST_CODE) { if (requestCode == PICK_FILES_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data != null) { if (resultCode == Activity.RESULT_OK && data != null) {
object : LoadingTask(this, R.string.loading_msg_import){ val uris: MutableList<Uri> = ArrayList()
override fun doTask(activity: AppCompatActivity) { val singleUri = data.data
val uris: MutableList<Uri> = ArrayList() if (singleUri == null) { //multiples choices
val singleUri = data.data val clipData = data.clipData
if (singleUri == null) { //multiples choices if (clipData != null){
val clipData = data.clipData for (i in 0 until clipData.itemCount) {
if (clipData != null){ uris.add(clipData.getItemAt(i).uri)
for (i in 0 until clipData.itemCount) {
uris.add(clipData.getItemAt(i).uri)
}
}
} else {
uris.add(singleUri)
} }
Looper.prepare() }
if (importFilesFromUris(uris, this)) { } else {
stopTask { uris.add(singleUri)
ColoredAlertDialogBuilder(activity) }
.setTitle(R.string.success_import) importFilesFromUris(uris){ failedItem ->
.setMessage(""" if (failedItem == null){
ColoredAlertDialogBuilder(this)
.setTitle(R.string.success_import)
.setMessage("""
${getString(R.string.success_import_msg)} ${getString(R.string.success_import_msg)}
${getString(R.string.ask_for_wipe)} ${getString(R.string.ask_for_wipe)}
""".trimIndent()) """.trimIndent())
.setPositiveButton(R.string.yes) { _, _ -> .setPositiveButton(R.string.yes) { _, _ ->
object : LoadingTask(activity, R.string.loading_msg_wipe){ fileOperationService.wipeUris(uris) { errorMsg ->
override fun doTask(activity: AppCompatActivity) { runOnUiThread {
var success = true if (errorMsg == null){
for (uri in uris) { ColoredAlertDialogBuilder(this)
val errorMsg = Wiper.wipe(activity, uri) .setTitle(R.string.wipe_successful)
if (errorMsg != null) { .setMessage(R.string.wipe_success_msg)
stopTask { .setPositiveButton(R.string.ok, null)
ColoredAlertDialogBuilder(activity) .show()
.setTitle(R.string.error) } else {
.setMessage(getString(R.string.wipe_failed, errorMsg)) ColoredAlertDialogBuilder(this)
.setPositiveButton(R.string.ok, null) .setTitle(R.string.error)
.show() .setMessage(getString(R.string.wipe_failed, errorMsg))
} .setPositiveButton(R.string.ok, null)
success = false .show()
break
}
}
if (success) {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.wipe_successful)
.setMessage(R.string.wipe_success_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
}
} }
} }
} }
.setNegativeButton(R.string.no, null) }
.show() .setNegativeButton(R.string.no, null)
} .show()
} } else {
} ColoredAlertDialogBuilder(this)
override fun doFinally(activity: AppCompatActivity){ .setTitle(R.string.error)
setCurrentPath(currentDirectoryPath) .setMessage(getString(R.string.import_failed, failedItem))
.setPositiveButton(R.string.ok, null)
.show()
} }
} }
} }
} else if (requestCode == PICK_DIRECTORY_REQUEST_CODE) { } else if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data != null) { if (resultCode == Activity.RESULT_OK && data != null) {
object : LoadingTask(this, R.string.loading_msg_export){ data.data?.let { uri ->
override fun doTask(activity: AppCompatActivity) { fileOperationService.exportFiles(uri, explorerAdapter.selectedItems.map { i -> explorerElements[i] }){ failedItem ->
data.data?.let {uri -> runOnUiThread {
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION) if (failedItem == null){
DocumentFile.fromTreeUri(activity, uri)?.let { treeDocumentFile -> ColoredAlertDialogBuilder(this)
var failedItem: String? = null .setTitle(R.string.success_export)
for (i in explorerAdapter.selectedItems) { .setMessage(R.string.success_export_msg)
val element = explorerAdapter.getItem(i) .setPositiveButton(R.string.ok, null)
val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name) .show()
failedItem = if (element.isDirectory) { } else {
recursiveExportDirectory(fullPath, treeDocumentFile) ColoredAlertDialogBuilder(this)
} else { .setTitle(R.string.error)
if (exportFileInto(fullPath, treeDocumentFile)) null else fullPath .setMessage(getString(R.string.export_failed, failedItem))
} .setPositiveButton(R.string.ok, null)
if (failedItem != null) { .show()
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.error)
.setMessage(getString(R.string.export_failed, failedItem))
.setPositiveButton(R.string.ok, null)
.show()
}
break
}
}
if (failedItem == null) {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.success_export)
.setMessage(R.string.success_export_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
}
} }
} }
} }
override fun doFinally(activity: AppCompatActivity) { unselectAll()
unselectAll()
}
} }
} }
} else if (requestCode == PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE) { } else if (requestCode == PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE) {
if (resultCode == Activity.RESULT_OK && data != null) { if (resultCode == Activity.RESULT_OK && data != null) {
object : LoadingTask(this, R.string.loading_msg_import){ val remoteSessionID = data.getIntExtra("sessionID", -1)
override fun doTask(activity: AppCompatActivity) { val remoteGocryptfsVolume = GocryptfsVolume(remoteSessionID)
val remoteSessionID = data.getIntExtra("sessionID", -1) val path = data.getStringExtra("path")
val remoteGocryptfsVolume = GocryptfsVolume(remoteSessionID) val operationFiles = ArrayList<OperationFile>()
val path = data.getStringExtra("path") if (path == null){ //multiples elements
var failedItem: String? = null val paths = data.getStringArrayListExtra("paths")
Looper.prepare() val types = data.getIntegerArrayListExtra("types")
if (path == null) { if (types != null && paths != null){
val paths = data.getStringArrayListExtra("paths") for (i in paths.indices) {
val types = data.getIntegerArrayListExtra("types") operationFiles.add(
if (types != null && paths != null){ OperationFile.fromExplorerElement(
for (i in paths.indices) { ExplorerElement(File(paths[i]).name, types[i].toShort(), -1, -1, PathUtils.getParentPath(paths[i]))
failedItem = if (types[i] == 0) { //directory )
recursiveImportDirectoryFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath) )
} else { if (types[i] == 0){ //directory
safeImportFileFromOtherVolume(remoteGocryptfsVolume, paths[i], PathUtils.pathJoin(currentDirectoryPath, File(paths[i]).name)) remoteGocryptfsVolume.recursiveMapFiles(paths[i]).forEach {
} operationFiles.add(OperationFile.fromExplorerElement(it))
if (failedItem != null) {
break
}
} }
} }
}
}
} else {
operationFiles.add(
OperationFile.fromExplorerElement(
ExplorerElement(File(path).name, 1, -1, -1, PathUtils.getParentPath(path))
)
)
}
if (operationFiles.size > 0){
checkPathOverwrite(operationFiles, currentDirectoryPath) { items ->
if (items == null) {
remoteGocryptfsVolume.close()
} else { } else {
failedItem = safeImportFileFromOtherVolume(remoteGocryptfsVolume, path, PathUtils.pathJoin(currentDirectoryPath, File(path).name)) fileOperationService.copyElements(items, remoteGocryptfsVolume){ failedItem ->
} runOnUiThread {
if (failedItem == null) { if (failedItem == null){
stopTask { ColoredAlertDialogBuilder(this)
ColoredAlertDialogBuilder(activity) .setTitle(R.string.success_import)
.setTitle(R.string.success_import) .setMessage(R.string.success_import_msg)
.setMessage(R.string.success_import_msg) .setPositiveButton(R.string.ok, null)
.setPositiveButton(R.string.ok, null) .show()
.show() } else {
} ColoredAlertDialogBuilder(this)
} else if (failedItem!!.isNotEmpty()){ .setTitle(R.string.error)
stopTask { .setMessage(getString(R.string.import_failed, failedItem))
ColoredAlertDialogBuilder(activity) .setPositiveButton(R.string.ok, null)
.setTitle(R.string.error) .show()
.setMessage(getString(R.string.import_failed, failedItem)) }
.setPositiveButton(R.string.ok, null) }
.show() remoteGocryptfsVolume.close()
} }
} }
remoteGocryptfsVolume.close()
}
override fun doFinally(activity: AppCompatActivity) {
setCurrentPath(currentDirectoryPath)
} }
} else {
remoteGocryptfsVolume.close()
} }
} }
} }
@ -344,7 +317,7 @@ class ExplorerActivity : BaseExplorerActivity() {
} }
R.id.cut -> { R.id.cut -> {
for (i in explorerAdapter.selectedItems){ for (i in explorerAdapter.selectedItems){
itemsToProcess.add(explorerElements[i]) itemsToProcess.add(OperationFile.fromExplorerElement(explorerElements[i]))
} }
currentItemAction = ItemsActions.MOVE currentItemAction = ItemsActions.MOVE
unselectAll() unselectAll()
@ -352,7 +325,12 @@ class ExplorerActivity : BaseExplorerActivity() {
} }
R.id.copy -> { R.id.copy -> {
for (i in explorerAdapter.selectedItems){ for (i in explorerAdapter.selectedItems){
itemsToProcess.add(explorerElements[i]) itemsToProcess.add(OperationFile.fromExplorerElement(explorerElements[i]))
if (explorerElements[i].isDirectory){
gocryptfsVolume.recursiveMapFiles(explorerElements[i].fullPath).forEach {
itemsToProcess.add(OperationFile.fromExplorerElement(it))
}
}
} }
currentItemAction = ItemsActions.COPY currentItemAction = ItemsActions.COPY
unselectAll() unselectAll()
@ -360,81 +338,45 @@ class ExplorerActivity : BaseExplorerActivity() {
} }
R.id.validate -> { R.id.validate -> {
if (currentItemAction == ItemsActions.COPY){ if (currentItemAction == ItemsActions.COPY){
object : LoadingTask(this, R.string.loading_msg_copy){ checkPathOverwrite(itemsToProcess, currentDirectoryPath){ items ->
override fun doTask(activity: AppCompatActivity) { items?.let {
var failedItem: String? = null fileOperationService.copyElements(it.toMutableList() as ArrayList<OperationFile>){ failedItem ->
Looper.prepare() runOnUiThread {
for (element in itemsToProcess) { if (failedItem != null){
val dstPath = checkPathOverwrite(PathUtils.pathJoin(currentDirectoryPath, element.name), element.isDirectory) ColoredAlertDialogBuilder(this)
failedItem = if (dstPath == null){
""
} else {
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) .setTitle(R.string.error)
.setMessage(getString( .setMessage(getString(R.string.copy_failed, failedItem))
R.string.copy_failed,
failedItem
))
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show() .show()
} } else {
Toast.makeText(this, R.string.copy_success, Toast.LENGTH_SHORT).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()
cancelItemAction() unselectAll()
unselectAll()
setCurrentPath(currentDirectoryPath)
}
} }
} else if (currentItemAction == ItemsActions.MOVE){ } else if (currentItemAction == ItemsActions.MOVE){
object : LoadingTask(this, R.string.loading_msg_move){ mapFileForMove(itemsToProcess, itemsToProcess[0].explorerElement.parentPath)
override fun doTask(activity: AppCompatActivity) { checkPathOverwrite(itemsToProcess, currentDirectoryPath){ items ->
Looper.prepare() items?.let {
val failedItem = moveElements(itemsToProcess, currentDirectoryPath) fileOperationService.moveElements(it.toMutableList() as ArrayList<OperationFile>){ failedItem ->
if (failedItem == null) { runOnUiThread {
stopTask { if (failedItem != null){
ColoredAlertDialogBuilder(activity) ColoredAlertDialogBuilder(this)
.setTitle(getString(R.string.move_success)) .setTitle(R.string.error)
.setMessage(getString(R.string.move_success_msg)) .setMessage(getString(R.string.move_failed, failedItem))
.setPositiveButton(R.string.ok, null) .setPositiveButton(R.string.ok, null)
.show() .show()
} } else {
} else if (failedItem.isNotEmpty()){ Toast.makeText(this, R.string.move_success, Toast.LENGTH_SHORT).show()
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()
cancelItemAction() unselectAll()
unselectAll()
setCurrentPath(currentDirectoryPath)
}
} }
} }
true true
@ -473,6 +415,24 @@ 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
)
)
}
}
}
items.addAll(newItems)
return items
}
private fun cancelItemAction() { private fun cancelItemAction() {
if (currentItemAction != ItemsActions.NONE){ if (currentItemAction != ItemsActions.NONE){
currentItemAction = ItemsActions.NONE currentItemAction = ItemsActions.NONE
@ -489,220 +449,13 @@ class ExplorerActivity : BaseExplorerActivity() {
} }
} }
private fun copyFile(srcPath: String, dstPath: String): Boolean {
var success = true
val originalHandleId = gocryptfsVolume.openReadMode(srcPath)
if (originalHandleId != -1){
val newHandleId = gocryptfsVolume.openWriteMode(dstPath)
if (newHandleId != -1){
var offset: Long = 0
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
var length: Int
while (gocryptfsVolume.readFile(originalHandleId, offset, ioBuffer).also { length = it } > 0) {
val written = gocryptfsVolume.writeFile(newHandleId, offset, ioBuffer, length).toLong()
if (written == length.toLong()) {
offset += written
} else {
success = false
break
}
}
gocryptfsVolume.closeFile(newHandleId)
} else {
success = false
}
gocryptfsVolume.closeFile(originalHandleId)
} else {
success = false
}
return success
}
private fun recursiveCopyDirectory(srcDirectoryPath: String, dstDirectoryPath: String): String? {
val mappedElements = gocryptfsVolume.recursiveMapFiles(srcDirectoryPath)
if (!gocryptfsVolume.pathExists(dstDirectoryPath)){
if (!gocryptfsVolume.mkdir(dstDirectoryPath)) {
return srcDirectoryPath
}
}
for (e in mappedElements) {
val dstPath = checkPathOverwrite(PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, e.fullPath)), e.isDirectory)
if (dstPath == null){
return ""
} else {
if (e.isDirectory) {
if (!gocryptfsVolume.pathExists(dstPath)){
if (!gocryptfsVolume.mkdir(dstPath)){
return e.fullPath
}
}
} else {
if (!copyFile(e.fullPath, dstPath)) {
return e.fullPath
}
}
}
}
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<ExplorerElement>, dstDirectoryPath: String): String? {
for (element in elements){
val dstPath = checkPathOverwrite(PathUtils.pathJoin(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)
if (srcHandleID != -1) {
val dstHandleID = gocryptfsVolume.openWriteMode(dstPath)
if (dstHandleID != -1) {
var length: Int
val ioBuffer = ByteArray(GocryptfsVolume.DefaultBS)
var offset: Long = 0
while (remoteGocryptfsVolume.readFile(srcHandleID, offset, ioBuffer).also { length = it } > 0) {
val written =
gocryptfsVolume.writeFile(dstHandleID, offset, ioBuffer, length).toLong()
if (written == length.toLong()) {
offset += length.toLong()
} else {
success = false
break
}
}
gocryptfsVolume.closeFile(dstHandleID)
}
remoteGocryptfsVolume.closeFile(srcHandleID)
}
return success
}
private fun safeImportFileFromOtherVolume(remoteGocryptfsVolume: GocryptfsVolume, srcPath: String, dstPath: String): String? {
val checkedDstPath = checkPathOverwrite(dstPath, false)
return if (checkedDstPath == null){
""
} else {
if (importFileFromOtherVolume(remoteGocryptfsVolume, srcPath, checkedDstPath)) null else srcPath
}
}
private fun recursiveImportDirectoryFromOtherVolume(remote_gocryptfsVolume: GocryptfsVolume, remote_directory_path: String, outputPath: String): String? {
val mappedElements = remote_gocryptfsVolume.recursiveMapFiles(remote_directory_path)
val dstDirectoryPath = checkPathOverwrite(PathUtils.pathJoin(outputPath, File(remote_directory_path).name), true)
if (dstDirectoryPath == null){
return ""
} else {
if (!gocryptfsVolume.pathExists(dstDirectoryPath)) {
if (!gocryptfsVolume.mkdir(dstDirectoryPath)) {
return remote_directory_path
}
}
for (e in mappedElements) {
val dstPath = checkPathOverwrite(PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(remote_directory_path, e.fullPath)), e.isDirectory)
if (dstPath == null){
return ""
} else {
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
}
}
}
}
}
return null
}
private fun exportFileInto(srcPath: String, treeDocumentFile: DocumentFile): Boolean {
val outputStream = treeDocumentFile.createFile("*/*", File(srcPath).name)?.uri?.let {
contentResolver.openOutputStream(it)
}
return if (outputStream != null){
gocryptfsVolume.exportFile(srcPath, outputStream)
} else {
false
}
}
private fun recursiveExportDirectory(plain_directory_path: String, treeDocumentFile: DocumentFile): String? {
treeDocumentFile.createDirectory(File(plain_directory_path).name)?.let {childTree ->
val explorerElements = gocryptfsVolume.listDir(plain_directory_path)
for (e in explorerElements) {
val fullPath = PathUtils.pathJoin(plain_directory_path, e.name)
if (e.isDirectory) {
val failedItem = recursiveExportDirectory(fullPath, childTree)
failedItem?.let { return it }
} else {
if (!exportFileInto(fullPath, childTree)){
return fullPath
}
}
}
return null
}
return treeDocumentFile.name
}
private fun recursiveRemoveDirectory(plain_directory_path: String): String? {
val explorerElements = gocryptfsVolume.listDir(plain_directory_path)
for (e in explorerElements) {
val fullPath = PathUtils.pathJoin(plain_directory_path, e.name)
if (e.isDirectory) {
val result = recursiveRemoveDirectory(fullPath)
result?.let { return it }
} else {
if (!gocryptfsVolume.removeFile(fullPath)) {
return fullPath
}
}
}
return if (!gocryptfsVolume.rmdir(plain_directory_path)) {
plain_directory_path
} else {
null
}
}
private fun removeSelectedItems() { private fun removeSelectedItems() {
var failedItem: String? = null var failedItem: String? = null
for (i in explorerAdapter.selectedItems) { for (i in explorerAdapter.selectedItems) {
val element = explorerAdapter.getItem(i) val element = explorerAdapter.getItem(i)
val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name) val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name)
if (element.isDirectory) { if (element.isDirectory) {
val result = recursiveRemoveDirectory(fullPath) val result = gocryptfsVolume.recursiveRemoveDirectory(fullPath)
result?.let{ failedItem = it } result?.let{ failedItem = it }
} else { } else {
if (!gocryptfsVolume.removeFile(fullPath)) { if (!gocryptfsVolume.removeFile(fullPath)) {

View File

@ -1,15 +1,11 @@
package sushi.hardcore.droidfs.explorers package sushi.hardcore.droidfs.explorers
import android.content.DialogInterface
import android.content.Intent import android.content.Intent
import android.net.Uri import android.net.Uri
import android.os.Looper
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import androidx.appcompat.app.AppCompatActivity
import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.util.LoadingTask
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
class ExplorerActivityDrop : BaseExplorerActivity() { class ExplorerActivityDrop : BaseExplorerActivity() {
@ -31,54 +27,61 @@ class ExplorerActivityDrop : BaseExplorerActivity() {
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) { return when (item.itemId) {
R.id.validate -> { R.id.validate -> {
object : LoadingTask(this, R.string.loading_msg_import) { val extras = intent.extras
override fun doTask(activity: AppCompatActivity) { val errorMsg: String? = if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)) {
val alertDialog = ColoredAlertDialogBuilder(activity) when (intent.action) {
alertDialog.setCancelable(false) Intent.ACTION_SEND -> {
alertDialog.setPositiveButton(R.string.ok) { _, _ -> finish() } val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
val errorMsg: String? if (uri == null) {
val extras = intent.extras getString(R.string.share_intent_parsing_failed)
if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)){ } else {
Looper.prepare() importFilesFromUris(listOf(uri), ::onImported)
when (intent.action) { null
Intent.ACTION_SEND -> {
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
errorMsg = if (uri == null){
getString(R.string.share_intent_parsing_failed)
} else {
if (importFilesFromUris(listOf(uri), this){ _, _ -> finish() }) null else ""
}
}
Intent.ACTION_SEND_MULTIPLE -> {
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
errorMsg = if (uris != null){
if (importFilesFromUris(uris, this){ _, _ -> finish() }) null else ""
} else {
getString(R.string.share_intent_parsing_failed)
}
}
else -> {
errorMsg = getString(R.string.share_intent_parsing_failed)
}
} }
} else {
errorMsg = getString(R.string.share_intent_parsing_failed)
} }
if (errorMsg == null || errorMsg.isNotEmpty()){ Intent.ACTION_SEND_MULTIPLE -> {
if (errorMsg == null) { val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
alertDialog.setTitle(R.string.success_import) if (uris != null) {
alertDialog.setMessage(R.string.success_import_msg) importFilesFromUris(uris, ::onImported)
} else if (errorMsg.isNotEmpty()) { null
alertDialog.setTitle(R.string.error) } else {
alertDialog.setMessage(errorMsg) getString(R.string.share_intent_parsing_failed)
} }
stopTask { alertDialog.show() }
} }
else -> getString(R.string.share_intent_parsing_failed)
} }
} else {
getString(R.string.share_intent_parsing_failed)
}
errorMsg?.let {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(it)
.setPositiveButton(R.string.ok, null)
.show()
} }
true true
} }
else -> super.onOptionsItemSelected(item) else -> super.onOptionsItemSelected(item)
} }
} }
private fun onImported(failedItem: String?){
if (failedItem == null) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.success_import)
.setMessage(R.string.success_import_msg)
.setCancelable(false)
.setPositiveButton(R.string.ok){_, _ ->
finish()
}
.show()
} else {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(getString(R.string.import_failed, failedItem))
.setPositiveButton(R.string.ok, null)
.show()
}
}
} }

View File

@ -17,6 +17,10 @@ class ExplorerActivityPick : BaseExplorerActivity() {
resultIntent.putExtra("sessionID", gocryptfsVolume.sessionID) resultIntent.putExtra("sessionID", gocryptfsVolume.sessionID)
} }
override fun bindFileOperationService() {
//don't bind
}
override fun onExplorerItemClick(position: Int) { override fun onExplorerItemClick(position: Int) {
val wasSelecting = explorerAdapter.selectedItems.isNotEmpty() val wasSelecting = explorerAdapter.selectedItems.isNotEmpty()
explorerAdapter.onItemClick(position) explorerAdapter.onItemClick(position)

View File

@ -3,7 +3,7 @@ package sushi.hardcore.droidfs.explorers
import sushi.hardcore.droidfs.util.PathUtils import sushi.hardcore.droidfs.util.PathUtils
import java.util.* import java.util.*
class ExplorerElement(val name: String, val elementType: Short, var size: Long, mtime: Long, parentPath: String) { class ExplorerElement(val name: String, val elementType: Short, var size: Long, val mtime: Long, val parentPath: String) {
val mTime = Date((mtime * 1000).toString().toLong()) val mTime = Date((mtime * 1000).toString().toLong())
val fullPath: String = PathUtils.pathJoin(parentPath, name) val fullPath: String = PathUtils.pathJoin(parentPath, name)

View File

@ -0,0 +1,11 @@
package sushi.hardcore.droidfs.file_operations
import sushi.hardcore.droidfs.explorers.ExplorerElement
class OperationFile(val explorerElement: ExplorerElement, var dstPath: String? = null, var overwriteConfirmed: Boolean = false) {
companion object {
fun fromExplorerElement(e: ExplorerElement): OperationFile {
return OperationFile(e, null)
}
}
}

View File

@ -187,4 +187,24 @@ class GocryptfsVolume(var sessionID: Int) {
} }
return result return result
} }
fun recursiveRemoveDirectory(plain_directory_path: String): String? {
val explorerElements = listDir(plain_directory_path)
for (e in explorerElements) {
val fullPath = PathUtils.pathJoin(plain_directory_path, e.name)
if (e.isDirectory) {
val result = recursiveRemoveDirectory(fullPath)
result?.let { return it }
} else {
if (!removeFile(fullPath)) {
return fullPath
}
}
}
return if (!rmdir(plain_directory_path)) {
plain_directory_path
} else {
null
}
}
} }

View File

@ -43,7 +43,17 @@ object PathUtils {
} }
fun getRelativePath(parentPath: String, childPath: String): String { fun getRelativePath(parentPath: String, childPath: String): String {
return childPath.substring(parentPath.length + 1) return when {
parentPath.isEmpty() -> {
childPath
}
parentPath.length == childPath.length -> {
""
}
else -> {
childPath.substring(parentPath.length + 1)
}
}
} }
fun getFilenameFromURI(context: Context, uri: Uri): String? { fun getFilenameFromURI(context: Context, uri: Uri): String? {

View File

@ -133,8 +133,6 @@
<string name="loading_msg_create">Creating volume…</string> <string name="loading_msg_create">Creating volume…</string>
<string name="loading_msg_change_password">Changing password…</string> <string name="loading_msg_change_password">Changing password…</string>
<string name="loading_msg_open">Opening volume…</string> <string name="loading_msg_open">Opening volume…</string>
<string name="loading_msg_import">Importing selected files…</string>
<string name="loading_msg_wipe">Wiping original files…</string>
<string name="loading_msg_export">Exporting files…</string> <string name="loading_msg_export">Exporting files…</string>
<string name="query_cursor_null_error_msg">Unable to access this file</string> <string name="query_cursor_null_error_msg">Unable to access this file</string>
<string name="about">About</string> <string name="about">About</string>
@ -144,9 +142,7 @@
<string name="gitea_summary">The DroidFS repository on the DryCat Gitea instance. Gitea is fully free and self-hosted. Source code, documentation, bug tracker…</string> <string name="gitea_summary">The DroidFS repository on the DryCat Gitea instance. Gitea is fully free and self-hosted. Source code, documentation, bug tracker…</string>
<string name="share">Share</string> <string name="share">Share</string>
<string name="decrypt_files">Export/Decrypt</string> <string name="decrypt_files">Export/Decrypt</string>
<string name="loading_msg_copy">Copying selected items…</string>
<string name="copy_failed">Copy of %s failed.</string> <string name="copy_failed">Copy of %s failed.</string>
<string name="copy_success_msg">The selected items have been successfully copied.</string>
<string name="copy_success">Copy successful !</string> <string name="copy_success">Copy successful !</string>
<string name="fab_dialog_title">Add</string> <string name="fab_dialog_title">Add</string>
<string name="take_photo">Take photo</string> <string name="take_photo">Take photo</string>
@ -159,9 +155,7 @@
<string name="reset_theme_color">Reset theme color</string> <string name="reset_theme_color">Reset theme color</string>
<string name="reset_theme_color_summary">Reset theme color to the default one</string> <string name="reset_theme_color_summary">Reset theme color to the default one</string>
<string name="copy_menu_title">Copy</string> <string name="copy_menu_title">Copy</string>
<string name="loading_msg_move">Moving selected items…</string>
<string name="move_failed">Move of %s failed.</string> <string name="move_failed">Move of %s failed.</string>
<string name="move_success_msg">The selected items have been successfully moved.</string>
<string name="move_success">Move successful !</string> <string name="move_success">Move successful !</string>
<string name="enter_timer_duration">Enter the timer duration (in s)</string> <string name="enter_timer_duration">Enter the timer duration (in s)</string>
<string name="timer_empty_error_msg">Please enter a numeric value</string> <string name="timer_empty_error_msg">Please enter a numeric value</string>

View File

@ -1,6 +1,5 @@
distributionBase=GRADLE_USER_HOME distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
distributionSha256Sum=11657af6356b7587bfb37287b5992e94a9686d5c8a0a1b60b87b9928a2decde5 distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists