WIP: make attachment work

This commit is contained in:
Ryan ZHAO 2021-03-02 17:22:56 +11:00
parent 4a9ac91e5f
commit 0ea1ed15e7
10 changed files with 52 additions and 38 deletions

View File

@ -6,6 +6,7 @@ import org.greenrobot.eventbus.EventBus
import org.session.libsession.database.MessageDataProvider import org.session.libsession.database.MessageDataProvider
import org.session.libsession.messaging.sending_receiving.attachments.* import org.session.libsession.messaging.sending_receiving.attachments.*
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.libsignal.util.guava.Optional
import org.session.libsignal.service.api.messages.SignalServiceAttachment import org.session.libsignal.service.api.messages.SignalServiceAttachment
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
@ -51,12 +52,6 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
attachmentDatabase.setTransferState(messageID, AttachmentId(attachmentId, 0), attachmentState.value) attachmentDatabase.setTransferState(messageID, AttachmentId(attachmentId, 0), attachmentState.value)
} }
@Throws(Exception::class)
override fun uploadAttachment(attachmentId: Long) {
val attachmentUploadJob = AttachmentUploadJob(AttachmentId(attachmentId, 0), null)
attachmentUploadJob.onRun()
}
override fun getMessageForQuote(timestamp: Long, author: Address): Long? { override fun getMessageForQuote(timestamp: Long, author: Address): Long? {
val messagingDatabase = DatabaseFactory.getMmsSmsDatabase(context) val messagingDatabase = DatabaseFactory.getMmsSmsDatabase(context)
return messagingDatabase.getMessageFor(timestamp, author)?.id return messagingDatabase.getMessageFor(timestamp, author)?.id
@ -72,6 +67,12 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
return messagingDatabase.getMessage(messageID).body return messagingDatabase.getMessage(messageID).body
} }
override fun getAttachmentIDsFor(messageID: Long): List<Long> {
return DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageID).map {
it.attachmentId.rowId
}
}
override fun insertAttachment(messageId: Long, attachmentId: Long, stream: InputStream) { override fun insertAttachment(messageId: Long, attachmentId: Long, stream: InputStream) {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context) val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
attachmentDatabase.insertAttachmentsForPlaceholder(messageId, AttachmentId(attachmentId, 0), stream) attachmentDatabase.insertAttachmentsForPlaceholder(messageId, AttachmentId(attachmentId, 0), stream)
@ -83,6 +84,14 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp) return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp)
} }
override fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, uploadResult: DotNetAPI.UploadResult) {
TODO("Not yet implemented")
}
override fun updateAttachmentAfterUploadFailed(attachmentId: Long) {
TODO("Not yet implemented")
}
override fun getMessageID(serverID: Long): Long? { override fun getMessageID(serverID: Long): Long? {
val openGroupMessagingDatabase = DatabaseFactory.getLokiMessageDatabase(context) val openGroupMessagingDatabase = DatabaseFactory.getLokiMessageDatabase(context)
return openGroupMessagingDatabase.getMessageID(serverID) return openGroupMessagingDatabase.getMessageID(serverID)
@ -93,6 +102,11 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
messagingDatabase.deleteMessage(messageID) messagingDatabase.deleteMessage(messageID)
} }
override fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
return attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0))
}
} }
fun DatabaseAttachment.toAttachmentPointer(): SessionServiceAttachmentPointer { fun DatabaseAttachment.toAttachmentPointer(): SessionServiceAttachmentPointer {

View File

@ -1806,7 +1806,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} else { } else {
allocatedThreadId = threadId; allocatedThreadId = threadId;
} }
DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMessage, allocatedThreadId, false, ()->fragment.releaseOutgoingMessage(id)); message.setId(DatabaseFactory.getMmsDatabase(context).insertMessageOutbox(outgoingMessage, allocatedThreadId, false, ()->fragment.releaseOutgoingMessage(id)));
MessageSender.send(message, recipient.getAddress(), attachments, quote, linkPreview.orNull()); MessageSender.send(message, recipient.getAddress(), attachments, quote, linkPreview.orNull());
sendComplete(allocatedThreadId); sendComplete(allocatedThreadId);
} catch (MmsException e) { } catch (MmsException e) {
@ -1843,7 +1843,7 @@ public class ConversationActivity extends PassphraseRequiredActionBarActivity
} else { } else {
allocatedThreadId = threadId; allocatedThreadId = threadId;
} }
DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(allocatedThreadId, outgoingTextMessage, false, message.getSentTimestamp(), ()->fragment.releaseOutgoingMessage(id)); message.setId(DatabaseFactory.getSmsDatabase(context).insertMessageOutbox(allocatedThreadId, outgoingTextMessage, false, message.getSentTimestamp(), ()->fragment.releaseOutgoingMessage(id)));
MessageSender.send(message, recipient.getAddress()); MessageSender.send(message, recipient.getAddress());
sendComplete(allocatedThreadId); sendComplete(allocatedThreadId);

