From 76be7b0d58bfcafef6d39eba760ee992bcbbe148 Mon Sep 17 00:00:00 2001 From: davotoula Date: Sun, 2 Nov 2025 19:45:51 +0100 Subject: [PATCH] code simplification and deduplication --- .../uploads/BlurhashMetadataCalculator.kt | 45 ++++++++++------- .../service/uploads/UploadOrchestrator.kt | 50 +++++++++---------- .../uploads/blossom/BlossomUploader.kt | 26 +++------- .../service/uploads/nip96/Nip96Uploader.kt | 15 +++--- 4 files changed, 63 insertions(+), 73 deletions(-) diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/BlurhashMetadataCalculator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/BlurhashMetadataCalculator.kt index c90473a47..bfd3ab05d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/BlurhashMetadataCalculator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/BlurhashMetadataCalculator.kt @@ -31,10 +31,24 @@ import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag import com.vitorpamplona.quartz.utils.Log object BlurhashMetadataCalculator { - fun shouldAttempt(mimeType: String?): Boolean = - mimeType?.let { - it.startsWith("image/", ignoreCase = true) || it.startsWith("video/", ignoreCase = true) - } ?: false + private fun isImage(mimeType: String?) = mimeType?.startsWith("image/", ignoreCase = true) == true + + private fun isVideo(mimeType: String?) = mimeType?.startsWith("video/", ignoreCase = true) == true + + fun shouldAttempt(mimeType: String?): Boolean = isImage(mimeType) || isVideo(mimeType) + + private fun createBitmapOptions() = + BitmapFactory.Options().apply { + inPreferredConfig = Bitmap.Config.ARGB_8888 + } + + private fun processImage( + bitmap: Bitmap?, + dimPrecomputed: DimensionTag?, + ): Pair { + val (blur, dim) = processBitmap(bitmap) + return blur to (dim ?: dimPrecomputed) + } fun computeFromBytes( data: ByteArray, @@ -42,14 +56,11 @@ object BlurhashMetadataCalculator { dimPrecomputed: DimensionTag?, ): Pair = when { - mimeType?.startsWith("image/", ignoreCase = true) == true -> { - val options = BitmapFactory.Options() - options.inPreferredConfig = Bitmap.Config.ARGB_8888 - val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size, options) - val (blur, dim) = processBitmap(bitmap) - blur to (dim ?: dimPrecomputed) + isImage(mimeType) -> { + val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size, createBitmapOptions()) + processImage(bitmap, dimPrecomputed) } - mimeType?.startsWith("video/", ignoreCase = true) == true -> { + isVideo(mimeType) -> { val retriever = MediaMetadataRetriever() try { retriever.setDataSource(ByteArrayMediaDataSource(data)) @@ -71,16 +82,13 @@ object BlurhashMetadataCalculator { return try { when { - mimeType?.startsWith("image/", ignoreCase = true) == true -> + isImage(mimeType) -> context.contentResolver.openInputStream(uri)?.use { stream -> - val options = BitmapFactory.Options() - options.inPreferredConfig = Bitmap.Config.ARGB_8888 - val bitmap = BitmapFactory.decodeStream(stream, null, options) - val (blur, dim) = processBitmap(bitmap) - blur to (dim ?: dimPrecomputed) + val bitmap = BitmapFactory.decodeStream(stream, null, createBitmapOptions()) + processImage(bitmap, dimPrecomputed) } ?: (null to dimPrecomputed) - mimeType?.startsWith("video/", ignoreCase = true) == true -> { + isVideo(mimeType) -> { val retriever = MediaMetadataRetriever() try { retriever.setDataSource(context, uri) @@ -89,6 +97,7 @@ object BlurhashMetadataCalculator { retriever.release() } } + else -> null } } catch (e: Exception) { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt index ca4311edd..8426eca3d 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/UploadOrchestrator.kt @@ -232,35 +232,33 @@ class UploadOrchestrator { updateState(0.6, UploadingState.Downloading) // Use streaming verification for memory efficiency with large files - val verification: ImageDownloader.StreamVerification? = ImageDownloader().waitAndVerifyStream(uploadResult.url, okHttpClient) + val verification = + ImageDownloader().waitAndVerifyStream(uploadResult.url, okHttpClient) + ?: return error(R.string.could_not_download_from_the_server) - if (verification != null) { - updateState(0.8, UploadingState.Hashing) + updateState(0.8, UploadingState.Hashing) - // Create FileHeader with hash from streaming verification - // Note: We skip blurhash/dimensions since we already have them from upload - val fileHeader = - FileHeader( - mimeType = uploadResult.type ?: localContentType ?: verification.contentType, - hash = verification.hash, - size = verification.size.toInt(), - dim = uploadResult.dimension, - blurHash = uploadResult.blurHash, - ) - - return finish( - OrchestratorResult.ServerResult( - fileHeader, - uploadResult.url, - uploadResult.magnet, - uploadResult.sha256, - originalContentType, - originalHash, - ), + // Create FileHeader with hash from streaming verification + // Note: We skip blurhash/dimensions since we already have them from upload + val fileHeader = + FileHeader( + mimeType = uploadResult.type ?: localContentType ?: verification.contentType, + hash = verification.hash, + size = verification.size.toInt(), + dim = uploadResult.dimension, + blurHash = uploadResult.blurHash, ) - } else { - return error(R.string.could_not_download_from_the_server) - } + + return finish( + OrchestratorResult.ServerResult( + fileHeader, + uploadResult.url, + uploadResult.magnet, + uploadResult.sha256, + originalContentType, + originalHash, + ), + ) } sealed class OrchestratorResult { diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt index 924b63eec..6483ff09e 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/blossom/BlossomUploader.kt @@ -52,20 +52,6 @@ import java.io.InputStream import java.util.Base64 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 (hash, size) = sha256StreamWithCount(inputStream) - return StreamInfo(hash.toHexKey(), size) - } - fun Context.getFileName(uri: Uri): String? = when (uri.scheme) { ContentResolver.SCHEME_CONTENT -> getContentFileName(uri) @@ -100,23 +86,23 @@ class BlossomUploader { val imageInputStreamForHash = contentResolver.openInputStream(uri) checkNotNull(imageInputStreamForHash) { "Can't open the image input stream" } - val streamInfo = - imageInputStreamForHash.use { - calculateHashAndSize(it) + val (hash, size) = + imageInputStreamForHash.use { stream -> + val (hashBytes, totalBytes) = sha256StreamWithCount(stream) + hashBytes.toHexKey() to totalBytes } val localMetadata = BlurhashMetadataCalculator.computeFromUri(context, uri, myContentType) val imageInputStream = contentResolver.openInputStream(uri) - checkNotNull(imageInputStream) { "Can't open the image input stream" } val serverResult = imageInputStream.use { stream -> upload( stream, - streamInfo.hash, - streamInfo.size, + hash, + size, fileName, myContentType, alt, diff --git a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt index 9e17ed898..3bb80367f 100644 --- a/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt +++ b/amethyst/src/main/java/com/vitorpamplona/amethyst/service/uploads/nip96/Nip96Uploader.kt @@ -128,15 +128,12 @@ class Nip96Uploader { ) } - val merged = - localMetadata?.let { (blur, dim) -> - serverResult.copy( - dimension = dim ?: serverResult.dimension, - blurHash = blur ?: serverResult.blurHash, - ) - } ?: serverResult - - return merged + return localMetadata?.let { (blur, dim) -> + serverResult.copy( + dimension = dim ?: serverResult.dimension, + blurHash = blur ?: serverResult.blurHash, + ) + } ?: serverResult } suspend fun upload(