mirror of
https://github.com/ouchadam/small-talk.git
synced 2025-01-03 05:22:39 +01:00
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:
parent
02e76548e3
commit
ccdeed24ef
@ -8,6 +8,7 @@ import android.content.Intent
|
|||||||
import android.graphics.BitmapFactory
|
import android.graphics.BitmapFactory
|
||||||
import android.net.Uri
|
import android.net.Uri
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
|
import android.provider.OpenableColumns
|
||||||
import app.dapk.db.DapkDb
|
import app.dapk.db.DapkDb
|
||||||
import app.dapk.st.BuildConfig
|
import app.dapk.st.BuildConfig
|
||||||
import app.dapk.st.SharedPreferencesDelegate
|
import app.dapk.st.SharedPreferencesDelegate
|
||||||
@ -59,7 +60,7 @@ import app.dapk.st.work.TaskRunnerModule
|
|||||||
import app.dapk.st.work.WorkModule
|
import app.dapk.st.work.WorkModule
|
||||||
import com.squareup.sqldelight.android.AndroidSqliteDriver
|
import com.squareup.sqldelight.android.AndroidSqliteDriver
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import java.net.URI
|
import java.io.InputStream
|
||||||
import java.time.Clock
|
import java.time.Clock
|
||||||
|
|
||||||
internal class AppModule(context: Application, logger: MatrixLogger) {
|
internal class AppModule(context: Application, logger: MatrixLogger) {
|
||||||
@ -303,6 +304,7 @@ internal class MatrixModules(
|
|||||||
val result = cryptoService.encrypt(input)
|
val result = cryptoService.encrypt(input)
|
||||||
MediaEncrypter.Result(
|
MediaEncrypter.Result(
|
||||||
uri = result.uri,
|
uri = result.uri,
|
||||||
|
contentLength = result.contentLength,
|
||||||
algorithm = result.algorithm,
|
algorithm = result.algorithm,
|
||||||
ext = result.ext,
|
ext = result.ext,
|
||||||
keyOperations = result.keyOperations,
|
keyOperations = result.keyOperations,
|
||||||
@ -482,23 +484,27 @@ internal class DomainModules(
|
|||||||
}
|
}
|
||||||
|
|
||||||
internal class AndroidImageContentReader(private val contentResolver: ContentResolver) : ImageContentReader {
|
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 androidUri = Uri.parse(uri)
|
||||||
val fileStream = contentResolver.openInputStream(androidUri) ?: throw IllegalArgumentException("Could not process $uri")
|
val fileStream = contentResolver.openInputStream(androidUri) ?: throw IllegalArgumentException("Could not process $uri")
|
||||||
|
|
||||||
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
|
||||||
BitmapFactory.decodeStream(fileStream, null, options)
|
BitmapFactory.decodeStream(fileStream, null, options)
|
||||||
|
|
||||||
return contentResolver.openInputStream(androidUri)?.use { stream ->
|
val fileSize = contentResolver.query(androidUri, null, null, null, null)?.use { cursor ->
|
||||||
val output = stream.readBytes()
|
cursor.moveToFirst()
|
||||||
ImageContentReader.ImageContent(
|
val columnIndex = cursor.getColumnIndex(OpenableColumns.SIZE)
|
||||||
height = options.outHeight,
|
cursor.getLong(columnIndex)
|
||||||
width = options.outWidth,
|
|
||||||
size = output.size.toLong(),
|
|
||||||
mimeType = options.outMimeType,
|
|
||||||
fileName = androidUri.lastPathSegment ?: "file",
|
|
||||||
uri = URI.create(uri)
|
|
||||||
)
|
|
||||||
} ?: throw IllegalArgumentException("Could not process $uri")
|
} ?: 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))!!
|
||||||
}
|
}
|
@ -42,6 +42,7 @@ interface Crypto {
|
|||||||
|
|
||||||
data class MediaEncryptionResult(
|
data class MediaEncryptionResult(
|
||||||
val uri: URI,
|
val uri: URI,
|
||||||
|
val contentLength: Long,
|
||||||
val algorithm: String,
|
val algorithm: String,
|
||||||
val ext: Boolean,
|
val ext: Boolean,
|
||||||
val keyOperations: List<String>,
|
val keyOperations: List<String>,
|
||||||
|
@ -63,6 +63,7 @@ class MediaEncrypter(private val base64: Base64) {
|
|||||||
|
|
||||||
return Crypto.MediaEncryptionResult(
|
return Crypto.MediaEncryptionResult(
|
||||||
uri = outputFile.toURI(),
|
uri = outputFile.toURI(),
|
||||||
|
contentLength = outputFile.length(),
|
||||||
algorithm = "A256CTR",
|
algorithm = "A256CTR",
|
||||||
ext = true,
|
ext = true,
|
||||||
keyOperations = listOf("encrypt", "decrypt"),
|
keyOperations = listOf("encrypt", "decrypt"),
|
||||||
|
@ -10,6 +10,7 @@ fun interface MediaEncrypter {
|
|||||||
|
|
||||||
data class Result(
|
data class Result(
|
||||||
val uri: URI,
|
val uri: URI,
|
||||||
|
val contentLength: Long,
|
||||||
val algorithm: String,
|
val algorithm: String,
|
||||||
val ext: Boolean,
|
val ext: Boolean,
|
||||||
val keyOperations: List<String>,
|
val keyOperations: List<String>,
|
||||||
@ -20,7 +21,7 @@ fun interface MediaEncrypter {
|
|||||||
val v: String,
|
val v: String,
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun openStream() = File(uri).outputStream()
|
fun openStream() = File(uri).inputStream()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -1,10 +1,10 @@
|
|||||||
package app.dapk.st.matrix.message.internal
|
package app.dapk.st.matrix.message.internal
|
||||||
|
|
||||||
import java.io.File
|
import java.io.InputStream
|
||||||
import java.net.URI
|
|
||||||
|
|
||||||
interface ImageContentReader {
|
interface ImageContentReader {
|
||||||
fun read(uri: String): ImageContent
|
fun meta(uri: String): ImageContent
|
||||||
|
fun inputStream(uri: String): InputStream
|
||||||
|
|
||||||
data class ImageContent(
|
data class ImageContent(
|
||||||
val height: Int,
|
val height: Int,
|
||||||
@ -12,9 +12,5 @@ interface ImageContentReader {
|
|||||||
val size: Long,
|
val size: Long,
|
||||||
val fileName: String,
|
val fileName: String,
|
||||||
val mimeType: String,
|
val mimeType: String,
|
||||||
val uri: URI
|
)
|
||||||
) {
|
|
||||||
fun inputStream() = File(uri).inputStream()
|
|
||||||
fun outputStream() = File(uri).outputStream()
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -7,8 +7,6 @@ import app.dapk.st.matrix.message.ApiSendResponse
|
|||||||
import app.dapk.st.matrix.message.MediaEncrypter
|
import app.dapk.st.matrix.message.MediaEncrypter
|
||||||
import app.dapk.st.matrix.message.MessageEncrypter
|
import app.dapk.st.matrix.message.MessageEncrypter
|
||||||
import app.dapk.st.matrix.message.MessageService.Message
|
import app.dapk.st.matrix.message.MessageService.Message
|
||||||
import java.io.ByteArrayOutputStream
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
internal class SendMessageUseCase(
|
internal class SendMessageUseCase(
|
||||||
private val httpClient: MatrixHttpClient,
|
private val httpClient: MatrixHttpClient,
|
||||||
@ -60,18 +58,23 @@ internal class SendMessageUseCase(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun ApiMessageMapper.imageMessageRequest(message: Message.ImageMessage): HttpRequest<ApiSendResponse> {
|
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) {
|
return when (message.sendEncrypted) {
|
||||||
true -> {
|
true -> {
|
||||||
val result = mediaEncrypter.encrypt(imageContent.inputStream())
|
val result = mediaEncrypter.encrypt(imageContentReader.inputStream(message.content.uri))
|
||||||
val bytes = File(result.uri).readBytes()
|
val uri = httpClient.execute(
|
||||||
|
uploadRequest(
|
||||||
val uri = httpClient.execute(uploadRequest(bytes, imageContent.fileName, "application/octet-stream")).contentUri
|
result.openStream(),
|
||||||
|
result.contentLength,
|
||||||
|
imageMeta.fileName,
|
||||||
|
"application/octet-stream"
|
||||||
|
)
|
||||||
|
).contentUri
|
||||||
|
|
||||||
val content = ApiMessage.ImageMessage.ImageContent(
|
val content = ApiMessage.ImageMessage.ImageContent(
|
||||||
url = null,
|
url = null,
|
||||||
filename = imageContent.fileName,
|
filename = imageMeta.fileName,
|
||||||
file = ApiMessage.ImageMessage.ImageContent.File(
|
file = ApiMessage.ImageMessage.ImageContent.File(
|
||||||
url = uri,
|
url = uri,
|
||||||
key = ApiMessage.ImageMessage.ImageContent.File.EncryptionMeta(
|
key = ApiMessage.ImageMessage.ImageContent.File.EncryptionMeta(
|
||||||
@ -86,9 +89,9 @@ internal class SendMessageUseCase(
|
|||||||
v = result.v,
|
v = result.v,
|
||||||
),
|
),
|
||||||
info = ApiMessage.ImageMessage.ImageContent.Info(
|
info = ApiMessage.ImageMessage.ImageContent.Info(
|
||||||
height = imageContent.height,
|
height = imageMeta.height,
|
||||||
width = imageContent.width,
|
width = imageMeta.width,
|
||||||
size = imageContent.size
|
size = imageMeta.size
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -113,20 +116,25 @@ internal class SendMessageUseCase(
|
|||||||
}
|
}
|
||||||
|
|
||||||
false -> {
|
false -> {
|
||||||
val bytes = File(imageContent.uri).readBytes()
|
val uri = httpClient.execute(
|
||||||
|
uploadRequest(
|
||||||
val uri = httpClient.execute(uploadRequest(bytes, imageContent.fileName, imageContent.mimeType)).contentUri
|
imageContentReader.inputStream(message.content.uri),
|
||||||
|
imageMeta.size,
|
||||||
|
imageMeta.fileName,
|
||||||
|
imageMeta.mimeType
|
||||||
|
)
|
||||||
|
).contentUri
|
||||||
sendRequest(
|
sendRequest(
|
||||||
roomId = message.roomId,
|
roomId = message.roomId,
|
||||||
eventType = EventType.ROOM_MESSAGE,
|
eventType = EventType.ROOM_MESSAGE,
|
||||||
txId = message.localId,
|
txId = message.localId,
|
||||||
content = ApiMessage.ImageMessage.ImageContent(
|
content = ApiMessage.ImageMessage.ImageContent(
|
||||||
url = uri,
|
url = uri,
|
||||||
filename = imageContent.fileName,
|
filename = imageMeta.fileName,
|
||||||
ApiMessage.ImageMessage.ImageContent.Info(
|
ApiMessage.ImageMessage.ImageContent.Info(
|
||||||
height = imageContent.height,
|
height = imageMeta.height,
|
||||||
width = imageContent.width,
|
width = imageMeta.width,
|
||||||
size = imageContent.size
|
size = imageMeta.size
|
||||||
)
|
)
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
@ -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.MessageService.EventMessage
|
||||||
import app.dapk.st.matrix.message.internal.ApiMessage.ImageMessage
|
import app.dapk.st.matrix.message.internal.ApiMessage.ImageMessage
|
||||||
import app.dapk.st.matrix.message.internal.ApiMessage.TextMessage
|
import app.dapk.st.matrix.message.internal.ApiMessage.TextMessage
|
||||||
import io.ktor.content.*
|
|
||||||
import io.ktor.http.*
|
import io.ktor.http.*
|
||||||
|
import io.ktor.http.content.*
|
||||||
|
import io.ktor.utils.io.jvm.javaio.*
|
||||||
|
import java.io.InputStream
|
||||||
import java.util.*
|
import java.util.*
|
||||||
|
|
||||||
internal fun sendRequest(roomId: RoomId, eventType: EventType, txId: String, content: ApiMessageContent) = httpRequest<ApiSendResponse>(
|
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",
|
path = "_matrix/media/r0/upload/?filename=$filename",
|
||||||
headers = listOf("Content-Type" to contentType),
|
headers = listOf("Content-Type" to contentType),
|
||||||
method = MatrixHttpClient.Method.POST,
|
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()}"
|
fun txId() = "local.${UUID.randomUUID()}"
|
@ -87,7 +87,6 @@ class SmokeTest {
|
|||||||
bob.expectImageMessage(SharedState.sharedRoom, testImage, SharedState.alice.roomMember)
|
bob.expectImageMessage(SharedState.sharedRoom, testImage, SharedState.alice.roomMember)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Order(8)
|
@Order(8)
|
||||||
fun `can request and verify devices`() = testAfterInitialSync { alice, bob ->
|
fun `can request and verify devices`() = testAfterInitialSync { alice, bob ->
|
||||||
|
@ -146,6 +146,7 @@ class TestMatrix(
|
|||||||
val result = cryptoService.encrypt(input)
|
val result = cryptoService.encrypt(input)
|
||||||
MediaEncrypter.Result(
|
MediaEncrypter.Result(
|
||||||
uri = result.uri,
|
uri = result.uri,
|
||||||
|
contentLength = result.contentLength,
|
||||||
algorithm = result.algorithm,
|
algorithm = result.algorithm,
|
||||||
ext = result.ext,
|
ext = result.ext,
|
||||||
keyOperations = result.keyOperations,
|
keyOperations = result.keyOperations,
|
||||||
@ -339,7 +340,7 @@ class JavaBase64 : Base64 {
|
|||||||
|
|
||||||
class JavaImageContentReader : ImageContentReader {
|
class JavaImageContentReader : ImageContentReader {
|
||||||
|
|
||||||
override fun read(uri: String): ImageContentReader.ImageContent {
|
override fun meta(uri: String): ImageContentReader.ImageContent {
|
||||||
val file = File(uri)
|
val file = File(uri)
|
||||||
val size = file.length()
|
val size = file.length()
|
||||||
val image = ImageIO.read(file)
|
val image = ImageIO.read(file)
|
||||||
@ -349,8 +350,9 @@ class JavaImageContentReader : ImageContentReader {
|
|||||||
size = size,
|
size = size,
|
||||||
mimeType = "image/${file.extension}",
|
mimeType = "image/${file.extension}",
|
||||||
fileName = file.name,
|
fileName = file.name,
|
||||||
uri = file.toURI(),
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun inputStream(uri: String) = File(uri).inputStream()
|
||||||
|
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user