Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import java.util.Locale

object FileUtils {
private const val TAG = "FilePickerUtils"

// On Android, the CSV mime type from getMimeTypeFromExtension() returns
// "text/comma-separated-values" which is non-standard and doesn't filter
// CSV files in Google Drive.
Expand Down Expand Up @@ -355,7 +356,7 @@ object FileUtils {
}

mimes.add(mime)
if(allowedExtensions[i] == CSV_EXTENSION) {
if (allowedExtensions[i] == CSV_EXTENSION) {
// Add the standard CSV mime type.
mimes.add(CSV_MIME_TYPE)
}
Expand Down Expand Up @@ -457,7 +458,11 @@ object FileUtils {
val timeStamp = SimpleDateFormat("yyyyMMdd_HHmmss", Locale.getDefault()).format(Date())
val imageFileName = "IMAGE_" + timeStamp + "_"
val storageDir = context.cacheDir
return File.createTempFile(imageFileName, "." + getCompressFormatBasedFileExtension(compressFormat), storageDir)
return File.createTempFile(
imageFileName,
"." + getCompressFormatBasedFileExtension(compressFormat),
storageDir
)
}

/**
Expand Down Expand Up @@ -506,6 +511,26 @@ object FileUtils {
}
}

/**
* Opens a file stream from a URI, caches the file locally, and retrieves its metadata.
*
* This function takes a content URI, copies the file to the application's cache directory
* to ensure persistent access, and then builds a [FileInfo] object containing details
* about the file such as its path, name, URI, and size.
*
* If the `withData` flag is true, the file's content will also be read into a byte array
* and included in the returned [FileInfo] object. This is memory-intensive and should be
* used cautiously with large files.
*
* The cached file is placed in a structured directory within the app's cache to avoid
* name collisions.
*
* @param context The application context, used for accessing the content resolver and cache directory.
* @param uri The URI of the file to be opened.
* @param withData A boolean flag indicating whether to load the file's raw byte data into memory.
* @return A [FileInfo] object containing the file's details, or `null` if the file
* could not be accessed or cached.
*/
@JvmStatic
fun openFileStream(context: Context, uri: Uri, withData: Boolean): FileInfo? {
var fileInputStream: InputStream? = null
Expand All @@ -515,39 +540,38 @@ object FileUtils {
val path =
context.cacheDir.absolutePath + "/file_picker/" + System.currentTimeMillis() + "/" + (fileName
?: "unamed")

val file = File(path)
if (withData) {

if (!file.exists()) {
try {
file.parentFile?.mkdirs()
if (!file.exists()) {
try {
file.parentFile?.mkdirs()

fileInputStream = context.contentResolver.openInputStream(uri)
fileOutputStream = FileOutputStream(file)
fileInputStream = context.contentResolver.openInputStream(uri)
fileOutputStream = FileOutputStream(file)

val out = BufferedOutputStream(fileOutputStream)
val buffer = ByteArray(8192)
var len: Int
val out = BufferedOutputStream(fileOutputStream)
val buffer = ByteArray(8192)
var len: Int

while ((fileInputStream!!.read(buffer).also { len = it }) >= 0) {
out.write(buffer, 0, len)
}
out.flush()
} catch (e: Exception) {
Log.e(TAG, "Failed to retrieve and cache file: " + e.message, e)
return null
} finally {
try {
fileOutputStream?.fd?.sync()
fileOutputStream?.close()
fileInputStream?.close()
} catch (ex: IOException) {
Log.e(TAG, "Failed to close file streams: " + ex.message, ex)
while ((fileInputStream!!.read(buffer).also { len = it }) >= 0) {
out.write(buffer, 0, len)
}
out.flush()
} catch (e: Exception) {
Log.e(TAG, "Failed to retrieve and cache file: " + e.message, e)
return null
} finally {
try {
fileOutputStream?.fd?.sync()
fileOutputStream?.close()
fileInputStream?.close()
} catch (ex: IOException) {
Log.e(TAG, "Failed to close file streams: " + ex.message, ex)
}
}
}
}

if (withData) {
loadData(file, fileInfo)
}

Expand Down