stream file to calculate both hash and size without loading it all at once

This commit is contained in:
davotoula
2025-10-25 21:24:12 +02:00
parent 371d3bf0f7
commit e1c54f52e3

View File

@@ -36,7 +36,6 @@ import com.vitorpamplona.quartz.nip01Core.core.toHexKey
import com.vitorpamplona.quartz.nipB7Blossom.BlossomAuthorizationEvent import com.vitorpamplona.quartz.nipB7Blossom.BlossomAuthorizationEvent
import com.vitorpamplona.quartz.nipB7Blossom.BlossomUploadResult import com.vitorpamplona.quartz.nipB7Blossom.BlossomUploadResult
import com.vitorpamplona.quartz.utils.RandomInstance import com.vitorpamplona.quartz.utils.RandomInstance
import com.vitorpamplona.quartz.utils.sha256.sha256
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext import kotlinx.coroutines.withContext
import okhttp3.MediaType.Companion.toMediaType import okhttp3.MediaType.Companion.toMediaType
@@ -48,9 +47,34 @@ import okio.BufferedSink
import okio.source import okio.source
import java.io.File import java.io.File
import java.io.InputStream import java.io.InputStream
import java.security.MessageDigest
import java.util.Base64 import java.util.Base64
class BlossomUploader { class BlossomUploader {
data class StreamInfo(
val hash: HexKey,
val size: Long,
)
/**
* Calculate SHA256 hash and size of a file by streaming it in chunks
* to avoid loading the entire file into memory.
*/
private fun calculateHashAndSize(inputStream: InputStream): StreamInfo {
val digest = MessageDigest.getInstance("SHA-256")
val buffer = ByteArray(8192) // 8KB buffer
var bytesRead: Int
var totalBytes = 0L
while (inputStream.read(buffer).also { bytesRead = it } != -1) {
digest.update(buffer, 0, bytesRead)
totalBytes += bytesRead
}
val hash = digest.digest().toHexKey()
return StreamInfo(hash, totalBytes)
}
fun Context.getFileName(uri: Uri): String? = fun Context.getFileName(uri: Uri): String? =
when (uri.scheme) { when (uri.scheme) {
ContentResolver.SCHEME_CONTENT -> getContentFileName(uri) ContentResolver.SCHEME_CONTENT -> getContentFileName(uri)
@@ -82,25 +106,24 @@ class BlossomUploader {
val myContentType = contentType ?: contentResolver.getType(uri) val myContentType = contentType ?: contentResolver.getType(uri)
val fileName = context.getFileName(uri) val fileName = context.getFileName(uri)
// Calculate hash and size by streaming the file in chunks
// to avoid loading the entire file into memory
val imageInputStreamForHash = contentResolver.openInputStream(uri) val imageInputStreamForHash = contentResolver.openInputStream(uri)
val payload = checkNotNull(imageInputStreamForHash) { "Can't open the image input stream" }
imageInputStreamForHash?.use {
it.readBytes() val streamInfo =
imageInputStreamForHash.use {
calculateHashAndSize(it)
} }
checkNotNull(payload) { "Can't open the image input stream" }
val hash = sha256(payload).toHexKey()
val imageInputStream = contentResolver.openInputStream(uri) val imageInputStream = contentResolver.openInputStream(uri)
checkNotNull(imageInputStream) { "Can't open the image input stream" } checkNotNull(imageInputStream) { "Can't open the image input stream" }
return imageInputStream.use { stream -> return imageInputStream.use { stream ->
upload( upload(
stream, stream,
hash, streamInfo.hash,
payload.size, streamInfo.size.toInt(),
fileName, fileName,
myContentType, myContentType,
alt, alt,