code simplification and deduplication
This commit is contained in:
+27
-18
@@ -31,10 +31,24 @@ import com.vitorpamplona.quartz.nip94FileMetadata.tags.DimensionTag
|
|||||||
import com.vitorpamplona.quartz.utils.Log
|
import com.vitorpamplona.quartz.utils.Log
|
||||||
|
|
||||||
object BlurhashMetadataCalculator {
|
object BlurhashMetadataCalculator {
|
||||||
fun shouldAttempt(mimeType: String?): Boolean =
|
private fun isImage(mimeType: String?) = mimeType?.startsWith("image/", ignoreCase = true) == true
|
||||||
mimeType?.let {
|
|
||||||
it.startsWith("image/", ignoreCase = true) || it.startsWith("video/", ignoreCase = true)
|
private fun isVideo(mimeType: String?) = mimeType?.startsWith("video/", ignoreCase = true) == true
|
||||||
} ?: false
|
|
||||||
|
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<BlurhashWrapper?, DimensionTag?> {
|
||||||
|
val (blur, dim) = processBitmap(bitmap)
|
||||||
|
return blur to (dim ?: dimPrecomputed)
|
||||||
|
}
|
||||||
|
|
||||||
fun computeFromBytes(
|
fun computeFromBytes(
|
||||||
data: ByteArray,
|
data: ByteArray,
|
||||||
@@ -42,14 +56,11 @@ object BlurhashMetadataCalculator {
|
|||||||
dimPrecomputed: DimensionTag?,
|
dimPrecomputed: DimensionTag?,
|
||||||
): Pair<BlurhashWrapper?, DimensionTag?> =
|
): Pair<BlurhashWrapper?, DimensionTag?> =
|
||||||
when {
|
when {
|
||||||
mimeType?.startsWith("image/", ignoreCase = true) == true -> {
|
isImage(mimeType) -> {
|
||||||
val options = BitmapFactory.Options()
|
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size, createBitmapOptions())
|
||||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888
|
processImage(bitmap, dimPrecomputed)
|
||||||
val bitmap = BitmapFactory.decodeByteArray(data, 0, data.size, options)
|
|
||||||
val (blur, dim) = processBitmap(bitmap)
|
|
||||||
blur to (dim ?: dimPrecomputed)
|
|
||||||
}
|
}
|
||||||
mimeType?.startsWith("video/", ignoreCase = true) == true -> {
|
isVideo(mimeType) -> {
|
||||||
val retriever = MediaMetadataRetriever()
|
val retriever = MediaMetadataRetriever()
|
||||||
try {
|
try {
|
||||||
retriever.setDataSource(ByteArrayMediaDataSource(data))
|
retriever.setDataSource(ByteArrayMediaDataSource(data))
|
||||||
@@ -71,16 +82,13 @@ object BlurhashMetadataCalculator {
|
|||||||
|
|
||||||
return try {
|
return try {
|
||||||
when {
|
when {
|
||||||
mimeType?.startsWith("image/", ignoreCase = true) == true ->
|
isImage(mimeType) ->
|
||||||
context.contentResolver.openInputStream(uri)?.use { stream ->
|
context.contentResolver.openInputStream(uri)?.use { stream ->
|
||||||
val options = BitmapFactory.Options()
|
val bitmap = BitmapFactory.decodeStream(stream, null, createBitmapOptions())
|
||||||
options.inPreferredConfig = Bitmap.Config.ARGB_8888
|
processImage(bitmap, dimPrecomputed)
|
||||||
val bitmap = BitmapFactory.decodeStream(stream, null, options)
|
|
||||||
val (blur, dim) = processBitmap(bitmap)
|
|
||||||
blur to (dim ?: dimPrecomputed)
|
|
||||||
} ?: (null to dimPrecomputed)
|
} ?: (null to dimPrecomputed)
|
||||||
|
|
||||||
mimeType?.startsWith("video/", ignoreCase = true) == true -> {
|
isVideo(mimeType) -> {
|
||||||
val retriever = MediaMetadataRetriever()
|
val retriever = MediaMetadataRetriever()
|
||||||
try {
|
try {
|
||||||
retriever.setDataSource(context, uri)
|
retriever.setDataSource(context, uri)
|
||||||
@@ -89,6 +97,7 @@ object BlurhashMetadataCalculator {
|
|||||||
retriever.release()
|
retriever.release()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> null
|
else -> null
|
||||||
}
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
|||||||
+24
-26
@@ -232,35 +232,33 @@ class UploadOrchestrator {
|
|||||||
updateState(0.6, UploadingState.Downloading)
|
updateState(0.6, UploadingState.Downloading)
|
||||||
|
|
||||||
// Use streaming verification for memory efficiency with large files
|
// 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
|
// Create FileHeader with hash from streaming verification
|
||||||
// Note: We skip blurhash/dimensions since we already have them from upload
|
// Note: We skip blurhash/dimensions since we already have them from upload
|
||||||
val fileHeader =
|
val fileHeader =
|
||||||
FileHeader(
|
FileHeader(
|
||||||
mimeType = uploadResult.type ?: localContentType ?: verification.contentType,
|
mimeType = uploadResult.type ?: localContentType ?: verification.contentType,
|
||||||
hash = verification.hash,
|
hash = verification.hash,
|
||||||
size = verification.size.toInt(),
|
size = verification.size.toInt(),
|
||||||
dim = uploadResult.dimension,
|
dim = uploadResult.dimension,
|
||||||
blurHash = uploadResult.blurHash,
|
blurHash = uploadResult.blurHash,
|
||||||
)
|
|
||||||
|
|
||||||
return finish(
|
|
||||||
OrchestratorResult.ServerResult(
|
|
||||||
fileHeader,
|
|
||||||
uploadResult.url,
|
|
||||||
uploadResult.magnet,
|
|
||||||
uploadResult.sha256,
|
|
||||||
originalContentType,
|
|
||||||
originalHash,
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
} 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 {
|
sealed class OrchestratorResult {
|
||||||
|
|||||||
+6
-20
@@ -52,20 +52,6 @@ import java.io.InputStream
|
|||||||
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 (hash, size) = sha256StreamWithCount(inputStream)
|
|
||||||
return StreamInfo(hash.toHexKey(), size)
|
|
||||||
}
|
|
||||||
|
|
||||||
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)
|
||||||
@@ -100,23 +86,23 @@ class BlossomUploader {
|
|||||||
val imageInputStreamForHash = contentResolver.openInputStream(uri)
|
val imageInputStreamForHash = contentResolver.openInputStream(uri)
|
||||||
checkNotNull(imageInputStreamForHash) { "Can't open the image input stream" }
|
checkNotNull(imageInputStreamForHash) { "Can't open the image input stream" }
|
||||||
|
|
||||||
val streamInfo =
|
val (hash, size) =
|
||||||
imageInputStreamForHash.use {
|
imageInputStreamForHash.use { stream ->
|
||||||
calculateHashAndSize(it)
|
val (hashBytes, totalBytes) = sha256StreamWithCount(stream)
|
||||||
|
hashBytes.toHexKey() to totalBytes
|
||||||
}
|
}
|
||||||
|
|
||||||
val localMetadata = BlurhashMetadataCalculator.computeFromUri(context, uri, myContentType)
|
val localMetadata = BlurhashMetadataCalculator.computeFromUri(context, uri, myContentType)
|
||||||
|
|
||||||
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" }
|
||||||
|
|
||||||
val serverResult =
|
val serverResult =
|
||||||
imageInputStream.use { stream ->
|
imageInputStream.use { stream ->
|
||||||
upload(
|
upload(
|
||||||
stream,
|
stream,
|
||||||
streamInfo.hash,
|
hash,
|
||||||
streamInfo.size,
|
size,
|
||||||
fileName,
|
fileName,
|
||||||
myContentType,
|
myContentType,
|
||||||
alt,
|
alt,
|
||||||
|
|||||||
+6
-9
@@ -128,15 +128,12 @@ class Nip96Uploader {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
val merged =
|
return localMetadata?.let { (blur, dim) ->
|
||||||
localMetadata?.let { (blur, dim) ->
|
serverResult.copy(
|
||||||
serverResult.copy(
|
dimension = dim ?: serverResult.dimension,
|
||||||
dimension = dim ?: serverResult.dimension,
|
blurHash = blur ?: serverResult.blurHash,
|
||||||
blurHash = blur ?: serverResult.blurHash,
|
)
|
||||||
)
|
} ?: serverResult
|
||||||
} ?: serverResult
|
|
||||||
|
|
||||||
return merged
|
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun upload(
|
suspend fun upload(
|
||||||
|
|||||||
Reference in New Issue
Block a user