Adding an 'Import/Encrypt Folder' button
This commit is contained in:
parent
60ba9531be
commit
5cc9abfd76
@ -17,6 +17,7 @@ import android.widget.EditText
|
||||
import android.widget.ListView
|
||||
import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.documentfile.provider.DocumentFile
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
|
||||
@ -254,7 +255,7 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
synchronized(this) {
|
||||
explorerElements.add(
|
||||
0,
|
||||
ExplorerElement("..", (-1).toShort(), -1, -1, currentDirectoryPath)
|
||||
ExplorerElement("..", (-1).toShort(), parentPath = currentDirectoryPath)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -408,13 +409,13 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
items.clear()
|
||||
break
|
||||
} else {
|
||||
items.add(OperationFile.fromExplorerElement(ExplorerElement(fileName, 1, -1, -1, currentDirectoryPath)))
|
||||
items.add(OperationFile.fromExplorerElement(ExplorerElement(fileName, 1, parentPath = currentDirectoryPath)))
|
||||
}
|
||||
}
|
||||
if (items.size > 0) {
|
||||
checkPathOverwrite(items, currentDirectoryPath) { checkedItems ->
|
||||
checkedItems?.let {
|
||||
fileOperationService.importFilesFromUris(checkedItems, uris){ failedItem ->
|
||||
fileOperationService.importFilesFromUris(checkedItems.map { it.dstPath!! }, uris){ failedItem ->
|
||||
runOnUiThread {
|
||||
callback(failedItem)
|
||||
}
|
||||
@ -424,6 +425,20 @@ open class BaseExplorerActivity : BaseActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
fun importDirectory(sourceUri: Uri, callback: (String?, List<Uri>) -> Unit) {
|
||||
val tree = DocumentFile.fromTreeUri(this, sourceUri)!! //non-null after Lollipop
|
||||
val operation = OperationFile.fromExplorerElement(ExplorerElement(tree.name!!, 0, parentPath = currentDirectoryPath))
|
||||
checkPathOverwrite(arrayListOf(operation), currentDirectoryPath) { checkedOperation ->
|
||||
checkedOperation?.let {
|
||||
fileOperationService.importDirectory(checkedOperation[0].dstPath!!, tree) { failedItem, uris ->
|
||||
runOnUiThread {
|
||||
callback(failedItem, uris)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun rename(old_name: String, new_name: String){
|
||||
if (new_name.isEmpty()) {
|
||||
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
|
||||
|
@ -2,6 +2,7 @@ package sushi.hardcore.droidfs.explorers
|
||||
|
||||
import android.app.Activity
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.view.Menu
|
||||
import android.view.MenuItem
|
||||
import android.view.WindowManager
|
||||
@ -44,7 +45,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
for (i in paths.indices) {
|
||||
operationFiles.add(
|
||||
OperationFile.fromExplorerElement(
|
||||
ExplorerElement(File(paths[i]).name, types[i].toShort(), -1, -1, PathUtils.getParentPath(paths[i]))
|
||||
ExplorerElement(File(paths[i]).name, types[i].toShort(), parentPath = PathUtils.getParentPath(paths[i]))
|
||||
)
|
||||
)
|
||||
if (types[i] == 0){ //directory
|
||||
@ -57,7 +58,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
} else {
|
||||
operationFiles.add(
|
||||
OperationFile.fromExplorerElement(
|
||||
ExplorerElement(File(path).name, 1, -1, -1, PathUtils.getParentPath(path))
|
||||
ExplorerElement(File(path).name, 1, parentPath = PathUtils.getParentPath(path))
|
||||
)
|
||||
)
|
||||
}
|
||||
@ -92,42 +93,11 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
private val pickFiles = registerForActivityResult(ActivityResultContracts.OpenMultipleDocuments()) { uris ->
|
||||
if (uris != null) {
|
||||
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) { _, _ ->
|
||||
fileOperationService.wipeUris(uris) { errorMsg ->
|
||||
runOnUiThread {
|
||||
if (errorMsg == null){
|
||||
Toast.makeText(this, R.string.wipe_successful, Toast.LENGTH_SHORT).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()
|
||||
} else {
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.import_failed, failedItem))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
onImportComplete(failedItem, uris)
|
||||
}
|
||||
}
|
||||
}
|
||||
private val pickDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
|
||||
private val pickExportDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { uri ->
|
||||
if (uri != null) {
|
||||
fileOperationService.exportFiles(uri, explorerAdapter.selectedItems.map { i -> explorerElements[i] }){ failedItem ->
|
||||
runOnUiThread {
|
||||
@ -145,6 +115,46 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
}
|
||||
unselectAll()
|
||||
}
|
||||
private val pickImportDirectory = registerForActivityResult(ActivityResultContracts.OpenDocumentTree()) { rootUri ->
|
||||
rootUri?.let {
|
||||
importDirectory(it, ::onImportComplete)
|
||||
}
|
||||
}
|
||||
|
||||
private fun onImportComplete(failedItem: String?, uris: List<Uri>) {
|
||||
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) { _, _ ->
|
||||
fileOperationService.wipeUris(uris) { errorMsg ->
|
||||
runOnUiThread {
|
||||
if (errorMsg == null){
|
||||
Toast.makeText(this, R.string.wipe_successful, Toast.LENGTH_SHORT).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()
|
||||
} else {
|
||||
ColoredAlertDialogBuilder(this)
|
||||
.setTitle(R.string.error)
|
||||
.setMessage(getString(R.string.import_failed, failedItem))
|
||||
.setPositiveButton(R.string.ok, null)
|
||||
.show()
|
||||
}
|
||||
setCurrentPath(currentDirectoryPath)
|
||||
}
|
||||
|
||||
override fun init() {
|
||||
binding = ActivityExplorerBinding.inflate(layoutInflater)
|
||||
@ -157,8 +167,9 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
adapter.items = listOf(
|
||||
listOf("importFromOtherVolumes", R.string.import_from_other_volume, R.drawable.icon_transfert),
|
||||
listOf("importFiles", R.string.import_files, R.drawable.icon_encrypt),
|
||||
listOf("importFolder", R.string.import_folder, R.drawable.icon_import_folder),
|
||||
listOf("createFile", R.string.new_file, R.drawable.icon_file_unknown),
|
||||
listOf("createFolder", R.string.mkdir, R.drawable.icon_folder),
|
||||
listOf("createFolder", R.string.mkdir, R.drawable.icon_create_new_folder),
|
||||
listOf("takePhoto", R.string.take_photo, R.drawable.icon_camera)
|
||||
)
|
||||
ColoredAlertDialogBuilder(this)
|
||||
@ -175,6 +186,10 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
isStartingActivity = true
|
||||
pickFiles.launch(arrayOf("*/*"))
|
||||
}
|
||||
"importFolder" -> {
|
||||
isStartingActivity = true
|
||||
pickImportDirectory.launch(null)
|
||||
}
|
||||
"createFile" -> {
|
||||
val dialogEditTextView = layoutInflater.inflate(R.layout.dialog_edit_text, null)
|
||||
val dialogEditText = dialogEditTextView.findViewById<EditText>(R.id.dialog_edit_text)
|
||||
@ -381,7 +396,7 @@ class ExplorerActivity : BaseExplorerActivity() {
|
||||
}
|
||||
R.id.decrypt -> {
|
||||
isStartingActivity = true
|
||||
pickDirectory.launch(null)
|
||||
pickExportDirectory.launch(null)
|
||||
true
|
||||
}
|
||||
else -> super.onOptionsItemSelected(item)
|
||||
|
@ -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, val parentPath: String) {
|
||||
class ExplorerElement(val name: String, val elementType: Short, var size: Long = -1, mTime: Long = -1, val parentPath: String) {
|
||||
val mTime = Date((mTime * 1000).toString().toLong())
|
||||
val fullPath: String = PathUtils.pathJoin(parentPath, name)
|
||||
|
||||
|
@ -38,7 +38,7 @@ class FileOperationService : Service() {
|
||||
return binder
|
||||
}
|
||||
|
||||
private fun showNotification(message: Int, total: Int): FileOperationNotification {
|
||||
private fun showNotification(message: Int, total: Int?): FileOperationNotification {
|
||||
++lastNotificationId
|
||||
if (!::notificationManager.isInitialized){
|
||||
notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
|
||||
@ -73,11 +73,18 @@ class FileOperationService : Service() {
|
||||
}
|
||||
notificationBuilder
|
||||
.setContentTitle(getString(message))
|
||||
.setContentText("0/$total")
|
||||
.setSmallIcon(R.mipmap.icon_launcher)
|
||||
.setOngoing(true)
|
||||
.setProgress(total, 0, false)
|
||||
.addAction(notificationAction.build())
|
||||
if (total != null) {
|
||||
notificationBuilder
|
||||
.setContentText("0/$total")
|
||||
.setProgress(total, 0, false)
|
||||
} else {
|
||||
notificationBuilder
|
||||
.setContentText(getString(R.string.discovering_files))
|
||||
.setProgress(0, 0, true)
|
||||
}
|
||||
notifications[lastNotificationId] = false
|
||||
notificationManager.notify(lastNotificationId, notificationBuilder.build())
|
||||
return FileOperationNotification(notificationBuilder, lastNotificationId)
|
||||
@ -198,33 +205,102 @@ class FileOperationService : Service() {
|
||||
}.start()
|
||||
}
|
||||
|
||||
fun importFilesFromUris(items: ArrayList<OperationFile>, uris: List<Uri>, callback: (String?) -> Unit){
|
||||
private fun importFilesFromUris(dstPaths: List<String>, uris: List<Uri>, reuseNotification: FileOperationNotification? = null, callback: (String?) -> Unit){
|
||||
val notification = reuseNotification ?: showNotification(R.string.file_op_import_msg, dstPaths.size)
|
||||
var failedIndex = -1
|
||||
for (i in dstPaths.indices) {
|
||||
if (notifications[notification.notificationId]!!){
|
||||
cancelNotification(notification)
|
||||
return
|
||||
}
|
||||
try {
|
||||
if (!gocryptfsVolume.importFile(this, uris[i], dstPaths[i])) {
|
||||
failedIndex = i
|
||||
}
|
||||
} catch (e: FileNotFoundException){
|
||||
failedIndex = i
|
||||
}
|
||||
if (failedIndex == -1) {
|
||||
updateNotificationProgress(notification, i, dstPaths.size)
|
||||
} else {
|
||||
cancelNotification(notification)
|
||||
callback(uris[failedIndex].toString())
|
||||
break
|
||||
}
|
||||
}
|
||||
if (failedIndex == -1){
|
||||
cancelNotification(notification)
|
||||
callback(null)
|
||||
}
|
||||
}
|
||||
|
||||
fun importFilesFromUris(dstPaths: List<String>, uris: List<Uri>, callback: (String?) -> Unit) {
|
||||
Thread {
|
||||
val notification = showNotification(R.string.file_op_import_msg, items.size)
|
||||
var failedIndex = -1
|
||||
for (i in 0 until items.size) {
|
||||
if (notifications[notification.notificationId]!!){
|
||||
importFilesFromUris(dstPaths, uris, null, callback)
|
||||
}.start()
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the content of an unencrypted directory to prepare its import
|
||||
*
|
||||
* Contents of dstFiles and srcUris, at the same index, will match each other
|
||||
*
|
||||
* @return false if cancelled early, true otherwise.
|
||||
*/
|
||||
private fun recursiveMapDirectoryForImport(
|
||||
rootSrcDir: DocumentFile,
|
||||
rootDstPath: String,
|
||||
dstFiles: ArrayList<String>,
|
||||
srcUris: ArrayList<Uri>,
|
||||
dstDirs: ArrayList<String>,
|
||||
notification: FileOperationNotification
|
||||
): Boolean {
|
||||
dstDirs.add(rootDstPath)
|
||||
for (child in rootSrcDir.listFiles()) {
|
||||
if (notifications[notification.notificationId]!!) {
|
||||
cancelNotification(notification)
|
||||
return false
|
||||
}
|
||||
child.name?.let { name ->
|
||||
val subPath = PathUtils.pathJoin(rootDstPath, name)
|
||||
if (child.isDirectory) {
|
||||
if (!recursiveMapDirectoryForImport(child, subPath, dstFiles, srcUris, dstDirs, notification)) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
else if (child.isFile) {
|
||||
srcUris.add(child.uri)
|
||||
dstFiles.add(subPath)
|
||||
}
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
fun importDirectory(rootDstPath: String, rootSrcDir: DocumentFile, callback: (String?, List<Uri>) -> Unit) {
|
||||
Thread {
|
||||
val notification = showNotification(R.string.file_op_import_msg, null)
|
||||
|
||||
val dstFiles = arrayListOf<String>()
|
||||
val srcUris = arrayListOf<Uri>()
|
||||
val dstDirs = arrayListOf<String>()
|
||||
if (!recursiveMapDirectoryForImport(rootSrcDir, rootDstPath, dstFiles, srcUris, dstDirs, notification)) {
|
||||
return@Thread
|
||||
}
|
||||
|
||||
updateNotificationProgress(notification, 0, dstDirs.size)
|
||||
|
||||
// create destination folders so the new files can use them
|
||||
for (mkdir in dstDirs) {
|
||||
if (notifications[notification.notificationId]!!) {
|
||||
cancelNotification(notification)
|
||||
return@Thread
|
||||
}
|
||||
try {
|
||||
if (!gocryptfsVolume.importFile(this, uris[i], items[i].dstPath!!)){
|
||||
failedIndex = i
|
||||
}
|
||||
} catch (e: FileNotFoundException){
|
||||
failedIndex = i
|
||||
}
|
||||
if (failedIndex == -1) {
|
||||
updateNotificationProgress(notification, i, items.size)
|
||||
} else {
|
||||
cancelNotification(notification)
|
||||
callback(uris[failedIndex].toString())
|
||||
break
|
||||
}
|
||||
gocryptfsVolume.mkdir(mkdir)
|
||||
}
|
||||
if (failedIndex == -1){
|
||||
cancelNotification(notification)
|
||||
callback(null)
|
||||
|
||||
importFilesFromUris(dstFiles, srcUris, notification) { failedItem ->
|
||||
callback(failedItem, srcUris)
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
5
app/src/main/res/drawable/icon_create_new_folder.xml
Normal file
5
app/src/main/res/drawable/icon_create_new_folder.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,6h-8l-2,-2L4,4c-1.11,0 -1.99,0.89 -1.99,2L2,18c0,1.11 0.89,2 2,2h16c1.11,0 2,-0.89 2,-2L22,8c0,-1.11 -0.89,-2 -2,-2zM19,14h-3v3h-2v-3h-3v-2h3L14,9h2v3h3v2z"/>
|
||||
</vector>
|
5
app/src/main/res/drawable/icon_import_folder.xml
Normal file
5
app/src/main/res/drawable/icon_import_folder.xml
Normal file
@ -0,0 +1,5 @@
|
||||
<vector android:height="24dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M20,6h-8l-2,-2L4,4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2L22,8c0,-1.1 -0.9,-2 -2,-2zM20,18L4,18L4,8h16v10zM8,13.01l1.41,1.41L11,12.84L11,17h2v-4.16l1.59,1.59L16,13.01 12.01,9 8,13.01z"/>
|
||||
</vector>
|
@ -11,6 +11,8 @@
|
||||
<string name="volume_path">Volume Path:</string>
|
||||
<string name="volume_name">Volume Name:</string>
|
||||
<string name="import_files">Import/Encrypt files</string>
|
||||
<string name="import_folder">Import/Encrypt folder</string>
|
||||
<string name="discovering_files">Discovering files…</string>
|
||||
<string name="mkdir">Create folder</string>
|
||||
<string name="dir_empty">Directory Empty</string>
|
||||
<string name="warning">Warning !</string>
|
||||
|
Loading…
Reference in New Issue
Block a user