package sushi.hardcore.droidfs.util import android.content.ActivityNotFoundException import android.content.Context import android.net.Uri import android.os.storage.StorageManager import android.provider.DocumentsContract import android.provider.OpenableColumns import androidx.activity.result.ActivityResultLauncher import androidx.core.content.ContextCompat import sushi.hardcore.droidfs.R import sushi.hardcore.droidfs.widgets.CustomAlertDialogBuilder 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 when { parentPath.isEmpty() -> { childPath } parentPath.length == childPath.length -> { "" } else -> { childPath.substring(parentPath.length + 1) } } } fun isChildOf(childPath: String, parentPath: String): Boolean { if (parentPath.length > childPath.length){ return false } return childPath.substring(0, parentPath.length) == parentPath } 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", "PB", "EB", "ZB", "YB") fun formatSize(size: Long): String { if (size <= 0) { return "0 B" } val digitGroups = (log10(size.toDouble()) / log10(1000.0)).toInt() return DecimalFormat("#,##0.#").format(size / 1000.0.pow(digitGroups.toDouble()) ) + " " + units[digitGroups] } fun getPackageDataFolder(context: Context): String { return "Android/data/${context.packageName}/" } private fun getExternalStoragePath(context: Context): List { val externalPaths: MutableList = ArrayList() ContextCompat.getExternalFilesDirs(context, null).forEach { val rootPath = it.path.substring(0, it.path.indexOf(getPackageDataFolder(context)+"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 = docId.split(":").toTypedArray() return if (split.size >= 2 && split[1] != null) split[1] else File.separator } fun recursiveRemoveDirectory(rootDirectory: File): Boolean { rootDirectory.listFiles()?.forEach { item -> if (item.isDirectory) { if (!recursiveRemoveDirectory(item)){ return false } } else { if (!item.delete()) { return false } } } return rootDirectory.delete() } fun safePickDirectory(directoryPicker: ActivityResultLauncher, context: Context, themeValue: String) { try { directoryPicker.launch(null) } catch (e: ActivityNotFoundException) { CustomAlertDialogBuilder(context, themeValue) .setTitle(R.string.error) .setMessage(R.string.open_tree_failed) .setPositiveButton(R.string.ok, null) .show() } } }