Private
Public Access
1
0

attachments: proper metadata plumbing

This commit is contained in:
2024-04-08 12:07:34 -07:00
parent 5a148e2b20
commit 20e33f70a8
3 changed files with 39 additions and 58 deletions

View File

@@ -2,55 +2,23 @@ package net.buzzert.kordophonedroid.ui.messagelist
import android.content.Context import android.content.Context
import android.net.Uri import android.net.Uri
import android.util.Log import android.webkit.MimeTypeMap
import androidx.compose.runtime.mutableStateListOf
import androidx.core.net.toFile
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import coil.Coil
import coil.ComponentRegistry
import coil.ImageLoader
import coil.ImageLoaderFactory
import coil.decode.DataSource
import coil.decode.ImageSource
import coil.disk.DiskCache
import coil.fetch.FetchResult
import coil.fetch.Fetcher
import coil.fetch.SourceResult
import coil.memory.MemoryCache
import coil.request.CachePolicy
import coil.request.Options
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asFlow
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.combine import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.first
import kotlinx.coroutines.flow.flowOf
import kotlinx.coroutines.flow.map import kotlinx.coroutines.flow.map
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import net.buzzert.kordophone.backend.model.Conversation import net.buzzert.kordophone.backend.model.Conversation
import net.buzzert.kordophone.backend.model.GUID import net.buzzert.kordophone.backend.model.GUID
import net.buzzert.kordophone.backend.model.Message import net.buzzert.kordophone.backend.model.Message
import net.buzzert.kordophone.backend.model.OutgoingMessage import net.buzzert.kordophone.backend.model.OutgoingMessage
import net.buzzert.kordophone.backend.model.UploadingAttachmentMetadata
import net.buzzert.kordophone.backend.server.ChatRepository import net.buzzert.kordophone.backend.server.ChatRepository
import net.buzzert.kordophonedroid.ui.attachments.AttachmentFetchData
import net.buzzert.kordophonedroid.ui.attachments.AttachmentViewModel
import okio.ByteString.Companion.toByteString
import java.io.BufferedInputStream
import java.io.ByteArrayInputStream
import java.io.InputStream
import java.nio.file.FileSystem
import java.util.Date
import java.util.UUID
import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
import javax.xml.transform.Source
import kotlin.time.Duration.Companion.seconds
const val MVM_LOG: String = "MessageListViewModel" const val MVM_LOG: String = "MessageListViewModel"
@@ -106,7 +74,22 @@ class MessageListViewModel @Inject constructor(
conversation = conversation!!, conversation = conversation!!,
attachmentUris = attachmentUris, attachmentUris = attachmentUris,
attachmentDataSource = { uri -> attachmentDataSource = { uri ->
context.contentResolver.openInputStream(uri) val resolver = context.contentResolver
val inputStream = resolver.openInputStream(uri)
val mimeType = resolver.getType(uri)
val extension = MimeTypeMap.getSingleton().getExtensionFromMimeType(mimeType) ?: "jpg"
val filename = uri.lastPathSegment + ".$extension"
if (inputStream != null && mimeType != null) {
UploadingAttachmentMetadata(
inputStream = inputStream,
mimeType = mimeType,
filename = filename
)
} else {
null
}
} }
) )

View File

