diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt index 269407d3b5..ea214ff683 100644 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt +++ b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/File.kt @@ -17,9 +17,11 @@ package io.element.android.libraries.androidutils.file import android.content.Context +import androidx.annotation.WorkerThread import io.element.android.libraries.core.data.tryOrNull import timber.log.Timber import java.io.File +import java.util.Locale import java.util.UUID fun File.safeDelete() { @@ -52,3 +54,99 @@ fun Context.createTmpFile(baseDir: File = cacheDir, extension: String? = null): val suffix = extension?.let { ".$extension" } return File.createTempFile(UUID.randomUUID().toString(), suffix, baseDir).apply { mkdirs() } } + +// Implementation should return true in case of success +typealias ActionOnFile = (file: File) -> Boolean + +/* ========================================================================================== + * Log + * ========================================================================================== */ + +fun lsFiles(context: Context) { + Timber.v("Content of cache dir:") + recursiveActionOnFile(context.cacheDir, ::logAction) + + Timber.v("Content of files dir:") + recursiveActionOnFile(context.filesDir, ::logAction) +} + +private fun logAction(file: File): Boolean { + if (file.isDirectory) { + Timber.v(file.toString()) + } else { + Timber.v("$file ${file.length()} bytes") + } + return true +} + +/* ========================================================================================== + * Private + * ========================================================================================== */ + +/** + * Return true in case of success. + */ +private fun recursiveActionOnFile(file: File, action: ActionOnFile): Boolean { + if (file.isDirectory) { + file.list()?.forEach { + val result = recursiveActionOnFile(File(file, it), action) + + if (!result) { + // Break the loop + return false + } + } + } + + return action.invoke(file) +} + +/** + * Get the file extension of a fileUri or a filename. + * + * @param fileUri the fileUri (can be a simple filename) + * @return the file extension, in lower case, or null is extension is not available or empty + */ +fun getFileExtension(fileUri: String): String? { + var reducedStr = fileUri + + if (reducedStr.isNotEmpty()) { + // Remove fragment + reducedStr = reducedStr.substringBeforeLast('#') + + // Remove query + reducedStr = reducedStr.substringBeforeLast('?') + + // Remove path + val filename = reducedStr.substringAfterLast('/') + + // Contrary to method MimeTypeMap.getFileExtensionFromUrl, we do not check the pattern + // See https://stackoverflow.com/questions/14320527/android-should-i-use-mimetypemap-getfileextensionfromurl-bugs + if (filename.isNotEmpty()) { + val dotPos = filename.lastIndexOf('.') + if (0 <= dotPos) { + val ext = filename.substring(dotPos + 1) + + if (ext.isNotBlank()) { + return ext.lowercase(Locale.ROOT) + } + } + } + } + + return null +} + +/* ========================================================================================== + * Size + * ========================================================================================== */ + +@WorkerThread +fun File.getSizeOfFiles(): Long { + return walkTopDown() + .onEnter { + Timber.v("Get size of ${it.absolutePath}") + true + } + .sumOf { it.length() } +} diff --git a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileUtils.kt b/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileUtils.kt deleted file mode 100644 index dbd284d305..0000000000 --- a/libraries/androidutils/src/main/kotlin/io/element/android/libraries/androidutils/file/FileUtils.kt +++ /dev/null @@ -1,137 +0,0 @@ -/* - * Copyright (c) 2023 New Vector Ltd - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ - -package io.element.android.libraries.androidutils.file - -import android.content.Context -import androidx.annotation.WorkerThread -import timber.log.Timber -import java.io.File -import java.util.Locale - -// Implementation should return true in case of success -typealias ActionOnFile = (file: File) -> Boolean - -/* ========================================================================================== - * Delete - * ========================================================================================== */ - -fun deleteAllFiles(root: File) { - Timber.v("Delete ${root.absolutePath}") - recursiveActionOnFile(root, ::deleteAction) -} - -private fun deleteAction(file: File): Boolean { - if (file.exists()) { - Timber.v("deleteFile: $file") - return file.delete() - } - - return true -} - -/* ========================================================================================== - * Log - * ========================================================================================== */ - -fun lsFiles(context: Context) { - Timber.v("Content of cache dir:") - recursiveActionOnFile(context.cacheDir, ::logAction) - - Timber.v("Content of files dir:") - recursiveActionOnFile(context.filesDir, ::logAction) -} - -private fun logAction(file: File): Boolean { - if (file.isDirectory) { - Timber.v(file.toString()) - } else { - Timber.v("$file ${file.length()} bytes") - } - return true -} - -/* ========================================================================================== - * Private - * ========================================================================================== */ - -/** - * Return true in case of success. - */ -private fun recursiveActionOnFile(file: File, action: ActionOnFile): Boolean { - if (file.isDirectory) { - file.list()?.forEach { - val result = recursiveActionOnFile(File(file, it), action) - - if (!result) { - // Break the loop - return false - } - } - } - - return action.invoke(file) -} - -/** - * Get the file extension of a fileUri or a filename. - * - * @param fileUri the fileUri (can be a simple filename) - * @return the file extension, in lower case, or null is extension is not available or empty - */ -fun getFileExtension(fileUri: String): String? { - var reducedStr = fileUri - - if (reducedStr.isNotEmpty()) { - // Remove fragment - reducedStr = reducedStr.substringBeforeLast('#') - - // Remove query - reducedStr = reducedStr.substringBeforeLast('?') - - // Remove path - val filename = reducedStr.substringAfterLast('/') - - // Contrary to method MimeTypeMap.getFileExtensionFromUrl, we do not check the pattern - // See https://stackoverflow.com/questions/14320527/android-should-i-use-mimetypemap-getfileextensionfromurl-bugs - if (filename.isNotEmpty()) { - val dotPos = filename.lastIndexOf('.') - if (0 <= dotPos) { - val ext = filename.substring(dotPos + 1) - - if (ext.isNotBlank()) { - return ext.lowercase(Locale.ROOT) - } - } - } - } - - return null -} - -/* ========================================================================================== - * Size - * ========================================================================================== */ - -@WorkerThread -fun File.getSizeOfFiles(): Long { - return walkTopDown() - .onEnter { - Timber.v("Get size of ${it.absolutePath}") - true - } - .sumOf { it.length() } -}