Improving handling of SD cards errors

This commit is contained in:
Matéo Duparc 2020-11-03 17:22:09 +01:00
parent 3795f8790f
commit 0b509c2f98
13 changed files with 292 additions and 256 deletions

View File

@ -66,7 +66,7 @@ class CameraActivity : BaseActivity(), SensorOrientationListener.Listener {
override fun onPictureTaken(result: PictureResult) {
take_photo_button.onPhotoTaken()
val inputStream = ByteArrayInputStream(result.data)
if (gocryptfsVolume.importFile(inputStream, PathUtils.path_join(outputDirectory, fileName))){
if (gocryptfsVolume.importFile(inputStream, PathUtils.pathJoin(outputDirectory, fileName))){
Toast.makeText(applicationContext, getString(R.string.picture_save_success, fileName), Toast.LENGTH_SHORT).show()
} else {
ColoredAlertDialogBuilder(applicationContext)

View File

@ -78,7 +78,7 @@ class ChangePasswordActivity : VolumeActionActivity() {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
if (data?.data != null) {
if (PathUtils.isTreeUriOnPrimaryStorage(data.data)){
if (PathUtils.isTreeUriOnPrimaryStorage(data.data!!)){
val path = PathUtils.getFullPathFromTreeUri(data.data, this)
if (path != null){
edit_volume_path.setText(path)

View File

@ -45,7 +45,7 @@ class CreateActivity : VolumeActionActivity() {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
if (data?.data != null) {
if (PathUtils.isTreeUriOnPrimaryStorage(data.data)){
if (PathUtils.isTreeUriOnPrimaryStorage(data.data!!)){
val path = PathUtils.getFullPathFromTreeUri(data.data, this)
if (path != null){
edit_volume_path.setText(path)

View File

@ -100,21 +100,13 @@ class OpenActivity : VolumeActionActivity() {
if (resultCode == Activity.RESULT_OK) {
if (requestCode == PICK_DIRECTORY_REQUEST_CODE) {
if (data?.data != null) {
if (PathUtils.isTreeUriOnPrimaryStorage(data.data)){
val path = PathUtils.getFullPathFromTreeUri(data.data, this)
if (path != null){
edit_volume_path.setText(path)
} else {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(R.string.path_from_uri_null_error_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
val path = PathUtils.getFullPathFromTreeUri(data.data, this)
if (path != null){
edit_volume_path.setText(path)
} else {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.warning)
.setMessage(R.string.open_on_sdcard_warning)
.setTitle(R.string.error)
.setMessage(R.string.path_from_uri_null_error_msg)
.setPositiveButton(R.string.ok, null)
.show()
}
@ -129,19 +121,37 @@ class OpenActivity : VolumeActionActivity() {
Toast.makeText(this, R.string.enter_volume_path, Toast.LENGTH_SHORT).show()
} else {
val rootCipherDirFile = File(rootCipherDir)
if (!GocryptfsVolume.isGocryptfsVolume(rootCipherDirFile)){
if (!rootCipherDirFile.canRead()) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(R.string.open_cant_read_error)
.setPositiveButton(R.string.ok, null)
.show()
} else if (!GocryptfsVolume.isGocryptfsVolume(rootCipherDirFile)){
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(R.string.error_not_a_volume)
.setPositiveButton(R.string.ok, null)
.show()
} else if (!rootCipherDirFile.canWrite()) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.warning)
.setMessage(R.string.open_cant_write_warning)
.setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ -> openVolume() }
.show()
if ((intent.action == Intent.ACTION_SEND || intent.action == Intent.ACTION_SEND_MULTIPLE) && intent.extras != null) { //import via android share menu
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(R.string.open_cant_write_error_msg)
.setPositiveButton(R.string.ok, null)
.show()
} else {
val dialog = ColoredAlertDialogBuilder(this)
.setTitle(R.string.warning)
.setCancelable(false)
.setPositiveButton(R.string.ok) { _, _ -> openVolume() }
if (PathUtils.isPathOnExternalStorage(rootCipherDir, this)){
dialog.setMessage(R.string.open_on_sdcard_warning)
} else {
dialog.setMessage(R.string.open_cant_write_warning)
}
dialog.show()
}
} else {
openVolume()
}

View File

@ -1,6 +1,8 @@
package sushi.hardcore.droidfs.explorers
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Bundle
import android.os.Handler
import android.os.Looper
@ -35,9 +37,11 @@ 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>
@ -235,7 +239,7 @@ open class BaseExplorerActivity : BaseActivity() {
if (folder_name.isEmpty()) {
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
} else {
if (!gocryptfsVolume.mkdir(PathUtils.path_join(currentDirectoryPath, folder_name))) {
if (!gocryptfsVolume.mkdir(PathUtils.pathJoin(currentDirectoryPath, folder_name))) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(R.string.error_mkdir)
@ -291,13 +295,13 @@ open class BaseExplorerActivity : BaseActivity() {
.setView(dialogEditTextView)
.setTitle(R.string.enter_new_name)
.setPositiveButton(R.string.ok) { _, _ ->
handler.sendMessage(Message().apply { obj = checkPathOverwrite(PathUtils.path_join(PathUtils.getParentPath(path), dialogEditText.text.toString()), isDirectory) })
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()
dialogEditText.setOnEditorActionListener { _, _, _ ->
dialog.dismiss()
handler.sendMessage(Message().apply { obj = checkPathOverwrite(PathUtils.path_join(PathUtils.getParentPath(path), dialogEditText.text.toString()), isDirectory) })
handler.sendMessage(Message().apply { obj = checkPathOverwrite(PathUtils.pathJoin(PathUtils.getParentPath(path), dialogEditText.text.toString()), isDirectory) })
true
}
dialog.setOnCancelListener { handler.sendMessage(Message().apply { obj = null }) }
@ -317,11 +321,57 @@ open class BaseExplorerActivity : BaseActivity() {
return outputPath
}
protected fun importFilesFromUris(uris: List<Uri>, task: LoadingTask, callback: (DialogInterface.OnClickListener)? = null): Boolean {
var success = false
for (uri in uris) {
val fileName = PathUtils.getFilenameFromURI(task.activity, uri)
if (fileName == null){
task.stopTask {
ColoredAlertDialogBuilder(task.activity)
.setTitle(R.string.error)
.setMessage(getString(R.string.error_retrieving_filename, uri))
.setPositiveButton(R.string.ok, null)
.show()
}
success = false
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 {
""
}
}
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){
if (new_name.isEmpty()) {
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
} else {
if (!gocryptfsVolume.rename(PathUtils.path_join(currentDirectoryPath, old_name), PathUtils.path_join(currentDirectoryPath, new_name))) {
if (!gocryptfsVolume.rename(PathUtils.pathJoin(currentDirectoryPath, old_name), PathUtils.pathJoin(currentDirectoryPath, new_name))) {
ColoredAlertDialogBuilder(this)
.setTitle(R.string.error)
.setMessage(getString(R.string.rename_failed, old_name))
@ -400,7 +450,7 @@ open class BaseExplorerActivity : BaseActivity() {
}
R.id.external_open -> {
if (usf_open){
openWithExternalApp(PathUtils.path_join(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name))
openWithExternalApp(PathUtils.pathJoin(currentDirectoryPath, explorerElements[explorerAdapter.selectedItems[0]].name))
unselectAll()
}
true

View File

@ -47,7 +47,7 @@ class ExplorerActivity : BaseExplorerActivity() {
if (fileName.isEmpty()) {
Toast.makeText(this, R.string.error_filename_empty, Toast.LENGTH_SHORT).show()
} else {
checkPathOverwrite(PathUtils.path_join(currentDirectoryPath, fileName), false)?.let {
checkPathOverwrite(PathUtils.pathJoin(currentDirectoryPath, fileName), false)?.let {
val handleID = gocryptfsVolume.openWriteMode(it)
if (handleID == -1) {
ColoredAlertDialogBuilder(this)
@ -153,28 +153,7 @@ class ExplorerActivity : BaseExplorerActivity() {
uris.add(singleUri)
}
Looper.prepare()
var success = false
for (uri in uris) {
val dstPath = checkPathOverwrite(PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(activity, uri)), false)
if (dstPath == null){
break
} else {
contentResolver.openInputStream(uri)?.let {
success = gocryptfsVolume.importFile(it, dstPath)
}
if (!success) {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.error)
.setMessage(getString(R.string.import_failed, uri))
.setPositiveButton(R.string.ok, null)
.show()
}
break
}
}
}
if (success) {
if (importFilesFromUris(uris, this)) {
stopTask {
ColoredAlertDialogBuilder(activity)
.setTitle(R.string.success_import)
@ -185,7 +164,7 @@ class ExplorerActivity : BaseExplorerActivity() {
.setPositiveButton(R.string.yes) { _, _ ->
object : LoadingTask(activity, R.string.loading_msg_wipe){
override fun doTask(activity: AppCompatActivity) {
success = true
var success = true
for (uri in uris) {
val errorMsg = Wiper.wipe(activity, uri)
if (errorMsg != null) {
@ -232,7 +211,7 @@ class ExplorerActivity : BaseExplorerActivity() {
var failedItem: String? = null
for (i in explorerAdapter.selectedItems) {
val element = explorerAdapter.getItem(i)
val fullPath = PathUtils.path_join(currentDirectoryPath, element.name)
val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name)
failedItem = if (element.isDirectory) {
recursiveExportDirectory(fullPath, treeDocumentFile)
} else {
@ -283,7 +262,7 @@ class ExplorerActivity : BaseExplorerActivity() {
failedItem = if (types[i] == 0) { //directory
recursiveImportDirectoryFromOtherVolume(remoteGocryptfsVolume, paths[i], currentDirectoryPath)
} else {
safeImportFileFromOtherVolume(remoteGocryptfsVolume, paths[i], PathUtils.path_join(currentDirectoryPath, File(paths[i]).name))
safeImportFileFromOtherVolume(remoteGocryptfsVolume, paths[i], PathUtils.pathJoin(currentDirectoryPath, File(paths[i]).name))
}
if (failedItem != null) {
break
@ -291,7 +270,7 @@ class ExplorerActivity : BaseExplorerActivity() {
}
}
} else {
failedItem = safeImportFileFromOtherVolume(remoteGocryptfsVolume, path, PathUtils.path_join(currentDirectoryPath, File(path).name))
failedItem = safeImportFileFromOtherVolume(remoteGocryptfsVolume, path, PathUtils.pathJoin(currentDirectoryPath, File(path).name))
}
if (failedItem == null) {
stopTask {
@ -386,7 +365,7 @@ class ExplorerActivity : BaseExplorerActivity() {
var failedItem: String? = null
Looper.prepare()
for (element in itemsToProcess) {
val dstPath = checkPathOverwrite(PathUtils.path_join(currentDirectoryPath, element.name), element.isDirectory)
val dstPath = checkPathOverwrite(PathUtils.pathJoin(currentDirectoryPath, element.name), element.isDirectory)
failedItem = if (dstPath == null){
""
} else {
@ -547,7 +526,7 @@ class ExplorerActivity : BaseExplorerActivity() {
}
}
for (e in mappedElements) {
val dstPath = checkPathOverwrite(PathUtils.path_join(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, e.fullPath)), e.isDirectory)
val dstPath = checkPathOverwrite(PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(srcDirectoryPath, e.fullPath)), e.isDirectory)
if (dstPath == null){
return ""
} else {
@ -581,7 +560,7 @@ class ExplorerActivity : BaseExplorerActivity() {
private fun moveElements(elements: List<ExplorerElement>, dstDirectoryPath: String): String? {
for (element in elements){
val dstPath = checkPathOverwrite(PathUtils.path_join(dstDirectoryPath, element.name), element.isDirectory)
val dstPath = checkPathOverwrite(PathUtils.pathJoin(dstDirectoryPath, element.name), element.isDirectory)
if (dstPath == null){
return ""
} else {
@ -636,7 +615,7 @@ class ExplorerActivity : BaseExplorerActivity() {
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.path_join(outputPath, File(remote_directory_path).name), true)
val dstDirectoryPath = checkPathOverwrite(PathUtils.pathJoin(outputPath, File(remote_directory_path).name), true)
if (dstDirectoryPath == null){
return ""
} else {
@ -646,7 +625,7 @@ class ExplorerActivity : BaseExplorerActivity() {
}
}
for (e in mappedElements) {
val dstPath = checkPathOverwrite(PathUtils.path_join(dstDirectoryPath, PathUtils.getRelativePath(remote_directory_path, e.fullPath)), e.isDirectory)
val dstPath = checkPathOverwrite(PathUtils.pathJoin(dstDirectoryPath, PathUtils.getRelativePath(remote_directory_path, e.fullPath)), e.isDirectory)
if (dstPath == null){
return ""
} else {
@ -682,7 +661,7 @@ class ExplorerActivity : BaseExplorerActivity() {
treeDocumentFile.createDirectory(File(plain_directory_path).name)?.let {childTree ->
val explorerElements = gocryptfsVolume.listDir(plain_directory_path)
for (e in explorerElements) {
val fullPath = PathUtils.path_join(plain_directory_path, e.name)
val fullPath = PathUtils.pathJoin(plain_directory_path, e.name)
if (e.isDirectory) {
val failedItem = recursiveExportDirectory(fullPath, childTree)
failedItem?.let { return it }
@ -700,7 +679,7 @@ class ExplorerActivity : BaseExplorerActivity() {
private fun recursiveRemoveDirectory(plain_directory_path: String): String? {
val explorerElements = gocryptfsVolume.listDir(plain_directory_path)
for (e in explorerElements) {
val fullPath = PathUtils.path_join(plain_directory_path, e.name)
val fullPath = PathUtils.pathJoin(plain_directory_path, e.name)
if (e.isDirectory) {
val result = recursiveRemoveDirectory(fullPath)
result?.let { return it }
@ -721,7 +700,7 @@ class ExplorerActivity : BaseExplorerActivity() {
var failedItem: String? = null
for (i in explorerAdapter.selectedItems) {
val element = explorerAdapter.getItem(i)
val fullPath = PathUtils.path_join(currentDirectoryPath, element.name)
val fullPath = PathUtils.pathJoin(currentDirectoryPath, element.name)
if (element.isDirectory) {
val result = recursiveRemoveDirectory(fullPath)
result?.let{ failedItem = it }

View File

@ -1,5 +1,6 @@
package sushi.hardcore.droidfs.explorers
import android.content.DialogInterface
import android.content.Intent
import android.net.Uri
import android.os.Looper
@ -9,7 +10,6 @@ import android.view.View
import androidx.appcompat.app.AppCompatActivity
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.util.LoadingTask
import sushi.hardcore.droidfs.util.PathUtils
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
class ExplorerActivityDrop : BaseExplorerActivity() {
@ -36,42 +36,30 @@ class ExplorerActivityDrop : BaseExplorerActivity() {
val alertDialog = ColoredAlertDialogBuilder(activity)
alertDialog.setCancelable(false)
alertDialog.setPositiveButton(R.string.ok) { _, _ -> finish() }
var errorMsg: String? = null
val errorMsg: String?
val extras = intent.extras
if (extras != null && extras.containsKey(Intent.EXTRA_STREAM)){
Looper.prepare()
if (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 {
val outputPath = checkPathOverwrite(PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(activity, uri)), false)
if (outputPath == null) {
""
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 (gocryptfsVolume.importFile(activity, uri, outputPath)) null else getString(R.string.import_failed, uri)
if (importFilesFromUris(listOf(uri), this){ _, _ -> finish() }) null else ""
}
}
} else if (intent.action == Intent.ACTION_SEND_MULTIPLE) {
val uris = intent.getParcelableArrayListExtra<Uri>(Intent.EXTRA_STREAM)
if (uris != null){
for (uri in uris) {
val outputPath = checkPathOverwrite(PathUtils.path_join(currentDirectoryPath, PathUtils.getFilenameFromURI(activity, uri)), false)
if (outputPath == null){
errorMsg = ""
break
} else {
if (!gocryptfsVolume.importFile(activity, uri, outputPath)) {
errorMsg = getString(R.string.import_failed, uri)
break
}
}
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 {
}
else -> {
errorMsg = 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)

View File

@ -22,7 +22,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
explorerAdapter.onItemClick(position)
if (explorerAdapter.selectedItems.isEmpty()) {
if (!wasSelecting) {
val fullPath = PathUtils.path_join(currentDirectoryPath, explorerElements[position].name)
val fullPath = PathUtils.pathJoin(currentDirectoryPath, explorerElements[position].name)
when {
explorerElements[position].isDirectory -> {
setCurrentPath(fullPath)
@ -61,7 +61,7 @@ class ExplorerActivityPick : BaseExplorerActivity() {
val types = ArrayList<Int>()
for (i in explorerAdapter.selectedItems) {
val e = explorerElements[i]
paths.add(PathUtils.path_join(currentDirectoryPath, e.name))
paths.add(PathUtils.pathJoin(currentDirectoryPath, e.name))
types.add(e.elementType.toInt())
}
resultIntent.putStringArrayListExtra("paths", paths)

View File

@ -5,7 +5,7 @@ import java.util.*
class ExplorerElement(val name: String, val elementType: Short, var size: Long, mtime: Long, parentPath: String) {
val mTime = Date((mtime * 1000).toString().toLong())
val fullPath: String = PathUtils.path_join(parentPath, name)
val fullPath: String = PathUtils.pathJoin(parentPath, name)
val isDirectory: Boolean
get() = elementType.toInt() == 0

View File

@ -7,7 +7,7 @@ import androidx.appcompat.app.AppCompatActivity
import sushi.hardcore.droidfs.R
import sushi.hardcore.droidfs.widgets.ColoredAlertDialogBuilder
abstract class LoadingTask(private val activity: AppCompatActivity, private val loadingMessageResId: Int) {
abstract class LoadingTask(val activity: AppCompatActivity, loadingMessageResId: Int) {
private val dialogLoadingView = activity.layoutInflater.inflate(R.layout.dialog_loading, null)
private val dialogLoading: AlertDialog = ColoredAlertDialogBuilder(activity)
.setView(dialogLoadingView)
@ -31,7 +31,7 @@ abstract class LoadingTask(private val activity: AppCompatActivity, private val
activity.runOnUiThread { doFinally(activity) }
}.start()
}
protected fun stopTask(onUiThread: (() -> Unit)?){
fun stopTask(onUiThread: (() -> Unit)?){
isStopped = true
dialogLoading.dismiss()
onUiThread?.let {

View File

@ -1,157 +0,0 @@
package sushi.hardcore.droidfs.util;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.os.storage.StorageManager;
import android.provider.DocumentsContract;
import android.provider.OpenableColumns;
import androidx.annotation.Nullable;
import java.io.File;
import java.lang.reflect.Array;
import java.lang.reflect.Method;
import java.text.DecimalFormat;
public class PathUtils {
public static String getParentPath(String path){
if (path.endsWith("/")){
String a = path.substring(0, path.length()-2);
if (a.contains("/")){
return a.substring(0, a.lastIndexOf("/"));
} else {
return "";
}
} else {
if (path.contains("/")){
return path.substring(0, path.lastIndexOf("/"));
} else {
return "";
}
}
}
public static String path_join(String... strings){
StringBuilder result = new StringBuilder();
for (String element : strings){
if (!element.isEmpty()){
if (!element.endsWith("/")){
element += "/";
}
result.append(element);
}
}
return result.substring(0, result.length()-1);
}
public static String getRelativePath(String parentPath, String childPath){
return childPath.substring(parentPath.length()+1);
}
public static String getFilenameFromURI(Context context, Uri uri){
String result = null;
if (uri.getScheme().equals("content")){
Cursor cursor = context.getContentResolver().query(uri, null, null, null, null);
if (cursor != null){
try {
if (cursor.moveToFirst()){
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME));
}
} finally {
cursor.close();
}
}
}
if (result == null){
result = uri.getPath();
int cut = result.lastIndexOf('/');
if (cut != -1){
result = result.substring(cut + 1);
}
}
return result;
}
static final String[] units = new String[]{"B", "kB", "MB", "GB", "TB"};
public static String formatSize(long size){
if (size <= 0){
return "0 B";
}
int digitGroups = (int)(Math.log10(size)/Math.log10(1024));
return new DecimalFormat("#,##0.#").format(size/Math.pow(1024, digitGroups))+" "+units[digitGroups];
}
public static Boolean isTreeUriOnPrimaryStorage(Uri treeUri){
String volumeId = getVolumeIdFromTreeUri(treeUri);
if (volumeId != null) {
return volumeId.equals(PRIMARY_VOLUME_NAME) || volumeId.equals("home") || volumeId.equals("downloads");
} else {
return false;
}
}
private static final String PRIMARY_VOLUME_NAME = "primary";
@Nullable
public static String getFullPathFromTreeUri(@Nullable Uri treeUri, Context context) {
if (treeUri == null) return null;
if ("content".equalsIgnoreCase(treeUri.getScheme())) {
String volumePath = getVolumePath(getVolumeIdFromTreeUri(treeUri),context);
if (volumePath == null) return null;
if (volumePath.endsWith(File.separator))
volumePath = volumePath.substring(0, volumePath.length() - 1);
String documentPath = getDocumentPathFromTreeUri(treeUri);
if (documentPath.endsWith(File.separator))
documentPath = documentPath.substring(0, documentPath.length() - 1);
if (documentPath.length() > 0) {
return path_join(volumePath, documentPath);
}
else return volumePath;
} else if ("file".equalsIgnoreCase(treeUri.getScheme())) {
return treeUri.getPath();
}
return null;
}
private static String getVolumePath(final String volumeId, Context context) {
try {
StorageManager mStorageManager =
(StorageManager) context.getSystemService(Context.STORAGE_SERVICE);
Class<?> storageVolumeClazz = Class.forName("android.os.storage.StorageVolume");
Method getVolumeList = mStorageManager.getClass().getMethod("getVolumeList");
Method getUuid = storageVolumeClazz.getMethod("getUuid");
Method getPath = storageVolumeClazz.getMethod("getPath");
Method isPrimary = storageVolumeClazz.getMethod("isPrimary");
Object result = getVolumeList.invoke(mStorageManager);
final int length = Array.getLength(result);
for (int i = 0; i < length; i++) {
Object storageVolumeElement = Array.get(result, i);
String uuid = (String) getUuid.invoke(storageVolumeElement);
Boolean primary = (Boolean) isPrimary.invoke(storageVolumeElement);
if (primary && PRIMARY_VOLUME_NAME.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
if (uuid != null && uuid.equals(volumeId))
return (String) getPath.invoke(storageVolumeElement);
}
return null;
} catch (Exception ex) {
return null;
}
}
private static String getVolumeIdFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if (split.length > 0) return split[0];
else return null;
}
private static String getDocumentPathFromTreeUri(final Uri treeUri) {
final String docId = DocumentsContract.getTreeDocumentId(treeUri);
final String[] split = docId.split(":");
if ((split.length >= 2) && (split[1] != null)) return split[1];
else return File.separator;
}
}

View File

@ -0,0 +1,163 @@
package sushi.hardcore.droidfs.util
import android.content.Context
import android.net.Uri
import android.os.storage.StorageManager
import android.provider.DocumentsContract
import android.provider.OpenableColumns
import androidx.core.content.ContextCompat
import java.io.File
import java.text.DecimalFormat
import kotlin.math.log10
import kotlin.math.pow
object PathUtils {
fun getParentPath(path: String): String {
return if (path.endsWith("/")) {
val a = path.substring(0, path.length - 2)
if (a.contains("/")) {
a.substring(0, a.lastIndexOf("/"))
} else {
""
}
} else {
if (path.contains("/")) {
path.substring(0, path.lastIndexOf("/"))
} else {
""
}
}
}
fun pathJoin(vararg strings: String): String {
val result = StringBuilder()
for (element in strings) {
if (element.isNotEmpty()) {
result.append(element)
if (!element.endsWith("/")) {
result.append("/")
}
}
}
return result.substring(0, result.length - 1)
}
fun getRelativePath(parentPath: String, childPath: String): String {
return childPath.substring(parentPath.length + 1)
}
fun getFilenameFromURI(context: Context, uri: Uri): String? {
var result: String? = null
if (uri.scheme == "content") {
context.contentResolver.query(uri, null, null, null, null)?.use { cursor ->
if (cursor.moveToFirst()) {
result = cursor.getString(cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME))
}
}
}
if (result == null) {
result = uri.path
result?.let {
val cut = it.lastIndexOf('/')
if (cut != -1) {
result = it.substring(cut + 1)
}
}
}
return result
}
private val units = arrayOf("B", "kB", "MB", "GB", "TB")
fun formatSize(size: Long): String {
if (size <= 0) {
return "0 B"
}
val digitGroups = (log10(size.toDouble()) / log10(1024.0)).toInt()
return DecimalFormat("#,##0.#").format(size / 1024.0.pow(digitGroups.toDouble())
) + " " + units[digitGroups]
}
fun isTreeUriOnPrimaryStorage(treeUri: Uri): Boolean {
val volumeId = getVolumeIdFromTreeUri(treeUri)
return if (volumeId != null) {
volumeId == PRIMARY_VOLUME_NAME || volumeId == "home" || volumeId == "downloads"
} else {
false
}
}
private fun getExternalStoragePath(context: Context): List<String> {
val externalPaths: MutableList<String> = ArrayList()
ContextCompat.getExternalFilesDirs(context, null).forEach {
val rootPath = it.path.substring(0, it.path.indexOf(pathJoin("Android/data/", context.packageName, "files")))
if (!rootPath.endsWith("/0/")){ //not primary storage
externalPaths.add(rootPath)
}
}
return externalPaths
}
fun isPathOnExternalStorage(path: String, context: Context): Boolean {
getExternalStoragePath(context).forEach {
if (path.startsWith(it)){
return true
}
}
return false
}
private const val PRIMARY_VOLUME_NAME = "primary"
fun getFullPathFromTreeUri(treeUri: Uri?, context: Context): String? {
if (treeUri == null) return null
if ("content".equals(treeUri.scheme, ignoreCase = true)) {
val vId = getVolumeIdFromTreeUri(treeUri)
var volumePath = getVolumePath(vId, context) ?: return null
if (volumePath.endsWith(File.separator))
volumePath = volumePath.substring(0, volumePath.length - 1)
var documentPath = getDocumentPathFromTreeUri(treeUri)
if (documentPath!!.endsWith(File.separator))
documentPath = documentPath.substring(0, documentPath.length - 1)
return if (documentPath.isNotEmpty()) {
pathJoin(volumePath, documentPath)
} else volumePath
} else if ("file".equals(treeUri.scheme, ignoreCase = true)) {
return treeUri.path
}
return null
}
private fun getVolumePath(volumeId: String?, context: Context): String? {
return try {
val mStorageManager = context.getSystemService(Context.STORAGE_SERVICE) as StorageManager
val storageVolumeClazz = Class.forName("android.os.storage.StorageVolume")
val getVolumeList = mStorageManager.javaClass.getMethod("getVolumeList")
val getUuid = storageVolumeClazz.getMethod("getUuid")
val getPath = storageVolumeClazz.getMethod("getPath")
val isPrimary = storageVolumeClazz.getMethod("isPrimary")
val result = getVolumeList.invoke(mStorageManager)
val length = java.lang.reflect.Array.getLength(result!!)
for (i in 0 until length) {
val storageVolumeElement = java.lang.reflect.Array.get(result, i)
val uuid = getUuid.invoke(storageVolumeElement)
val primary = isPrimary.invoke(storageVolumeElement) as Boolean
if (primary && PRIMARY_VOLUME_NAME == volumeId) return getPath.invoke(storageVolumeElement) as String
if (uuid == volumeId) return getPath.invoke(storageVolumeElement) as String
}
null
} catch (ex: Exception) {
null
}
}
private fun getVolumeIdFromTreeUri(treeUri: Uri): String? {
val docId = DocumentsContract.getTreeDocumentId(treeUri)
val split = docId.split(":").toTypedArray()
return if (split.isNotEmpty()) split[0] else null
}
private fun getDocumentPathFromTreeUri(treeUri: Uri): String? {
val docId = DocumentsContract.getTreeDocumentId(treeUri)
val split: Array<String?> = docId.split(":").toTypedArray()
return if (split.size >= 2 && split[1] != null) split[1] else File.separator
}
}

View File

@ -26,7 +26,7 @@
<string name="export_failed">Export of %s failed.</string>
<string name="success_export">Export successful !</string>
<string name="success_export_msg">The selected files have been successfully exported.</string>
<string name="remove_failed">Deletion of %s failed</string>
<string name="remove_failed">Deletion of %s failed.</string>
<string name="passwords_mismatch">Passwords don\'t match</string>
<string name="dir_not_empty">The selected directory isn\'t empty</string>
<string name="success_volume_create">Volume successfully created !</string>
@ -164,8 +164,10 @@
<string name="path_from_uri_null_error_msg">Failed to retrieve the selected path.</string>
<string name="create_cant_write_error_msg">DroidFS doesn\'t have write access to this path. Please try another location.</string>
<string name="create_on_sdcard_error_msg">DroidFS can\'t write on removable SD cards, please select a path on internal storage.</string>
<string name="open_on_sdcard_warning">DroidFS can\'t write on removable SD cards. You will only have read-only access to the volumes therein.</string>
<string name="open_cant_write_warning">DroidFS doesn\'t have write access to this path. You will only have read-only access to this volume.</string>
<string name="open_on_sdcard_warning">DroidFS can\'t write on removable SD cards. Opening volume with read-only access.</string>
<string name="open_cant_write_warning">DroidFS doesn\'t have write access to this path. Opening volume with read-only access.</string>
<string name="open_cant_write_error_msg">DroidFS doesn\'t have write access to this path. Please try another volume.</string>
<string name="open_cant_read_error">DroidFS doesn\'t have read access to this path. You can try to move the volume to a readable location.</string>
<string name="change_pwd_cant_write_error_msg">DroidFS doesn\'t have write access to this path. You can try to move the volume to a writable location.</string>
<string name="change_pwd_on_sdcard_error_msg">DroidFS can\'t write on removable SD cards, please move the volume to internal storage.</string>
<string name="slideshow_stopped">Slideshow stopped</string>
@ -182,4 +184,5 @@
<string name="no_more_images">No more images found.</string>
<string name="usf_read_doc">You should read it carefully before enabling any of these options.</string>
<string name="usf_doc">Unsafe features documentation</string>
<string name="error_retrieving_filename">Unable to retrieve file name for URI: %s</string>
</resources>