Skip to content
Merged
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
75 changes: 55 additions & 20 deletions File/src/main/kotlin/com/infomaniak/core/file/FileInfo.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,14 @@ package com.infomaniak.core.file

import android.database.Cursor
import android.net.Uri
import android.os.Build.VERSION.SDK_INT
import android.provider.DocumentsContract
import android.provider.MediaStore
import android.provider.OpenableColumns
import androidx.core.database.getLongOrNull
import androidx.core.database.getStringOrNull
import com.infomaniak.core.common.cancellable
import com.infomaniak.core.common.utils.FORMAT_NEW_FILE
import com.infomaniak.core.sentry.SentryLog
import io.sentry.Sentry
import kotlinx.coroutines.Dispatchers
Expand All @@ -32,12 +35,24 @@ import kotlinx.coroutines.ensureActive
import kotlinx.coroutines.invoke
import kotlinx.coroutines.yield
import splitties.init.appCtx
import java.time.format.DateTimeFormatter
import java.text.SimpleDateFormat
import java.util.Date
import java.util.Locale
import kotlin.time.Duration.Companion.seconds
import kotlin.time.ExperimentalTime
import kotlin.time.Instant
import kotlin.time.TimeSource
import kotlin.time.toJavaInstant

val DATE_TAKEN: String = if (SDK_INT >= 29) MediaStore.MediaColumns.DATE_TAKEN else "datetaken"

private val sizeProjection by lazy { arrayOf(OpenableColumns.SIZE) }
private val displayNameProjection by lazy { arrayOf(OpenableColumns.DISPLAY_NAME) }
private val displayNameAndSizeProjection by lazy { displayNameProjection + sizeProjection }
private val displayNameAndDateAddedProjection by lazy { displayNameProjection + MediaStore.MediaColumns.DATE_ADDED }
private val simpleDateFormatter by lazy { SimpleDateFormat(FORMAT_NEW_FILE, Locale.getDefault()) }
private val dateTimeRegex by lazy { ".*${DATE_AND_TIME}(?:_\\d+)?\\..*".toRegex() }
private val counter = InputStreamCounter()
private const val TAG = "FileInfo"
/* language=RegExp */
private const val DATE_AND_TIME = "(\\d{8}_\\d{6})"

suspend fun fileNameFor(uri: Uri): String? = Dispatchers.IO {
runCatching { uri.retrieveAndUse(displayNameProjection) { getFileName(uri) } }
Expand Down Expand Up @@ -123,18 +138,46 @@ private fun Uri.statSize(): Long? {

private fun Cursor.getDisplayName(): String? = getColumnIndexOrNull(OpenableColumns.DISPLAY_NAME)?.let(::getStringOrNull)

private fun Cursor.getFileSize(): Long? = getColumnIndexOrNull(OpenableColumns.SIZE)?.let(::getLongOrNull)
private fun Cursor.getFileSize(): Long? = getLongOrNull(OpenableColumns.SIZE)

private fun Cursor.getNameFromDate(): String? = getDateAdded()?.toInstant()?.let(simpleDateFormatter::format)

fun Cursor.getFileDatesWithFallback(lastModifiedDateFallback: Date? = null): Pair<Date?, Date> {
val createdAt = getFileCreatedDate()
val modifiedAt = getFileUpdatedDate() ?: lastModifiedDateFallback ?: createdAt ?: Date()
return createdAt to modifiedAt
}

private fun Cursor.getFileCreatedDate(): Date? = getDateTaken() ?: attemptExtractFromName() ?: getDateAdded()

private fun Cursor.getFileUpdatedDate(): Date? = getLastModified() ?: getDateModified()

private fun Cursor.getDateTaken(): Date? = getDateFromMillisecond(DATE_TAKEN)

private fun Cursor.getDateAdded(): Date? = getDateFromSecond(MediaStore.MediaColumns.DATE_ADDED)

@OptIn(ExperimentalTime::class)
private fun Cursor.getNameFromDate(): String? = getDate()?.toJavaInstant()?.let(simpleDateFormatter::format)
private fun Cursor.attemptExtractFromName(): Date? {
return getDisplayName()
?.let { dateTimeRegex.find(it)?.groups[1]?.value }
?.let { runCatching { simpleDateFormatter.parse(it) }.getOrNull() }
}

fun Cursor.getLongOrNull(columnName: String): Long? = getColumnIndexOrNull(columnName)?.let(::getLongOrNull)

private fun Cursor.getLastModified(): Date? = getDateFromMillisecond(DocumentsContract.Document.COLUMN_LAST_MODIFIED)

@OptIn(ExperimentalTime::class)
private fun Cursor.getDate(): Instant? {
return getColumnIndexOrNull(MediaStore.MediaColumns.DATE_ADDED)
?.let(::getLongOrNull)
?.let(Instant::fromEpochSeconds)
private fun Cursor.getDateModified(): Date? = getDateFromSecond(MediaStore.MediaColumns.DATE_MODIFIED)

private fun Cursor.getDateFromSecond(columnName: String): Date? {
return getLongOrNull(columnName)?.run { Date(seconds.inWholeMilliseconds) }.isValid()
}

private fun Cursor.getDateFromMillisecond(columnName: String): Date? {
return getLongOrNull(columnName)?.let(::Date).isValid()
}

private fun Date?.isValid() = this?.takeIf { it.time > 0 }

private fun Cursor.getColumnIndexOrNull(columnName: String) = getColumnIndex(columnName).takeUnless { it == -1 }

private fun warnSentryFileName(uri: Uri, columns: Array<String>, availableColumns: Array<String>) {
Expand Down Expand Up @@ -176,14 +219,6 @@ private suspend fun <R> Uri.retrieveAndUse(projection: Array<String>, block: sus
}
}

private val counter = InputStreamCounter()
private const val TAG = "FileInfo"
private val sizeProjection by lazy { arrayOf(OpenableColumns.SIZE) }
private val displayNameProjection by lazy { arrayOf(OpenableColumns.DISPLAY_NAME) }
private val displayNameAndSizeProjection by lazy { displayNameProjection + sizeProjection }
private val displayNameAndDateAddedProjection by lazy { displayNameProjection + MediaStore.MediaColumns.DATE_ADDED }
private val simpleDateFormatter by lazy { DateTimeFormatter.ofPattern("yyyyMMdd_HHmmss") }

private class FileNameException(
uri: Uri,
searchedColumns: Array<String>,
Expand Down
Loading