View File

@ -121,6 +121,7 @@ public class AttachmentDatabase extends Database {
static final String AUDIO_DURATION = "audio_duration"; // Duration of the audio track in milliseconds. static final String AUDIO_DURATION = "audio_duration"; // Duration of the audio track in milliseconds.
private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?"; private static final String PART_ID_WHERE = ROW_ID + " = ? AND " + UNIQUE_ID + " = ?";
private static final String ROW_ID_WHERE = ROW_ID + " = ?";
private static final String PART_AUDIO_ONLY_WHERE = CONTENT_TYPE + " LIKE \"audio/%\""; private static final String PART_AUDIO_ONLY_WHERE = CONTENT_TYPE + " LIKE \"audio/%\"";
private static final String[] PROJECTION = new String[] {ROW_ID, private static final String[] PROJECTION = new String[] {ROW_ID,
@ -221,7 +222,7 @@ public class AttachmentDatabase extends Database {
Cursor cursor = null; Cursor cursor = null;
try { try {
cursor = database.query(TABLE_NAME, PROJECTION, PART_ID_WHERE, attachmentId.toStrings(), null, null, null); cursor = database.query(TABLE_NAME, PROJECTION, ROW_ID_WHERE, new String[]{String.valueOf(attachmentId.getRowId())}, null, null, null);
if (cursor != null && cursor.moveToFirst()) { if (cursor != null && cursor.moveToFirst()) {
List<DatabaseAttachment> list = getAttachment(cursor); List<DatabaseAttachment> list = getAttachment(cursor);

View File

@ -89,7 +89,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
override fun persistAttachments(messageId: Long, attachments: List<Attachment>): List<Long> { override fun persistAttachments(messageId: Long, attachments: List<Attachment>): List<Long> {
val database = DatabaseFactory.getAttachmentDatabase(context) val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachments = attachments.map { it.toDatabaseAttachment() } val databaseAttachments = attachments.mapNotNull { it.toSignalAttachment() }
return database.insertAttachments(messageId, databaseAttachments) return database.insertAttachments(messageId, databaseAttachments)
} }

View File

@ -22,12 +22,12 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
fun persistJob(job: Job) { fun persistJob(job: Job) {
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
val contentValues = ContentValues(2) val contentValues = ContentValues()
contentValues.put(jobID, job.id) contentValues.put(jobID, job.id)
contentValues.put(jobType, job.getFactoryKey()) contentValues.put(jobType, job.getFactoryKey())
contentValues.put(failureCount, job.failureCount) contentValues.put(failureCount, job.failureCount)
contentValues.put(serializedData, SessionJobHelper.dataSerializer.serialize(job.serialize())) contentValues.put(serializedData, SessionJobHelper.dataSerializer.serialize(job.serialize()))
database.insertOrUpdate(sessionJobTable, contentValues, "$jobID = ?", arrayOf(jobID.toString())) database.insertOrUpdate(sessionJobTable, contentValues, "$jobID = ?", arrayOf(jobID))
} }
fun markJobAsSucceeded(job: Job) { fun markJobAsSucceeded(job: Job) {
@ -51,7 +51,7 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
database.getAll(sessionJobTable, "$jobType = ?", arrayOf(AttachmentUploadJob.KEY)) { cursor -> database.getAll(sessionJobTable, "$jobType = ?", arrayOf(AttachmentUploadJob.KEY)) { cursor ->
result.add(jobFromCursor(cursor) as AttachmentUploadJob) result.add(jobFromCursor(cursor) as AttachmentUploadJob)
} }
return result.first { job -> job.attachmentID == attachmentID } return result.firstOrNull { job -> job.attachmentID == attachmentID }
} }
fun getMessageSendJob(messageSendJobID: String): MessageSendJob? { fun getMessageSendJob(messageSendJobID: String): MessageSendJob? {

View File

@ -1,10 +1,8 @@
package org.session.libsession.database package org.session.libsession.database
import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.messaging.sending_receiving.attachments.*
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentState
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentPointer
import org.session.libsession.messaging.sending_receiving.attachments.SessionServiceAttachmentStream
import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address
import org.session.libsession.messaging.utilities.DotNetAPI
import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer
import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream import org.session.libsignal.service.api.messages.SignalServiceAttachmentStream
import java.io.InputStream import java.io.InputStream
@ -14,6 +12,8 @@ interface MessageDataProvider {
fun getMessageID(serverID: Long): Long? fun getMessageID(serverID: Long): Long?
fun deleteMessage(messageID: Long) fun deleteMessage(messageID: Long)
fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment?
fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream?
fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer? fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer?
@ -26,12 +26,14 @@ interface MessageDataProvider {
fun isOutgoingMessage(timestamp: Long): Boolean fun isOutgoingMessage(timestamp: Long): Boolean
@Throws(Exception::class) fun updateAttachmentAfterUploadSucceeded(attachmentId: Long, uploadResult: DotNetAPI.UploadResult)
fun uploadAttachment(attachmentId: Long) fun updateAttachmentAfterUploadFailed(attachmentId: Long)
// Quotes // Quotes
fun getMessageForQuote(timestamp: Long, author: Address): Long? fun getMessageForQuote(timestamp: Long, author: Address): Long?
fun getAttachmentsAndLinkPreviewFor(messageID: Long): List<Attachment> fun getAttachmentsAndLinkPreviewFor(messageID: Long): List<Attachment>
fun getMessageBodyFor(messageID: Long): String fun getMessageBodyFor(messageID: Long): String
fun getAttachmentIDsFor(messageID: Long): List<Long>
} }

View File

@ -58,7 +58,8 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
val attachmentData = PushAttachmentData(attachmentStream.contentType, attachmentStream.inputStream, ciphertextLength, outputStreamFactory, attachmentStream.listener) val attachmentData = PushAttachmentData(attachmentStream.contentType, attachmentStream.inputStream, ciphertextLength, outputStreamFactory, attachmentStream.listener)
FileServerAPI.shared.uploadAttachment(server, attachmentData) val uploadResult = FileServerAPI.shared.uploadAttachment(server, attachmentData)
handleSuccess(uploadResult)
} catch (e: java.lang.Exception) { } catch (e: java.lang.Exception) {
if (e is Error && e == Error.NoAttachment) { if (e is Error && e == Error.NoAttachment) {
@ -71,11 +72,11 @@ class AttachmentUploadJob(val attachmentID: Long, val threadID: String, val mess
} }
} }
private fun handleSuccess() { private fun handleSuccess(uploadResult: DotNetAPI.UploadResult) {
Log.w(TAG, "Attachment uploaded successfully.") Log.w(TAG, "Attachment uploaded successfully.")
delegate?.handleJobSucceeded(this) delegate?.handleJobSucceeded(this)
//TODO: handle success in database
MessagingConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID) MessagingConfiguration.shared.storage.resumeMessageSendJobIfNeeded(messageSendJobID)
//TODO interaction stuff, not sure how to deal with that
} }
private fun handlePermanentFailure(e: Exception) { private fun handlePermanentFailure(e: Exception) {

View File

@ -32,13 +32,13 @@ class MessageSendJob(val message: Message, val destination: Destination) : Job {
val message = message as? VisibleMessage val message = message as? VisibleMessage
message?.let { message?.let {
if(!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted if(!messageDataProvider.isOutgoingMessage(message.sentTimestamp!!)) return // The message has been deleted
val attachments = message.attachmentIDs.map { messageDataProvider.getAttachmentStream(it) }.filterNotNull() val attachments = message.attachmentIDs.map { messageDataProvider.getDatabaseAttachment(it) }.filterNotNull()
val attachmentsToUpload = attachments.filter { !it.isUploaded } val attachmentsToUpload = attachments.filter { it.url.isNullOrEmpty() }
attachmentsToUpload.forEach { attachmentsToUpload.forEach {
if(MessagingConfiguration.shared.storage.getAttachmentUploadJob(it.attachmentId) != null) { if (MessagingConfiguration.shared.storage.getAttachmentUploadJob(it.attachmentId.rowId) != null) {
// Wait for it to finish // Wait for it to finish
} else { } else {
val job = AttachmentUploadJob(it.attachmentId, message.threadID!!.toString(), message, id!!) val job = AttachmentUploadJob(it.attachmentId.rowId, message.threadID!!.toString(), message, id!!)
JobQueue.shared.add(job) JobQueue.shared.add(job)
} }
} }

View File

@ -2,8 +2,9 @@ package org.session.libsession.messaging.messages.visible
import android.util.Size import android.util.Size
import android.webkit.MimeTypeMap import android.webkit.MimeTypeMap
import org.session.libsession.database.MessageDataProvider import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress
import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment
import org.session.libsession.messaging.sending_receiving.attachments.Attachment as SignalAttachment
import org.session.libsignal.service.internal.push.SignalServiceProtos import org.session.libsignal.service.internal.push.SignalServiceProtos
import java.io.File import java.io.File
@ -66,8 +67,9 @@ class Attachment {
TODO("Not implemented") TODO("Not implemented")
} }
fun toDatabaseAttachment(): org.session.libsession.messaging.sending_receiving.attachments.Attachment { fun toSignalAttachment(): SignalAttachment? {
return DatabaseAttachment(null, 0, true, true, contentType, 0, if (!isValid()) return null
return DatabaseAttachment(null, 0, false, false, contentType, 0,
sizeInBytes?.toLong() ?: 0, fileName, null, key.toString(), null, digest, null, kind == Kind.VOICE_MESSAGE, sizeInBytes?.toLong() ?: 0, fileName, null, key.toString(), null, digest, null, kind == Kind.VOICE_MESSAGE,
size?.width ?: 0, size?.height ?: 0, false, caption, url) size?.width ?: 0, size?.height ?: 0, false, caption, url)
} }

View File

@ -74,8 +74,7 @@ object MessageSender {
attachment.size = Size(signalAttachment.width, signalAttachment.height) attachment.size = Size(signalAttachment.width, signalAttachment.height)
attachments.add(attachment) attachments.add(attachment)
} }
val attachmentIDs = MessagingConfiguration.shared.storage.persistAttachments(message.id val attachmentIDs = MessagingConfiguration.shared.messageDataProvider.getAttachmentIDsFor(message.id!!)
?: 0, attachments)
message.attachmentIDs.addAll(attachmentIDs) message.attachmentIDs.addAll(attachmentIDs)
} }
@ -94,7 +93,6 @@ object MessageSender {
val storage = MessagingConfiguration.shared.storage val storage = MessagingConfiguration.shared.storage
val userPublicKey = storage.getUserPublicKey() val userPublicKey = storage.getUserPublicKey()
val preconditionFailure = Exception("Destination should not be open groups!") val preconditionFailure = Exception("Destination should not be open groups!")
var snodeMessage: SnodeMessage? = null
// Set the timestamp, sender and recipient // Set the timestamp, sender and recipient
message.sentTimestamp ?: run { message.sentTimestamp = System.currentTimeMillis() } /* Visible messages will already have their sent timestamp set */ message.sentTimestamp ?: run { message.sentTimestamp = System.currentTimeMillis() } /* Visible messages will already have their sent timestamp set */
message.sender = userPublicKey message.sender = userPublicKey
@ -109,7 +107,7 @@ object MessageSender {
fun handleFailure(error: Exception) { fun handleFailure(error: Exception) {
handleFailedMessageSend(message, error) handleFailedMessageSend(message, error)
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
//TODO Notify user for send failure SnodeConfiguration.shared.broadcaster.broadcast("messageFailed", message.sentTimestamp!!)
} }
deferred.reject(error) deferred.reject(error)
} }
@ -142,9 +140,6 @@ object MessageSender {
// Serialize the protobuf // Serialize the protobuf
val plaintext = proto.toByteArray() val plaintext = proto.toByteArray()
// Encrypt the serialized protobuf // Encrypt the serialized protobuf
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
//TODO Notify user for encrypting message
}
val ciphertext: ByteArray val ciphertext: ByteArray
when (destination) { when (destination) {
is Destination.Contact -> ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, destination.publicKey) is Destination.Contact -> ciphertext = MessageSenderEncryption.encryptWithSessionProtocol(plaintext, destination.publicKey)
@ -178,7 +173,7 @@ object MessageSender {
val timestamp = System.currentTimeMillis() val timestamp = System.currentTimeMillis()
val nonce = ProofOfWork.calculate(base64EncodedData, recipient, timestamp, message.ttl.toInt()) ?: throw Error.ProofOfWorkCalculationFailed val nonce = ProofOfWork.calculate(base64EncodedData, recipient, timestamp, message.ttl.toInt()) ?: throw Error.ProofOfWorkCalculationFailed
// Send the result // Send the result
snodeMessage = SnodeMessage(recipient, base64EncodedData, message.ttl, timestamp, nonce) val snodeMessage = SnodeMessage(recipient, base64EncodedData, message.ttl, timestamp, nonce)
if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) { if (destination is Destination.Contact && message is VisibleMessage && !isSelfSend) {
SnodeConfiguration.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!) SnodeConfiguration.shared.broadcaster.broadcast("sendingMessage", message.sentTimestamp!!)
} }
@ -299,7 +294,6 @@ object MessageSender {
fun handleFailedMessageSend(message: Message, error: Exception) { fun handleFailedMessageSend(message: Message, error: Exception) {
val storage = MessagingConfiguration.shared.storage val storage = MessagingConfiguration.shared.storage
storage.setErrorMessage(message.sentTimestamp!!, message.sender!!, error) storage.setErrorMessage(message.sentTimestamp!!, message.sender!!, error)
SnodeConfiguration.shared.broadcaster.broadcast("messageFailed", message.sentTimestamp!!)
} }
// Convenience // Convenience