FileUtils.kt 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180
  1. package com.nextcloud.talk.utils
  2. /*
  3. * Nextcloud Talk application
  4. * @author Marcel Hibbe
  5. * @author Andy Scherzinger
  6. * @author Stefan Niedermann
  7. * @author David A. Velasco
  8. * @author Chris Narkiewicz
  9. * Copyright (C) 2021 Andy Scherzinger <info@andy-scherzinger.de>
  10. * Copyright (C) 2021 Stefan Niedermann <info@niedermann.it>
  11. * Copyright (C) 2021 Marcel Hibbe <dev@mhibbe.de>
  12. * Copyright (C) 2016 ownCloud GmbH.
  13. * Copyright (C) 2020 Chris Narkiewicz <hello@ezaquarii.com>
  14. *
  15. * This program is free software: you can redistribute it and/or modify
  16. * it under the terms of the GNU General Public License as published by
  17. * the Free Software Foundation, either version 3 of the License, or
  18. * at your option) any later version.
  19. *
  20. * This program is distributed in the hope that it will be useful,
  21. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  22. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  23. * GNU General Public License for more details.
  24. *
  25. * You should have received a copy of the GNU General Public License
  26. * along with this program. If not, see <http://www.gnu.org/licenses/>.
  27. */
  28. import android.content.ContentResolver
  29. import android.content.Context
  30. import android.database.Cursor
  31. import android.net.Uri
  32. import android.provider.OpenableColumns
  33. import android.util.Log
  34. import java.io.File
  35. import java.io.FileNotFoundException
  36. import java.io.FileOutputStream
  37. import java.io.IOException
  38. import java.io.InputStream
  39. import java.math.BigInteger
  40. import java.security.MessageDigest
  41. object FileUtils {
  42. private val TAG = FileUtils::class.java.simpleName
  43. private const val RADIX: Int = 16
  44. private const val MD5_LENGTH: Int = 32
  45. /**
  46. * Creates a new [File]
  47. */
  48. @Suppress("ThrowsCount")
  49. @JvmStatic
  50. fun getTempCacheFile(context: Context, fileName: String): File {
  51. val cacheFile = File(context.applicationContext.filesDir.absolutePath + "/" + fileName)
  52. Log.v(TAG, "Full path for new cache file:" + cacheFile.absolutePath)
  53. val tempDir = cacheFile.parentFile ?: throw FileNotFoundException("could not cacheFile.getParentFile()")
  54. if (!tempDir.exists()) {
  55. Log.v(
  56. TAG,
  57. "The folder in which the new file should be created does not exist yet. Trying to create it…"
  58. )
  59. if (tempDir.mkdirs()) {
  60. Log.v(TAG, "Creation successful")
  61. } else {
  62. throw IOException("Directory for temporary file does not exist and could not be created.")
  63. }
  64. }
  65. Log.v(TAG, "- Try to create actual cache file")
  66. if (cacheFile.createNewFile()) {
  67. Log.v(TAG, "Successfully created cache file")
  68. } else {
  69. throw IOException("Failed to create cacheFile")
  70. }
  71. return cacheFile
  72. }
  73. /**
  74. * Creates a new [File]
  75. */
  76. fun removeTempCacheFile(context: Context, fileName: String) {
  77. val cacheFile = File(context.applicationContext.filesDir.absolutePath + "/" + fileName)
  78. Log.v(TAG, "Full path for new cache file:" + cacheFile.absolutePath)
  79. if (cacheFile.exists()) {
  80. if (cacheFile.delete()) {
  81. Log.v(TAG, "Deletion successful")
  82. } else {
  83. throw IOException("Directory for temporary file does not exist and could not be created.")
  84. }
  85. }
  86. }
  87. @Suppress("ThrowsCount")
  88. fun getFileFromUri(context: Context, sourceFileUri: Uri): File? {
  89. val fileName = getFileName(sourceFileUri, context)
  90. val scheme = sourceFileUri.scheme
  91. val file = if (scheme == null) {
  92. Log.d(TAG, "relative uri: " + sourceFileUri.path)
  93. throw IllegalArgumentException("relative paths are not supported")
  94. } else if (ContentResolver.SCHEME_CONTENT == scheme) {
  95. copyFileToCache(context, sourceFileUri, fileName)
  96. } else if (ContentResolver.SCHEME_FILE == scheme) {
  97. if (sourceFileUri.path != null) {
  98. sourceFileUri.path?.let { File(it) }
  99. } else {
  100. throw IllegalArgumentException("uri does not contain path")
  101. }
  102. } else {
  103. throw IllegalArgumentException("unsupported scheme: " + sourceFileUri.path)
  104. }
  105. return file
  106. }
  107. @Suppress("NestedBlockDepth")
  108. fun copyFileToCache(context: Context, sourceFileUri: Uri, filename: String): File? {
  109. val cachedFile = File(context.cacheDir, filename)
  110. if (!cachedFile.canonicalPath.startsWith(context.cacheDir.canonicalPath, true)) {
  111. Log.w(TAG, "cachedFile was not created in cacheDir. Aborting for security reasons.")
  112. cachedFile.delete()
  113. return null
  114. }
  115. if (cachedFile.exists()) {
  116. Log.d(TAG, "file is already in cache")
  117. } else {
  118. val outputStream = FileOutputStream(cachedFile)
  119. try {
  120. val inputStream: InputStream? = context.contentResolver.openInputStream(sourceFileUri)
  121. inputStream?.use { input ->
  122. outputStream.use { output ->
  123. input.copyTo(output)
  124. }
  125. }
  126. outputStream.flush()
  127. } catch (e: FileNotFoundException) {
  128. Log.w(TAG, "failed to copy file to cache", e)
  129. }
  130. }
  131. return cachedFile
  132. }
  133. fun getFileName(uri: Uri, context: Context?): String {
  134. var filename: String? = null
  135. if (uri.scheme == "content" && context != null) {
  136. val cursor: Cursor? = context.contentResolver.query(uri, null, null, null, null)
  137. try {
  138. if (cursor != null && cursor.moveToFirst()) {
  139. filename = cursor.getString(cursor.getColumnIndexOrThrow(OpenableColumns.DISPLAY_NAME))
  140. }
  141. } finally {
  142. cursor?.close()
  143. }
  144. }
  145. // if it was no content uri, read filename from path
  146. if (filename == null) {
  147. filename = uri.path
  148. }
  149. val lastIndexOfSlash = filename!!.lastIndexOf('/')
  150. if (lastIndexOfSlash != -1) {
  151. filename = filename.substring(lastIndexOfSlash + 1)
  152. }
  153. return filename
  154. }
  155. @JvmStatic
  156. fun md5Sum(file: File): String {
  157. val temp = file.name + file.lastModified() + file.length()
  158. val messageDigest = MessageDigest.getInstance("MD5")
  159. messageDigest.update(temp.toByteArray())
  160. val digest = messageDigest.digest()
  161. val md5String = StringBuilder(BigInteger(1, digest).toString(RADIX))
  162. while (md5String.length < MD5_LENGTH) {
  163. md5String.insert(0, "0")
  164. }
  165. return md5String.toString()
  166. }
  167. }