FileOperationService
This commit is contained in:
parent
cab0c3bf30
commit
1a6c1d2901
@ -86,6 +86,8 @@
|
||||
android:name=".file_viewers.TextEditor"
|
||||
android:configChanges="screenSize|orientation" />
|
||||
|
||||
<service android:name=".FileOperationService" android:exported="false"/>
|
||||
|
||||
<provider
|
||||
android:name=".provider.RestrictedFileProvider"
|
||||
android:authorities="${applicationId}.temporary_provider"
|
||||
|
194
app/src/main/java/sushi/hardcore/droidfs/FileOperationService.kt
Normal file
194
app/src/main/java/sushi/hardcore/droidfs/FileOperationService.kt
Normal 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()
|
||||
}
|
||||
}
|
@ -1,12 +1,12 @@
|
||||
package sushi.hardcore.droidfs.explorers
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.ServiceConnection
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import android.os.Handler
|
||||
import android.os.Looper
|
||||
import android.os.Message
|
||||
import android.os.IBinder
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
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.isText
|
||||
import sushi.hardcore.droidfs.ConstValues.Companion.isVideo
|
||||
import sushi.hardcore.droidfs.FileOperationService
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.adapters.DialogSingleChoiceAdapter
|
||||
import sushi.hardcore.droidfs.adapters.ExplorerElementAdapter
|
||||
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.ImageViewer
|
||||
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.util.ExternalProvider
|
||||
import sushi.hardcore.droidfs.util.GocryptfsVolume
|
||||
import sushi.hardcore.droidfs.util.LoadingTask
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
|
||||
import java.io.File
|
||||
import java.io.FileNotFoundException
|
||||
|
||||
open class BaseExplorerActivity : BaseActivity() {
|
||||
private lateinit var sortOrderEntries: Array<String>
|
||||
@ -55,6 +54,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
field = value
|
||||
explorerViewModel.currentDirectoryPath = value
|
||||
}
|
||||
protected lateinit var fileOperationService: FileOperationService
|
||||
protected lateinit var explorerElements: MutableList<ExplorerElement>
|
||||
protected lateinit var explorerAdapter: ExplorerElementAdapter
|
||||
private var isCreating = true
|
||||
@ -87,6 +87,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
refresher.isRefreshing = false
|
||||
}
|
||||
bindFileOperationService()
|
||||
}
|
||||
|
||||
class ExplorerViewModel: ViewModel() {
|
||||
@ -97,6 +98,21 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
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 = ""){
|
||||
val intent = Intent(this, cls)
|
||||
intent.putExtra("path", filePath)
|
||||
@ -274,97 +290,97 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
dialog.show()
|
||||
}
|
||||
|
||||
protected fun checkPathOverwrite(path: String, isDirectory: Boolean): String? {
|
||||
var outputPath: String? = null
|
||||
if (gocryptfsVolume.pathExists(path)){
|
||||
val fileName = File(path).name
|
||||
val handler = Handler{ msg ->
|
||||
outputPath = msg.obj as String?
|
||||
throw RuntimeException()
|
||||
protected fun checkPathOverwrite(items: ArrayList<OperationFile>, dstDirectoryPath: String, callback: (ArrayList<OperationFile>?) -> Unit) {
|
||||
val srcDirectoryPath = items[0].explorerElement.parentPath
|
||||
var ready = true
|
||||
for (i in 0 until items.size) {
|
||||
val testDstPath: String
|
||||
if (items[i].dstPath == null){
|
||||
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 {
|
||||
val dialog = ColoredAlertDialogBuilder(this)
|
||||
if (!ready){
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.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) { _, _ ->
|
||||
val dialogEditTextView = layoutInflater.inflate(R.layout.dialog_edit_text, null)
|
||||
val dialogEditText = dialogEditTextView.findViewById<EditText>(R.id.dialog_edit_text)
|
||||
dialogEditText.setText(fileName)
|
||||
dialogEditText.setText(items[i].explorerElement.name)
|
||||
dialogEditText.selectAll()
|
||||
val dialog = ColoredAlertDialogBuilder(this)
|
||||
.setView(dialogEditTextView)
|
||||
.setTitle(R.string.enter_new_name)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
handler.sendMessage(Message().apply { obj = checkPathOverwrite(PathUtils.pathJoin(PathUtils.getParentPath(path), dialogEditText.text.toString()), isDirectory) })
|
||||
}
|
||||
.setNegativeButton(R.string.cancel) { _, _ -> handler.sendMessage(Message().apply { obj = null }) }
|
||||
.create()
|
||||
.setView(dialogEditTextView)
|
||||
.setTitle(R.string.enter_new_name)
|
||||
.setPositiveButton(R.string.ok) { _, _ ->
|
||||
items[i].dstPath = PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, items[i].explorerElement.parentPath), dialogEditText.text.toString())
|
||||
checkPathOverwrite(items, dstDirectoryPath, callback)
|
||||
}
|
||||
.setOnCancelListener{
|
||||
callback(null)
|
||||
}
|
||||
.create()
|
||||
dialogEditText.setOnEditorActionListener { _, _, _ ->
|
||||
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
|
||||
}
|
||||
dialog.setOnCancelListener { handler.sendMessage(Message().apply { obj = null }) }
|
||||
dialog.window?.setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_VISIBLE)
|
||||
dialog.show()
|
||||
}
|
||||
.setPositiveButton(R.string.yes) { _, _ -> handler.sendMessage(Message().apply { obj = path }) }
|
||||
.create()
|
||||
dialog.setOnCancelListener { handler.sendMessage(Message().apply { obj = null }) }
|
||||
dialog.show()
|
||||
.setOnCancelListener{
|
||||
callback(null)
|
||||
}
|
||||
.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 {
|
||||
var success = false
|
||||
protected fun importFilesFromUris(uris: List<Uri>, callback: (String?) -> Unit) {
|
||||
val items = ArrayList<OperationFile>()
|
||||
for (uri in uris) {
|
||||
val fileName = PathUtils.getFilenameFromURI(task.activity, uri)
|
||||
if (fileName == null){
|
||||
task.stopTask {
|
||||
ColoredAlertDialogBuilder(task.activity)
|
||||
val fileName = PathUtils.getFilenameFromURI(this, uri)
|
||||
if (fileName == null) {
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.error_retrieving_filename, uri))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
success = false
|
||||
items.clear()
|
||||
break
|
||||
} else {
|
||||
val dstPath = checkPathOverwrite(PathUtils.pathJoin(currentDirectoryPath, fileName), false)
|
||||
if (dstPath == null){
|
||||
break
|
||||
} else {
|
||||
var message: String? = null
|
||||
try {
|
||||
success = gocryptfsVolume.importFile(task.activity, uri, dstPath)
|
||||
} catch (e: FileNotFoundException){
|
||||
message = if (e.message != null){
|
||||
e.message!!+"\n"
|
||||
} else {
|
||||
""
|
||||
items.add(OperationFile.fromExplorerElement(ExplorerElement(fileName, 1, -1, -1, currentDirectoryPath)))
|
||||
}
|
||||
}
|
||||
if (items.size > 0) {
|
||||
checkPathOverwrite(items, currentDirectoryPath) { checkedItems ->
|
||||
checkedItems?.let {
|
||||
fileOperationService.importFilesFromUris(checkedItems, uris){ failedItem ->
|
||||
runOnUiThread {
|
||||
callback(failedItem)
|
||||
}
|
||||
}
|
||||
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){
|
||||
|
@ -3,20 +3,20 @@ package sushi.hardcore.droidfs.explorers
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Looper
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import android.view.WindowManager
|
||||
import android.widget.EditText
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import sushi.hardcore.droidfs.CameraActivity
|
||||
import sushi.hardcore.droidfs.OpenActivity
|
||||
import sushi.hardcore.droidfs.R
|
||||
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 java.io.File
|
||||
|
||||
@ -30,7 +30,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
private var usf_decrypt = false
|
||||
private var usf_share = false
|
||||
private var currentItemAction = ItemsActions.NONE
|
||||
private val itemsToProcess = ArrayList<ExplorerElement>()
|
||||
private val itemsToProcess = ArrayList<OperationFile>()
|
||||
override fun init() {
|
||||
setContentView(R.layout.activity_explorer)
|
||||
usf_decrypt = sharedPrefs.getBoolean("usf_decrypt", false)
|
||||
@ -47,19 +47,17 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
if (fileName.isEmpty()) {
|
||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||
} else {
|
||||
checkPathOverwrite(PathUtils.pathJoin(currentDirectoryPath, fileName), false)?.let {
|
||||
val handleID = gocryptfsVolume.openWriteMode(it)
|
||||
if (handleID == -1) {
|
||||
ColoredAlertDialogBuilder(this)
|
||||
val handleID = gocryptfsVolume.openWriteMode(fileName) //don't check overwrite because openWriteMode open in read-write (doesn't erase content)
|
||||
if (handleID == -1) {
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(R.string.file_creation_failed)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
} else {
|
||||
gocryptfsVolume.closeFile(handleID)
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
} else {
|
||||
gocryptfsVolume.closeFile(handleID)
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
invalidateOptionsMenu()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -138,162 +136,137 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
super.onActivityResult(requestCode, resultCode, data)
|
||||
if (requestCode == PICK_FILES_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
object : LoadingTask(this, R.string.loading_msg_import){
|
||||
override fun doTask(activity: AppCompatActivity) {
|
||||
val uris: MutableList<Uri> = ArrayList()
|
||||
val singleUri = data.data
|
||||
if (singleUri == null) { //multiples choices
|
||||
val clipData = data.clipData
|
||||
if (clipData != null){
|
||||
for (i in 0 until clipData.itemCount) {
|
||||
uris.add(clipData.getItemAt(i).uri)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
uris.add(singleUri)
|
||||
val uris: MutableList<Uri> = ArrayList()
|
||||
val singleUri = data.data
|
||||
if (singleUri == null) { //multiples choices
|
||||
val clipData = data.clipData
|
||||
if (clipData != null){
|
||||
for (i in 0 until clipData.itemCount) {
|
||||
uris.add(clipData.getItemAt(i).uri)
|
||||
}
|
||||
Looper.prepare()
|
||||
if (importFilesFromUris(uris, this)) {
|
||||
stopTask {
|
||||
ColoredAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.success_import)
|
||||
.setMessage("""
|
||||
}
|
||||
} else {
|
||||
uris.add(singleUri)
|
||||
}
|
||||
importFilesFromUris(uris){ failedItem ->
|
||||
if (failedItem == null){
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.success_import)
|
||||
.setMessage("""
|
||||
${getString(R.string.success_import_msg)}
|
||||
${getString(R.string.ask_for_wipe)}
|
||||
""".trimIndent())
|
||||
.setPositiveButton(R.string.yes) { _, _ ->
|
||||
object : LoadingTask(activity, R.string.loading_msg_wipe){
|
||||
override fun doTask(activity: AppCompatActivity) {
|
||||
var success = true
|
||||
for (uri in uris) {
|
||||
val errorMsg = Wiper.wipe(activity, uri)
|
||||
if (errorMsg != null) {
|
||||
stopTask {
|
||||
ColoredAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.wipe_failed, errorMsg))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
success = false
|
||||
break
|
||||
}
|
||||
}
|
||||
if (success) {
|
||||
stopTask {
|
||||
ColoredAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.wipe_successful)
|
||||
.setMessage(R.string.wipe_success_msg)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
.setPositiveButton(R.string.yes) { _, _ ->
|
||||
fileOperationService.wipeUris(uris) { errorMsg ->
|
||||
runOnUiThread {
|
||||
if (errorMsg == null){
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.wipe_successful)
|
||||
.setMessage(R.string.wipe_success_msg)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
} else {
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.wipe_failed, errorMsg))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun doFinally(activity: AppCompatActivity){
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
}
|
||||
.setNegativeButton(R.string.no, null)
|
||||
.show()
|
||||
} else {
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.import_failed, failedItem))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
object : LoadingTask(this, R.string.loading_msg_export){
|
||||
override fun doTask(activity: AppCompatActivity) {
|
||||
data.data?.let {uri ->
|
||||
contentResolver.takePersistableUriPermission(uri, Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
|
||||
DocumentFile.fromTreeUri(activity, uri)?.let { treeDocumentFile ->
|
||||
var failedItem: String? = null
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
val element = explorerAdapter.getItem(i)
|
||||
val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name)
|
||||
failedItem = if (element.isDirectory) {
|
||||
recursiveExportDirectory(fullPath, treeDocumentFile)
|
||||
} else {
|
||||
if (exportFileInto(fullPath, treeDocumentFile)) null else fullPath
|
||||
}
|
||||
if (failedItem != null) {
|
||||
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()
|
||||
}
|
||||
}
|
||||
data.data?.let { uri ->
|
||||
fileOperationService.exportFiles(uri, explorerAdapter.selectedItems.map { i -> explorerElements[i] }){ failedItem ->
|
||||
runOnUiThread {
|
||||
if (failedItem == null){
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.success_export)
|
||||
.setMessage(R.string.success_export_msg)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
} else {
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.export_failed, failedItem))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun doFinally(activity: AppCompatActivity) {
|
||||
unselectAll()
|
||||
}
|
||||
unselectAll()
|
||||
}
|
||||
}
|
||||
} else if (requestCode == PICK_OTHER_VOLUME_ITEMS_REQUEST_CODE) {
|
||||
if (resultCode == Activity.RESULT_OK && data != null) {
|
||||
object : LoadingTask(this, R.string.loading_msg_import){
|
||||
override fun doTask(activity: AppCompatActivity) {
|
||||
val remoteSessionID = data.getIntExtra("sessionID", -1)
|
||||
val remoteGocryptfsVolume = GocryptfsVolume(remoteSessionID)
|
||||
val path = data.getStringExtra("path")
|
||||
var failedItem: String? = null
|
||||
Looper.prepare()
|
||||
if (path == null) {
|
||||
val paths = data.getStringArrayListExtra("paths")
|
||||
val types = data.getIntegerArrayListExtra("types")
|
||||
if (types != null && paths != null){
|
||||
for (i in paths.indices) {
|
||||
failedItem = if (types[i] == 0) { //directory
|
||||
recursiveImportDirectoryFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)
|
||||
} else {
|
||||
safeImportFileFromOtherVolume(remoteGocryptfsVolume, paths[i], PathUtils.pathJoin(currentDirectoryPath, File(paths[i]).name))
|
||||
}
|
||||
if (failedItem != null) {
|
||||
break
|
||||
}
|
||||
val remoteSessionID = data.getIntExtra("sessionID", -1)
|
||||
val remoteGocryptfsVolume = GocryptfsVolume(remoteSessionID)
|
||||
val path = data.getStringExtra("path")
|
||||
val operationFiles = ArrayList<OperationFile>()
|
||||
if (path == null){ //multiples elements
|
||||
val paths = data.getStringArrayListExtra("paths")
|
||||
val types = data.getIntegerArrayListExtra("types")
|
||||
if (types != null && paths != null){
|
||||
for (i in paths.indices) {
|
||||
operationFiles.add(
|
||||
OperationFile.fromExplorerElement(
|
||||
ExplorerElement(File(paths[i]).name, types[i].toShort(), -1, -1, PathUtils.getParentPath(paths[i]))
|
||||
)
|
||||
)
|
||||
if (types[i] == 0){ //directory
|
||||
remoteGocryptfsVolume.recursiveMapFiles(paths[i]).forEach {
|
||||
operationFiles.add(OperationFile.fromExplorerElement(it))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} 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 {
|
||||
failedItem = safeImportFileFromOtherVolume(remoteGocryptfsVolume, path, PathUtils.pathJoin(currentDirectoryPath, File(path).name))
|
||||
}
|
||||
if (failedItem == null) {
|
||||
stopTask {
|
||||
ColoredAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.success_import)
|
||||
.setMessage(R.string.success_import_msg)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
} else if (failedItem!!.isNotEmpty()){
|
||||
stopTask {
|
||||
ColoredAlertDialogBuilder(activity)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.import_failed, failedItem))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
fileOperationService.copyElements(items, remoteGocryptfsVolume){ failedItem ->
|
||||
runOnUiThread {
|
||||
if (failedItem == null){
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.success_import)
|
||||
.setMessage(R.string.success_import_msg)
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
} else {
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.error)
|
||||
.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 -> {
|
||||
for (i in explorerAdapter.selectedItems){
|
||||
itemsToProcess.add(explorerElements[i])
|
||||
itemsToProcess.add(OperationFile.fromExplorerElement(explorerElements[i]))
|
||||
}
|
||||
currentItemAction = ItemsActions.MOVE
|
||||
unselectAll()
|
||||
@ -352,7 +325,12 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
}
|
||||
R.id.copy -> {
|
||||
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
|
||||
unselectAll()
|
||||
@ -360,81 +338,45 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
}
|
||||
R.id.validate -> {
|
||||
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.pathJoin(currentDirectoryPath, element.name), element.isDirectory)
|
||||
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)
|
||||
checkPathOverwrite(itemsToProcess, currentDirectoryPath){ items ->
|
||||
items?.let {
|
||||
fileOperationService.copyElements(it.toMutableList() as ArrayList<OperationFile>){ failedItem ->
|
||||
runOnUiThread {
|
||||
if (failedItem != null){
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(
|
||||
R.string.copy_failed,
|
||||
failedItem
|
||||
))
|
||||
.setMessage(getString(R.string.copy_failed, failedItem))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.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()
|
||||
unselectAll()
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
}
|
||||
cancelItemAction()
|
||||
unselectAll()
|
||||
}
|
||||
} 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()
|
||||
mapFileForMove(itemsToProcess, itemsToProcess[0].explorerElement.parentPath)
|
||||
checkPathOverwrite(itemsToProcess, currentDirectoryPath){ items ->
|
||||
items?.let {
|
||||
fileOperationService.moveElements(it.toMutableList() as ArrayList<OperationFile>){ failedItem ->
|
||||
runOnUiThread {
|
||||
if (failedItem != null){
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.move_failed, failedItem))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
} else {
|
||||
Toast.makeText(this, R.string.move_success, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
override fun doFinally(activity: AppCompatActivity) {
|
||||
cancelItemAction()
|
||||
unselectAll()
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
}
|
||||
cancelItemAction()
|
||||
unselectAll()
|
||||
}
|
||||
}
|
||||
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() {
|
||||
if (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() {
|
||||
var failedItem: String? = null
|
||||
for (i in explorerAdapter.selectedItems) {
|
||||
val element = explorerAdapter.getItem(i)
|
||||
val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name)
|
||||
if (element.isDirectory) {
|
||||
val result = recursiveRemoveDirectory(fullPath)
|
||||
val result = gocryptfsVolume.recursiveRemoveDirectory(fullPath)
|
||||
result?.let{ failedItem = it }
|
||||
} else {
|
||||
if (!gocryptfsVolume.removeFile(fullPath)) {
|
||||
|
@ -1,15 +1,11 @@
|
||||
package sushi.hardcore.droidfs.explorers
|
||||
|
||||
import android.content.DialogInterface
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.os.Looper
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.View
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import sushi.hardcore.droidfs.R
|
||||
import sushi.hardcore.droidfs.util.LoadingTask
|
||||
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
|
||||
|
||||
class ExplorerActivityDrop : BaseExplorerActivity() {
|
||||
@ -31,54 +27,61 @@ class ExplorerActivityDrop : BaseExplorerActivity() {
|
||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||
return when (item.itemId) {
|
||||
R.id.validate -> {
|
||||
object : LoadingTask(this, R.string.loading_msg_import) {
|
||||
override fun doTask(activity: AppCompatActivity) {
|
||||
val alertDialog = ColoredAlertDialogBuilder(activity)
|
||||
alertDialog.setCancelable(false)
|
||||
alertDialog.setPositiveButton(R.string.ok) { _, _ -> finish() }
|
||||
val errorMsg: String?
|
||||
val extras = intent.extras
|
||||
if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)){
|
||||
Looper.prepare()
|
||||
when (intent.action) {
|
||||
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)
|
||||
}
|
||||
val extras = intent.extras
|
||||
val errorMsg: String? = if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)) {
|
||||
when (intent.action) {
|
||||
Intent.ACTION_SEND -> {
|
||||
val uri = intent.getParcelableExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
if (uri == null) {
|
||||
getString(R.string.share_intent_parsing_failed)
|
||||
} else {
|
||||
importFilesFromUris(listOf(uri), ::onImported)
|
||||
null
|
||||
}
|
||||
} else {
|
||||
errorMsg = getString(R.string.share_intent_parsing_failed)
|
||||
}
|
||||
if (errorMsg == null || errorMsg.isNotEmpty()){
|
||||
if (errorMsg == null) {
|
||||
alertDialog.setTitle(R.string.success_import)
|
||||
alertDialog.setMessage(R.string.success_import_msg)
|
||||
} else if (errorMsg.isNotEmpty()) {
|
||||
alertDialog.setTitle(R.string.error)
|
||||
alertDialog.setMessage(errorMsg)
|
||||
Intent.ACTION_SEND_MULTIPLE -> {
|
||||
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
|
||||
if (uris != null) {
|
||||
importFilesFromUris(uris, ::onImported)
|
||||
null
|
||||
} else {
|
||||
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
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
}
|
@ -17,6 +17,10 @@ class ExplorerActivityPick : BaseExplorerActivity() {
|
||||
resultIntent.putExtra("sessionID", gocryptfsVolume.sessionID)
|
||||
}
|
||||
|
||||
override fun bindFileOperationService() {
|
||||
//don't bind
|
||||
}
|
||||
|
||||
override fun onExplorerItemClick(position: Int) {
|
||||
val wasSelecting = explorerAdapter.selectedItems.isNotEmpty()
|
||||
explorerAdapter.onItemClick(position)
|
||||
|
@ -3,7 +3,7 @@ package sushi.hardcore.droidfs.explorers
|
||||
import sushi.hardcore.droidfs.util.PathUtils
|
||||
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 fullPath: String = PathUtils.pathJoin(parentPath, name)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -187,4 +187,24 @@ class GocryptfsVolume(var sessionID: Int) {
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
@ -43,7 +43,17 @@ object PathUtils {
|
||||
}
|
||||
|
||||
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? {
|
||||
|
@ -133,8 +133,6 @@
|
||||
<string name="loading_msg_create">Creating volume…</string>
|
||||
<string name="loading_msg_change_password">Changing password…</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="query_cursor_null_error_msg">Unable to access this file</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="share">Share</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_success_msg">The selected items have been successfully copied.</string>
|
||||
<string name="copy_success">Copy successful !</string>
|
||||
<string name="fab_dialog_title">Add</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_summary">Reset theme color to the default one</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_success_msg">The selected items have been successfully moved.</string>
|
||||
<string name="move_success">Move successful !</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>
|
||||
|
3
gradle/wrapper/gradle-wrapper.properties
vendored
3
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,6 +1,5 @@
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
distributionSha256Sum=11657af6356b7587bfb37287b5992e94a9686d5c8a0a1b60b87b9928a2decde5
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.6.1-all.zip
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
|
Loading…
Reference in New Issue
Block a user