using separate content provider query to find image file size rather than collect the content of the image itself into memory, should be much more effecient!

This commit is contained in:
Adam Brown 2022-09-21 23:29:01 +01:00 committed by Adam Brown
parent 5472b41d73
commit b4878aa2c6
9 changed files with 65 additions and 45 deletions

View File

@ -8,6 +8,7 @@ import android.content.Intent
import android.graphics.BitmapFactory
import android.net.Uri
import android.os.Build
import android.provider.OpenableColumns
import app.dapk.db.DapkDb
import app.dapk.st.BuildConfig
import app.dapk.st.SharedPreferencesDelegate
@ -59,7 +60,7 @@ import app.dapk.st.work.TaskRunnerModule
import app.dapk.st.work.WorkModule
import com.squareup.sqldelight.android.AndroidSqliteDriver
import kotlinx.coroutines.Dispatchers
import java.net.URI
import java.io.InputStream
import java.time.Clock
internal class AppModule(context: Application, logger: MatrixLogger) {
@ -303,6 +304,7 @@ internal class MatrixModules(
val result = cryptoService.encrypt(input)
MediaEncrypter.Result(
uri = result.uri,
contentLength = result.contentLength,
algorithm = result.algorithm,
ext = result.ext,
keyOperations = result.keyOperations,
@ -482,23 +484,27 @@ internal class DomainModules(
}
internal class AndroidImageContentReader(private val contentResolver: ContentResolver) : ImageContentReader {
override fun read(uri: String): ImageContentReader.ImageContent {
override fun meta(uri: String): ImageContentReader.ImageContent {
val androidUri = Uri.parse(uri)
val fileStream = contentResolver.openInputStream(androidUri) ?: throw IllegalArgumentException("Could not process $uri")
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeStream(fileStream, null, options)
return contentResolver.openInputStream(androidUri)?.use { stream ->
val output = stream.readBytes()
ImageContentReader.ImageContent(
height = options.outHeight,
width = options.outWidth,
size = output.size.toLong(),
mimeType = options.outMimeType,
fileName = androidUri.lastPathSegment ?: "file",
uri = URI.create(uri)
)
val fileSize = contentResolver.query(androidUri, null, null, null, null)?.use { cursor ->
cursor.moveToFirst()
val columnIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
cursor.getLong(columnIndex)
} ?: throw IllegalArgumentException("Could not process $uri")
return ImageContentReader.ImageContent(
height = options.outHeight,
width = options.outWidth,
size = fileSize,
mimeType = options.outMimeType,
fileName = androidUri.lastPathSegment ?: "file",
)
}
override fun inputStream(uri: String): InputStream = contentResolver.openInputStream(Uri.parse(uri))!!
}

View File

@ -42,6 +42,7 @@ interface Crypto {
data class MediaEncryptionResult(
val uri: URI,
val contentLength: Long,
val algorithm: String,
val ext: Boolean,
val keyOperations: List<String>,

View File

@ -63,6 +63,7 @@ class MediaEncrypter(private val base64: Base64) {
return Crypto.MediaEncryptionResult(
uri = outputFile.toURI(),
contentLength = outputFile.length(),
algorithm = "A256CTR",
ext = true,
keyOperations = listOf("encrypt", "decrypt"),

View File

@ -10,6 +10,7 @@ fun interface MediaEncrypter {
data class Result(
val uri: URI,
val contentLength: Long,
val algorithm: String,
val ext: Boolean,
val keyOperations: List<String>,
@ -20,7 +21,7 @@ fun interface MediaEncrypter {
val v: String,
) {
fun openStream() = File(uri).outputStream()
fun openStream() = File(uri).inputStream()
}
}

View File

@ -1,10 +1,10 @@
package app.dapk.st.matrix.message.internal
import java.io.File
import java.net.URI
import java.io.InputStream
interface ImageContentReader {
fun read(uri: String): ImageContent
fun meta(uri: String): ImageContent
fun inputStream(uri: String): InputStream
data class ImageContent(
val height: Int,
@ -12,9 +12,5 @@ interface ImageContentReader {
val size: Long,
val fileName: String,
val mimeType: String,
val uri: URI
) {
fun inputStream() = File(uri).inputStream()
fun outputStream() = File(uri).outputStream()
}
)
}

View File

@ -7,8 +7,6 @@ import app.dapk.st.matrix.message.ApiSendResponse
import app.dapk.st.matrix.message.MediaEncrypter
import app.dapk.st.matrix.message.MessageEncrypter
import app.dapk.st.matrix.message.MessageService.Message
import java.io.ByteArrayOutputStream
import java.io.File
internal class SendMessageUseCase(
private val httpClient: MatrixHttpClient,
@ -60,18 +58,23 @@ internal class SendMessageUseCase(
}
private suspend fun ApiMessageMapper.imageMessageRequest(message: Message.ImageMessage): HttpRequest<ApiSendResponse> {
val imageContent = imageContentReader.read(message.content.uri)
val imageMeta = imageContentReader.meta(message.content.uri)
return when (message.sendEncrypted) {
true -> {
val result = mediaEncrypter.encrypt(imageContent.inputStream())
val bytes = File(result.uri).readBytes()
val uri = httpClient.execute(uploadRequest(bytes, imageContent.fileName, "application/octet-stream")).contentUri
val result = mediaEncrypter.encrypt(imageContentReader.inputStream(message.content.uri))
val uri = httpClient.execute(
uploadRequest(
result.openStream(),
result.contentLength,
imageMeta.fileName,
"application/octet-stream"
)
).contentUri
val content = ApiMessage.ImageMessage.ImageContent(
url = null,
filename = imageContent.fileName,
filename = imageMeta.fileName,
file = ApiMessage.ImageMessage.ImageContent.File(
url = uri,
key = ApiMessage.ImageMessage.ImageContent.File.EncryptionMeta(
@ -86,9 +89,9 @@ internal class SendMessageUseCase(
v = result.v,
),
info = ApiMessage.ImageMessage.ImageContent.Info(
height = imageContent.height,
width = imageContent.width,
size = imageContent.size
height = imageMeta.height,
width = imageMeta.width,
size = imageMeta.size
)
)
@ -113,20 +116,25 @@ internal class SendMessageUseCase(
}
false -> {
val bytes = File(imageContent.uri).readBytes()
val uri = httpClient.execute(uploadRequest(bytes, imageContent.fileName, imageContent.mimeType)).contentUri
val uri = httpClient.execute(
uploadRequest(
imageContentReader.inputStream(message.content.uri),
imageMeta.size,
imageMeta.fileName,
imageMeta.mimeType
)
).contentUri
sendRequest(
roomId = message.roomId,
eventType = EventType.ROOM_MESSAGE,
txId = message.localId,
content = ApiMessage.ImageMessage.ImageContent(
url = uri,
filename = imageContent.fileName,
filename = imageMeta.fileName,
ApiMessage.ImageMessage.ImageContent.Info(
height = imageContent.height,
width = imageContent.width,
size = imageContent.size
height = imageMeta.height,
width = imageMeta.width,
size = imageMeta.size
)
),
)

View File

@ -11,8 +11,10 @@ import app.dapk.st.matrix.message.MessageEncrypter
import app.dapk.st.matrix.message.MessageService.EventMessage
import app.dapk.st.matrix.message.internal.ApiMessage.ImageMessage
import app.dapk.st.matrix.message.internal.ApiMessage.TextMessage
import io.ktor.content.*
import io.ktor.http.*
import io.ktor.http.content.*
import io.ktor.utils.io.jvm.javaio.*
import java.io.InputStream
import java.util.*
internal fun sendRequest(roomId: RoomId, eventType: EventType, txId: String, content: ApiMessageContent) = httpRequest<ApiSendResponse>(
@ -38,11 +40,15 @@ internal fun sendRequest(roomId: RoomId, eventType: EventType, content: EventMes
}
)
internal fun uploadRequest(body: ByteArray, filename: String, contentType: String) = httpRequest<ApiUploadResponse>(
internal fun uploadRequest(stream: InputStream, contentLength: Long, filename: String, contentType: String) = httpRequest<ApiUploadResponse>(
path = "_matrix/media/r0/upload/?filename=$filename",
headers = listOf("Content-Type" to contentType),
method = MatrixHttpClient.Method.POST,
body = ByteArrayContent(body, ContentType.parse(contentType)),
body = ChannelWriterContent(
body = { stream.copyTo(this) },
contentType = ContentType.parse(contentType),
contentLength = contentLength,
),
)
fun txId() = "local.${UUID.randomUUID()}"

View File

@ -87,7 +87,6 @@ class SmokeTest {
bob.expectImageMessage(SharedState.sharedRoom, testImage, SharedState.alice.roomMember)
}
@Test
@Order(8)
fun `can request and verify devices`() = testAfterInitialSync { alice, bob ->

View File

@ -146,6 +146,7 @@ class TestMatrix(
val result = cryptoService.encrypt(input)
MediaEncrypter.Result(
uri = result.uri,
contentLength = result.contentLength,
algorithm = result.algorithm,
ext = result.ext,
keyOperations = result.keyOperations,
@ -339,7 +340,7 @@ class JavaBase64 : Base64 {
class JavaImageContentReader : ImageContentReader {
override fun read(uri: String): ImageContentReader.ImageContent {
override fun meta(uri: String): ImageContentReader.ImageContent {
val file = File(uri)
val size = file.length()
val image = ImageIO.read(file)
@ -349,8 +350,9 @@ class JavaImageContentReader : ImageContentReader {
size = size,
mimeType = "image/${file.extension}",
fileName = file.name,
uri = file.toURI(),
)
}
override fun inputStream(uri: String) = File(uri).inputStream()
}