@@ -71,11 +71,17 @@ data class Message(
} }
} }
data class UploadingAttachmentMetadata(
val inputStream: InputStream,
val mimeType: String,
val filename: String,
)
data class OutgoingMessage( data class OutgoingMessage(
val body: String, val body: String,
val conversation: Conversation, val conversation: Conversation,
val attachmentUris: Set<Uri>, val attachmentUris: Set<Uri>,
val attachmentDataSource: (Uri) -> InputStream? val attachmentDataSource: (Uri) -> UploadingAttachmentMetadata?
) { ) {
val guid: String = UUID.randomUUID().toString() val guid: String = UUID.randomUUID().toString()

View File

@@ -1,45 +1,37 @@
package net.buzzert.kordophone.backend.server package net.buzzert.kordophone.backend.server
import android.net.Uri import android.net.Uri
import android.os.Build
import android.util.Log import android.util.Log
import androidx.annotation.RequiresApi
import androidx.core.net.toFile
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
import kotlinx.coroutines.cancel
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.channels.consumeEach
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.MutableSharedFlow import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.asSharedFlow import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.collect
import kotlinx.coroutines.flow.collectLatest import kotlinx.coroutines.flow.collectLatest
import kotlinx.coroutines.flow.onEach import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.withContext
import net.buzzert.kordophone.backend.db.CachedChatDatabase import net.buzzert.kordophone.backend.db.CachedChatDatabase
import net.buzzert.kordophone.backend.events.MessageDeliveredEvent import net.buzzert.kordophone.backend.events.MessageDeliveredEvent
import net.buzzert.kordophone.backend.model.Conversation import net.buzzert.kordophone.backend.model.Conversation
import net.buzzert.kordophone.backend.model.GUID import net.buzzert.kordophone.backend.model.GUID
import net.buzzert.kordophone.backend.model.Message import net.buzzert.kordophone.backend.model.Message
import net.buzzert.kordophone.backend.model.OutgoingMessage import net.buzzert.kordophone.backend.model.OutgoingMessage
import net.buzzert.kordophone.backend.model.UpdateItem
import okhttp3.MediaType import okhttp3.MediaType
import okhttp3.OkHttpClient
import okhttp3.RequestBody import okhttp3.RequestBody
import okio.BufferedSource import okio.BufferedSource
import java.io.File
import java.io.InputStream import java.io.InputStream
import java.lang.Error
import java.net.URL
import java.util.Date import java.util.Date
import java.util.Queue
import java.util.UUID import java.util.UUID
import java.util.concurrent.ArrayBlockingQueue import java.util.concurrent.ArrayBlockingQueue
import java.util.concurrent.CancellationException import kotlin.Boolean
import kotlin.Int
import kotlin.String
import kotlin.let
const val REPO_LOG: String = "ChatRepository" const val REPO_LOG: String = "ChatRepository"
const val CONVERSATION_MESSAGE_SYNC_COUNT = 10 const val CONVERSATION_MESSAGE_SYNC_COUNT = 10
@@ -210,7 +202,10 @@ class ChatRepository(
private suspend fun uploadAttachment(filename: String, mediaType: String, source: InputStream): String { private suspend fun uploadAttachment(filename: String, mediaType: String, source: InputStream): String {
val attachmentData = source.readBytes() val attachmentData = source.readBytes()
val requestBody = RequestBody.create(MediaType.get(mediaType), attachmentData) val requestBody = RequestBody.create(MediaType.get(mediaType), attachmentData)
source.close()
withContext(Dispatchers.IO) {
source.close()
}
val response = apiInterface.uploadAttachment(filename, requestBody) val response = apiInterface.uploadAttachment(filename, requestBody)
return response.bodyOnSuccessOrThrow().transferGUID return response.bodyOnSuccessOrThrow().transferGUID
@@ -237,7 +232,7 @@ class ChatRepository(
.onEach { it.conversation = conversation } .onEach { it.conversation = conversation }
} }
private suspend fun handleConversationChangedUpdate(conversation: Conversation) { private fun handleConversationChangedUpdate(conversation: Conversation) {
Log.d(REPO_LOG, "Handling conversation changed update") Log.d(REPO_LOG, "Handling conversation changed update")
database.writeConversations(listOf(conversation)) database.writeConversations(listOf(conversation))
} }
@@ -276,13 +271,10 @@ class ChatRepository(
val attachmentGUIDs = mutableListOf<String>() val attachmentGUIDs = mutableListOf<String>()
try { try {
for (uri: Uri in it.attachmentUris) { for (uri: Uri in it.attachmentUris) {
val inputStream = it.attachmentDataSource(uri) val uploadData = it.attachmentDataSource(uri)
?: throw java.lang.Exception("No input stream") ?: throw java.lang.Exception("No upload data.")
val filename = uri.lastPathSegment ?: "attachment.png"
val mediaType = "image/png" // TODO: Actually get this: it needs to be plumbed through ContentResolver
val guid = uploadAttachment(filename, mediaType, inputStream)
val guid = uploadAttachment(uploadData.filename, uploadData.mimeType, uploadData.inputStream)
attachmentGUIDs.add(guid) attachmentGUIDs.add(guid)
} }
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {