session-android/app/src/main/java/org/thoughtcrime/securesms/attachments/DatabaseAttachmentProvider.kt

268 lines
13 KiB
Kotlin
Raw Normal View History

package org.thoughtcrime.securesms.attachments
import android.content.Context
import android.text.TextUtils
import com.google.protobuf.ByteString
2021-01-06 06:11:00 +01:00
import org.greenrobot.eventbus.EventBus
import org.session.libsession.database.MessageDataProvider
2021-01-15 05:36:30 +01:00
import org.session.libsession.messaging.sending_receiving.attachments.*
2021-05-18 08:11:38 +02:00
import org.session.libsession.utilities.Address
2021-05-21 07:02:34 +02:00
import org.session.libsession.utilities.UploadResult
2021-03-03 05:14:45 +01:00
import org.session.libsession.utilities.Util
2021-05-18 01:26:08 +02:00
import org.session.libsignal.utilities.guava.Optional
2021-05-18 01:50:16 +02:00
import org.session.libsignal.messages.SignalServiceAttachment
import org.session.libsignal.messages.SignalServiceAttachmentPointer
import org.session.libsignal.messages.SignalServiceAttachmentStream
import org.session.libsignal.utilities.Base64
2021-05-18 01:12:33 +02:00
import org.session.libsignal.utilities.Log
2021-03-03 05:14:45 +01:00
import org.thoughtcrime.securesms.database.AttachmentDatabase
import org.thoughtcrime.securesms.database.Database
import org.thoughtcrime.securesms.database.DatabaseFactory
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
2021-01-06 06:11:00 +01:00
import org.thoughtcrime.securesms.events.PartProgressEvent
2021-03-03 05:14:45 +01:00
import org.thoughtcrime.securesms.mms.MediaConstraints
import org.thoughtcrime.securesms.mms.PartAuthority
import org.thoughtcrime.securesms.util.MediaUtil
2021-03-03 05:14:45 +01:00
import java.io.IOException
2021-01-05 04:17:42 +01:00
import java.io.InputStream
class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), MessageDataProvider {
override fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream? {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) ?: return null
return databaseAttachment.toAttachmentStream(context)
}
override fun getAttachmentPointer(attachmentId: Long): SessionServiceAttachmentPointer? {
2020-12-15 05:45:44 +01:00
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) ?: return null
return databaseAttachment.toAttachmentPointer()
2020-12-15 05:45:44 +01:00
}
override fun getSignalAttachmentStream(attachmentId: Long): SignalServiceAttachmentStream? {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) ?: return null
return databaseAttachment.toSignalAttachmentStream(context)
}
2021-03-03 05:14:45 +01:00
override fun getScaledSignalAttachmentStream(attachmentId: Long): SignalServiceAttachmentStream? {
val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = database.getAttachment(AttachmentId(attachmentId, 0)) ?: return null
val mediaConstraints = MediaConstraints.getPushMediaConstraints()
val scaledAttachment = scaleAndStripExif(database, mediaConstraints, databaseAttachment) ?: return null
return getAttachmentFor(scaledAttachment)
}
override fun getSignalAttachmentPointer(attachmentId: Long): SignalServiceAttachmentPointer? {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0)) ?: return null
return databaseAttachment.toSignalAttachmentPointer()
}
override fun setAttachmentState(attachmentState: AttachmentState, attachmentId: Long, messageID: Long) {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
attachmentDatabase.setTransferState(messageID, AttachmentId(attachmentId, 0), attachmentState.value)
2020-12-15 05:45:44 +01:00
}
2021-03-30 04:09:40 +02:00
override fun getMessageForQuote(timestamp: Long, author: Address): Pair<Long, Boolean>? {
val messagingDatabase = DatabaseFactory.getMmsSmsDatabase(context)
val message = messagingDatabase.getMessageFor(timestamp, author)
2021-03-30 04:09:40 +02:00
return if (message != null) Pair(message.id, message.isMms) else null
2021-01-13 06:13:49 +01:00
}
override fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List<Attachment> {
return DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(mmsId)
2021-01-13 06:13:49 +01:00
}
override fun getMessageBodyFor(timestamp: Long, author: String): String {
val messagingDatabase = DatabaseFactory.getMmsSmsDatabase(context)
return messagingDatabase.getMessageFor(timestamp, author)!!.body
2021-01-13 06:13:49 +01:00
}
2021-03-02 07:22:56 +01:00
override fun getAttachmentIDsFor(messageID: Long): List<Long> {
2021-03-05 03:31:40 +01:00
return DatabaseFactory.getAttachmentDatabase(context).getAttachmentsForMessage(messageID).mapNotNull {
if (it.isQuote) return@mapNotNull null
2021-03-02 07:22:56 +01:00
it.attachmentId.rowId
}
}
2021-03-09 00:50:02 +01:00
override fun getLinkPreviewAttachmentIDFor(messageID: Long): Long? {
val message = DatabaseFactory.getMmsDatabase(context).getOutgoingMessage(messageID)
return message.linkPreviews.firstOrNull()?.attachmentId?.rowId
}
override fun insertAttachment(messageId: Long, attachmentId: AttachmentId, stream: InputStream) {
2021-01-05 04:17:42 +01:00
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
attachmentDatabase.insertAttachmentsForPlaceholder(messageId, attachmentId, stream)
2021-01-05 04:17:42 +01:00
}
override fun updateAudioAttachmentDuration(attachmentId: AttachmentId, durationMs: Long) {
DatabaseFactory.getAttachmentDatabase(context).setAttachmentAudioExtras(DatabaseAttachmentAudioExtras(
attachmentId = attachmentId,
visualSamples = byteArrayOf(),
durationMs = durationMs
))
}
2020-12-15 05:45:44 +01:00
override fun isOutgoingMessage(timestamp: Long): Boolean {
val smsDatabase = DatabaseFactory.getSmsDatabase(context)
2021-03-02 02:24:09 +01:00
val mmsDatabase = DatabaseFactory.getMmsDatabase(context)
return smsDatabase.isOutgoingMessage(timestamp) || mmsDatabase.isOutgoingMessage(timestamp)
}
2021-05-21 07:02:34 +02:00
override fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult) {
2021-03-03 05:14:45 +01:00
val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return
val attachmentPointer = SignalServiceAttachmentPointer(uploadResult.id,
2021-05-13 05:19:08 +02:00
attachmentStream.contentType,
attachmentKey,
Optional.of(Util.toIntExact(attachmentStream.length)),
attachmentStream.preview,
attachmentStream.width, attachmentStream.height,
Optional.fromNullable(uploadResult.digest),
attachmentStream.fileName,
attachmentStream.voiceNote,
attachmentStream.caption,
uploadResult.url);
2021-03-03 05:14:45 +01:00
val attachment = PointerAttachment.forPointer(Optional.of(attachmentPointer), databaseAttachment.fastPreflightId).get()
database.updateAttachmentAfterUploadSucceeded(databaseAttachment.attachmentId, attachment)
2021-03-02 07:22:56 +01:00
}
2021-05-13 05:34:35 +02:00
override fun handleFailedAttachmentUpload(attachmentId: Long) {
2021-03-03 05:14:45 +01:00
val database = DatabaseFactory.getAttachmentDatabase(context)
val databaseAttachment = getDatabaseAttachment(attachmentId) ?: return
2021-05-13 05:34:35 +02:00
database.handleFailedAttachmentUpload(databaseAttachment.attachmentId)
2021-03-02 07:22:56 +01:00
}
2021-01-13 06:13:49 +01:00
override fun getMessageID(serverID: Long): Long? {
val openGroupMessagingDatabase = DatabaseFactory.getLokiMessageDatabase(context)
return openGroupMessagingDatabase.getMessageID(serverID)
2021-01-13 06:13:49 +01:00
}
override fun getMessageID(serverId: Long, threadId: Long): Pair<Long, Boolean>? {
val messageDB = DatabaseFactory.getLokiMessageDatabase(context)
return messageDB.getMessageID(serverId, threadId)
}
override fun deleteMessage(messageID: Long, isSms: Boolean) {
if (isSms) {
val db = DatabaseFactory.getSmsDatabase(context)
db.deleteMessage(messageID)
} else {
val db = DatabaseFactory.getMmsDatabase(context)
db.delete(messageID)
}
2021-04-30 06:19:37 +02:00
DatabaseFactory.getLokiMessageDatabase(context).deleteMessage(messageID, isSms)
2021-01-13 06:13:49 +01:00
}
2021-03-02 07:22:56 +01:00
override fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment? {
val attachmentDatabase = DatabaseFactory.getAttachmentDatabase(context)
return attachmentDatabase.getAttachment(AttachmentId(attachmentId, 0))
}
2021-03-03 05:14:45 +01:00
private fun scaleAndStripExif(attachmentDatabase: AttachmentDatabase, constraints: MediaConstraints, attachment: Attachment): Attachment? {
return try {
if (constraints.isSatisfied(context, attachment)) {
if (MediaUtil.isJpeg(attachment)) {
val stripped = constraints.getResizedMedia(context, attachment)
attachmentDatabase.updateAttachmentData(attachment, stripped)
} else {
attachment
}
} else if (constraints.canResize(attachment)) {
val resized = constraints.getResizedMedia(context, attachment)
attachmentDatabase.updateAttachmentData(attachment, resized)
} else {
2021-03-12 05:23:29 +01:00
throw Exception("Size constraints could not be met!")
2021-03-03 05:14:45 +01:00
}
} catch (e: Exception) {
return null
}
}
private fun getAttachmentFor(attachment: Attachment): SignalServiceAttachmentStream? {
try {
if (attachment.dataUri == null || attachment.size == 0L) throw IOException("Assertion failed, outgoing attachment has no data!")
val `is` = PartAuthority.getAttachmentStream(context, attachment.dataUri!!)
return SignalServiceAttachment.newStreamBuilder()
.withStream(`is`)
.withContentType(attachment.contentType)
.withLength(attachment.size)
.withFileName(attachment.fileName)
.withVoiceNote(attachment.isVoiceNote)
.withWidth(attachment.width)
.withHeight(attachment.height)
.withCaption(attachment.caption)
.withListener { total: Long, progress: Long -> EventBus.getDefault().postSticky(PartProgressEvent(attachment, total, progress)) }
.build()
} catch (ioe: IOException) {
Log.w("Loki", "Couldn't open attachment", ioe)
}
return null
}
}
fun DatabaseAttachment.toAttachmentPointer(): SessionServiceAttachmentPointer {
return SessionServiceAttachmentPointer(attachmentId.rowId, contentType, key?.toByteArray(), Optional.fromNullable(size.toInt()), Optional.absent(), width, height, Optional.fromNullable(digest), Optional.fromNullable(fileName), isVoiceNote, Optional.fromNullable(caption), url)
}
fun SessionServiceAttachmentPointer.toSignalPointer(): SignalServiceAttachmentPointer {
return SignalServiceAttachmentPointer(id,contentType,key?.toByteArray() ?: byteArrayOf(), size, preview, width, height, digest, fileName, voiceNote, caption, url)
}
fun DatabaseAttachment.toAttachmentStream(context: Context): SessionServiceAttachmentStream {
val stream = PartAuthority.getAttachmentStream(context, this.dataUri!!)
2021-01-06 06:11:00 +01:00
val listener = SignalServiceAttachment.ProgressListener { total: Long, progress: Long -> EventBus.getDefault().postSticky(PartProgressEvent(this, total, progress))}
var attachmentStream = SessionServiceAttachmentStream(stream, this.contentType, this.size, Optional.fromNullable(this.fileName), this.isVoiceNote, Optional.absent(), this.width, this.height, Optional.fromNullable(this.caption), listener)
attachmentStream.attachmentId = this.attachmentId.rowId
attachmentStream.isAudio = MediaUtil.isAudio(this)
attachmentStream.isGif = MediaUtil.isGif(this)
attachmentStream.isVideo = MediaUtil.isVideo(this)
attachmentStream.isImage = MediaUtil.isImage(this)
attachmentStream.key = ByteString.copyFrom(this.key?.toByteArray())
2021-01-06 06:11:00 +01:00
attachmentStream.digest = Optional.fromNullable(this.digest)
attachmentStream.url = this.url
return attachmentStream
}
fun DatabaseAttachment.toSignalAttachmentPointer(): SignalServiceAttachmentPointer? {
if (TextUtils.isEmpty(location)) { return null }
2021-05-17 03:23:49 +02:00
// `key` can be empty in an open group context (no encryption means no encryption key)
return try {
2021-05-17 03:23:49 +02:00
val id = location!!.toLong()
val key = Base64.decode(key!!)
SignalServiceAttachmentPointer(
id,
contentType,
key,
Optional.of(Util.toIntExact(size)),
Optional.absent(),
width,
height,
Optional.fromNullable(digest),
Optional.fromNullable(fileName),
isVoiceNote,
Optional.fromNullable(caption),
url
)
} catch (e: Exception) {
null
}
}
fun DatabaseAttachment.toSignalAttachmentStream(context: Context): SignalServiceAttachmentStream {
val stream = PartAuthority.getAttachmentStream(context, this.dataUri!!)
val listener = SignalServiceAttachment.ProgressListener { total: Long, progress: Long -> EventBus.getDefault().postSticky(PartProgressEvent(this, total, progress))}
return SignalServiceAttachmentStream(stream, this.contentType, this.size, Optional.fromNullable(this.fileName), this.isVoiceNote, Optional.absent(), this.width, this.height, Optional.fromNullable(this.caption), listener)
}
fun DatabaseAttachment.shouldHaveImageSize(): Boolean {
return (MediaUtil.isVideo(this) || MediaUtil.isImage(this) || MediaUtil.isGif(this));
}