Merge pull request #3 from Brice-W/refactor

Refactor
This commit is contained in:
RyanZhao 2020-12-02 16:26:03 +11:00 committed by GitHub
commit 67d53fc3f5
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
18 changed files with 866 additions and 36 deletions

View File

@ -38,7 +38,10 @@ dependencies {
implementation 'androidx.core:core-ktx:1.3.2' implementation 'androidx.core:core-ktx:1.3.2'
implementation 'androidx.appcompat:appcompat:1.2.0' implementation 'androidx.appcompat:appcompat:1.2.0'
implementation 'com.google.android.material:material:1.2.1' implementation 'com.google.android.material:material:1.2.1'
implementation "com.google.protobuf:protobuf-java:$protobufVersion"
testImplementation 'junit:junit:4.+' testImplementation 'junit:junit:4.+'
androidTestImplementation 'androidx.test.ext:junit:1.1.2' androidTestImplementation 'androidx.test.ext:junit:1.1.2'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'
implementation project(":libsignal")
} }

View File

@ -1,5 +1,12 @@
package org.session.messaging.messages package org.session.libsession.messaging.messages
enum class Destination { sealed class Destination {
class Contact(val publicKey: String)
class ClosedGroup(val groupPublicKey: String)
class OpenGroup(val channel: Long, val server: String)
companion object {
//TODO need to implement the equivalent to TSThread and then implement from(...)
}
} }

View File

@ -1,5 +1,30 @@
package org.session.messaging.messages package org.session.libsession.messaging.messages
open class Message { import org.session.libsignal.service.internal.push.SignalServiceProtos
abstract class Message {
var id: String? = null
var threadID: String? = null
var sentTimestamp: Long? = null
var receivedTimestamp: Long? = null
var recipient: String? = null
var sender: String? = null
var groupPublicKey: String? = null
var openGroupServerMessageID: Long? = null
companion object {
@JvmStatic
val ttl = 2 * 24 * 60 * 60 * 1000 //TODO not sure about that declaration
}
// validation
open fun isValid(): Boolean {
sentTimestamp = if (sentTimestamp!! > 0) sentTimestamp else return false
receivedTimestamp = if (receivedTimestamp!! > 0) receivedTimestamp else return false
return sender != null && recipient != null
}
abstract fun toProto(): SignalServiceProtos.Content?
} }

View File

@ -1,4 +1,154 @@
package org.session.messaging.messages.control package org.session.libsession.messaging.messages.control
class ClosedGroupUpdate : ControlMessage() { import com.google.protobuf.ByteString
import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos
import org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupSenderKey
class ClosedGroupUpdate() : ControlMessage() {
var kind: Kind? = null
// Kind enum
sealed class Kind {
class New(val groupPublicKey: ByteArray, val name: String, val groupPrivateKey: ByteArray, val senderKeys: Collection<org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupSenderKey>, val members: Collection<ByteArray>, val admins: Collection<ByteArray>) : Kind()
class Info(val groupPublicKey: ByteArray, val name: String, val senderKeys: Collection<org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupSenderKey>, val members: Collection<ByteArray>, val admins: Collection<ByteArray>) : Kind()
class SenderKeyRequest(val groupPublicKey: ByteArray) : Kind()
class SenderKey(val groupPublicKey: ByteArray, val senderKey: org.session.libsignal.service.loki.protocol.closedgroups.ClosedGroupSenderKey) : Kind()
}
companion object {
const val TAG = "ClosedGroupUpdate"
fun fromProto(proto: SignalServiceProtos.Content): ClosedGroupUpdate? {
val closedGroupUpdateProto = proto.dataMessage?.closedGroupUpdate ?: return null
val groupPublicKey = closedGroupUpdateProto.groupPublicKey
var kind: Kind
when(closedGroupUpdateProto.type) {
SignalServiceProtos.ClosedGroupUpdate.Type.NEW -> {
val name = closedGroupUpdateProto.name ?: return null
val groupPrivateKey = closedGroupUpdateProto.groupPrivateKey ?: return null
val senderKeys = closedGroupUpdateProto.senderKeysList.map { ClosedGroupSenderKey.fromProto(it) }
kind = Kind.New(
groupPublicKey = groupPublicKey.toByteArray(),
name = name,
groupPrivateKey = groupPrivateKey.toByteArray(),
senderKeys = senderKeys,
members = closedGroupUpdateProto.membersList.map { it.toByteArray() },
admins = closedGroupUpdateProto.adminsList.map { it.toByteArray() }
)
}
SignalServiceProtos.ClosedGroupUpdate.Type.INFO -> {
val name = closedGroupUpdateProto.name ?: return null
val senderKeys = closedGroupUpdateProto.senderKeysList.map { ClosedGroupSenderKey.fromProto(it) }
kind = Kind.Info(
groupPublicKey = groupPublicKey.toByteArray(),
name = name,
senderKeys = senderKeys,
members = closedGroupUpdateProto.membersList.map { it.toByteArray() },
admins = closedGroupUpdateProto.adminsList.map { it.toByteArray() }
)
}
SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY_REQUEST -> {
kind = Kind.SenderKeyRequest(groupPublicKey = groupPublicKey.toByteArray())
}
SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY -> {
val senderKeyProto = closedGroupUpdateProto.senderKeysList?.first() ?: return null
kind = Kind.SenderKey(
groupPublicKey = groupPublicKey.toByteArray(),
senderKey = ClosedGroupSenderKey.fromProto(senderKeyProto)
)
}
}
return ClosedGroupUpdate(kind)
}
}
// constructor
internal constructor(kind: Kind?) : this() {
this.kind = kind
}
// validation
override fun isValid(): Boolean {
if (!super.isValid()) return false
val kind = kind ?: return false
when(kind) {
is Kind.New -> {
return !kind.groupPublicKey.isEmpty() && !kind.name.isEmpty() && !kind.groupPrivateKey.isEmpty() && !kind.members.isEmpty() && !kind.admins.isEmpty()
}
is Kind.Info -> {
return !kind.groupPublicKey.isEmpty() && !kind.name.isEmpty() && !kind.members.isEmpty() && !kind.admins.isEmpty()
}
is Kind.SenderKeyRequest -> {
return !kind.groupPublicKey.isEmpty()
}
is Kind.SenderKey -> {
return !kind.groupPublicKey.isEmpty()
}
}
}
override fun toProto(): SignalServiceProtos.Content? {
val kind = kind
if (kind == null) {
Log.w(TAG, "Couldn't construct closed group update proto from: $this")
return null
}
try {
val closedGroupUpdate: SignalServiceProtos.ClosedGroupUpdate.Builder = SignalServiceProtos.ClosedGroupUpdate.newBuilder()
when (kind) {
is Kind.New -> {
closedGroupUpdate.groupPublicKey = ByteString.copyFrom(kind.groupPublicKey)
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdate.Type.NEW
closedGroupUpdate.name = kind.name
closedGroupUpdate.groupPrivateKey = ByteString.copyFrom(kind.groupPrivateKey)
closedGroupUpdate.addAllSenderKeys(kind.senderKeys.map { it.toProto() })
closedGroupUpdate.addAllMembers(kind.members.map { ByteString.copyFrom(it) })
closedGroupUpdate.addAllAdmins(kind.admins.map { ByteString.copyFrom(it) })
}
is Kind.Info -> {
closedGroupUpdate.groupPublicKey = ByteString.copyFrom(kind.groupPublicKey)
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdate.Type.INFO
closedGroupUpdate.name = kind.name
closedGroupUpdate.addAllSenderKeys(kind.senderKeys.map { it.toProto() })
closedGroupUpdate.addAllMembers(kind.members.map { ByteString.copyFrom(it) })
closedGroupUpdate.addAllAdmins(kind.admins.map { ByteString.copyFrom(it) })
}
is Kind.SenderKeyRequest -> {
closedGroupUpdate.groupPublicKey = ByteString.copyFrom(kind.groupPublicKey)
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY_REQUEST
}
is Kind.SenderKey -> {
closedGroupUpdate.groupPublicKey = ByteString.copyFrom(kind.groupPublicKey)
closedGroupUpdate.type = SignalServiceProtos.ClosedGroupUpdate.Type.SENDER_KEY
closedGroupUpdate.addAllSenderKeys(listOf( kind.senderKey.toProto() ))
}
}
val contentProto = SignalServiceProtos.Content.newBuilder()
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
dataMessageProto.closedGroupUpdate = closedGroupUpdate.build()
contentProto.dataMessage = dataMessageProto.build()
return contentProto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct closed group update proto from: $this")
return null
}
return null
}
}
// extension functions to class ClosedGroupSenderKey
private fun ClosedGroupSenderKey.Companion.fromProto(proto: SignalServiceProtos.ClosedGroupUpdate.SenderKey): ClosedGroupSenderKey {
return ClosedGroupSenderKey(chainKey = proto.chainKey.toByteArray(), keyIndex = proto.keyIndex, publicKey = proto.publicKey.toByteArray())
}
private fun ClosedGroupSenderKey.toProto(): SignalServiceProtos.ClosedGroupUpdate.SenderKey {
val proto = SignalServiceProtos.ClosedGroupUpdate.SenderKey.newBuilder()
proto.chainKey = ByteString.copyFrom(chainKey)
proto.keyIndex = keyIndex
proto.publicKey = ByteString.copyFrom(publicKey)
return proto.build()
} }

View File

@ -1,6 +1,6 @@
package org.session.messaging.messages.control package org.session.libsession.messaging.messages.control
import org.session.messaging.messages.Message import org.session.libsession.messaging.messages.Message
open class ControlMessage : Message() { abstract class ControlMessage : Message() {
} }

View File

@ -1,4 +1,51 @@
package org.session.messaging.messages.control package org.session.libsession.messaging.messages.control
class ExpirationTimerUpdate : ControlMessage() { import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos
class ExpirationTimerUpdate() : ControlMessage() {
var duration: Int? = 0
companion object {
const val TAG = "ExpirationTimerUpdate"
fun fromProto(proto: SignalServiceProtos.Content): ExpirationTimerUpdate? {
val dataMessageProto = proto.dataMessage ?: return null
val isExpirationTimerUpdate = (dataMessageProto.flags and SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE) != 0 //TODO validate that 'and' operator equivalent to Swift '&'
if (!isExpirationTimerUpdate) return null
val duration = dataMessageProto.expireTimer
return ExpirationTimerUpdate(duration)
}
}
//constructor
internal constructor(duration: Int) : this() {
this.duration = duration
}
// validation
override fun isValid(): Boolean {
if (!super.isValid()) return false
return duration != null
}
override fun toProto(): SignalServiceProtos.Content? {
val duration = duration
if (duration == null) {
Log.w(TAG, "Couldn't construct expiration timer update proto from: $this")
return null
}
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
dataMessageProto.flags = SignalServiceProtos.DataMessage.Flags.EXPIRATION_TIMER_UPDATE_VALUE
dataMessageProto.expireTimer = duration
val contentProto = SignalServiceProtos.Content.newBuilder()
try {
contentProto.dataMessage = dataMessageProto.build()
return contentProto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct expiration timer update proto from: $this")
return null
}
}
} }

View File

@ -1,4 +1,53 @@
package org.session.messaging.messages.control package org.session.libsession.messaging.messages.control
class ReadReceipt : ControlMessage() { import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos
class ReadReceipt() : ControlMessage() {
var timestamps: LongArray? = null
companion object {
const val TAG = "ReadReceipt"
fun fromProto(proto: SignalServiceProtos.Content): ReadReceipt? {
val receiptProto = proto.receiptMessage ?: return null
if (receiptProto.type != SignalServiceProtos.ReceiptMessage.Type.READ) return null
val timestamps = receiptProto.timestampList
if (timestamps.isEmpty()) return null
return ReadReceipt(timestamps = timestamps.toLongArray())
}
}
//constructor
internal constructor(timestamps: LongArray?) : this() {
this.timestamps = timestamps
}
// validation
override fun isValid(): Boolean {
if (!super.isValid()) return false
val timestamps = timestamps ?: return false
if (timestamps.isNotEmpty()) { return true }
return false
}
override fun toProto(): SignalServiceProtos.Content? {
val timestamps = timestamps
if (timestamps == null) {
Log.w(ExpirationTimerUpdate.TAG, "Couldn't construct read receipt proto from: $this")
return null
}
val receiptProto = SignalServiceProtos.ReceiptMessage.newBuilder()
receiptProto.type = SignalServiceProtos.ReceiptMessage.Type.READ
receiptProto.addAllTimestamp(timestamps.asIterable())
val contentProto = SignalServiceProtos.Content.newBuilder()
try {
contentProto.receiptMessage = receiptProto.build()
return contentProto.build()
} catch (e: Exception) {
Log.w(ExpirationTimerUpdate.TAG, "Couldn't construct read receipt proto from: $this")
return null
}
}
} }

View File

@ -1,4 +1,75 @@
package org.session.messaging.messages.control package org.session.libsession.messaging.messages.control
class TypingIndicator : ControlMessage() { import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos
class TypingIndicator() : ControlMessage() {
companion object {
const val TAG = "TypingIndicator"
//val ttl: 30 * 1000 //TODO
fun fromProto(proto: SignalServiceProtos.Content): TypingIndicator? {
val typingIndicatorProto = proto.typingMessage ?: return null
val kind = Kind.fromProto(typingIndicatorProto.action)
return TypingIndicator(kind = kind)
}
}
// Kind enum
enum class Kind {
STARTED,
STOPPED,
;
companion object {
@JvmStatic
fun fromProto(proto: SignalServiceProtos.TypingMessage.Action): Kind =
when (proto) {
SignalServiceProtos.TypingMessage.Action.STARTED -> STARTED
SignalServiceProtos.TypingMessage.Action.STOPPED -> STOPPED
}
}
fun toProto(): SignalServiceProtos.TypingMessage.Action {
when (this) {
STARTED -> return SignalServiceProtos.TypingMessage.Action.STARTED
STOPPED -> return SignalServiceProtos.TypingMessage.Action.STOPPED
}
}
}
var kind: Kind? = null
//constructor
internal constructor(kind: Kind) : this() {
this.kind = kind
}
// validation
override fun isValid(): Boolean {
if (!super.isValid()) return false
return kind != null
}
override fun toProto(): SignalServiceProtos.Content? {
val timestamp = sentTimestamp
val kind = kind
if (timestamp == null || kind == null) {
Log.w(TAG, "Couldn't construct typing indicator proto from: $this")
return null
}
val typingIndicatorProto = SignalServiceProtos.TypingMessage.newBuilder()
typingIndicatorProto.timestamp = timestamp
typingIndicatorProto.action = kind.toProto()
val contentProto = SignalServiceProtos.Content.newBuilder()
try {
contentProto.typingMessage = typingIndicatorProto.build()
return contentProto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct typing indicator proto from: $this")
return null
}
}
} }

View File

@ -1,6 +1,37 @@
package org.session.messaging.messages.control.unused package org.session.libsession.messaging.messages.control.unused
import org.session.messaging.messages.control.ControlMessage import com.google.protobuf.ByteString
import org.session.libsession.messaging.messages.control.ControlMessage
import org.session.libsession.messaging.messages.control.TypingIndicator
import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos
import java.security.SecureRandom
class NullMessage : ControlMessage() { class NullMessage() : ControlMessage() {
companion object {
const val TAG = "NullMessage"
fun fromProto(proto: SignalServiceProtos.Content): NullMessage? {
if (proto.nullMessage == null) return null
return NullMessage()
}
}
override fun toProto(): SignalServiceProtos.Content? {
val nullMessageProto = SignalServiceProtos.NullMessage.newBuilder()
val sr = SecureRandom()
val paddingSize = sr.nextInt(512)
val padding = ByteArray(paddingSize)
nullMessageProto.padding = ByteString.copyFrom(padding)
val contentProto = SignalServiceProtos.Content.newBuilder()
try {
contentProto.nullMessage = nullMessageProto.build()
return contentProto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct null message proto from: $this")
return null
}
}
} }

View File

@ -1,6 +1,81 @@
package org.session.messaging.messages.control.unused package org.session.libsession.messaging.messages.control.unused
import org.session.messaging.messages.control.ControlMessage import com.google.protobuf.ByteString
import org.session.libsession.messaging.messages.control.ControlMessage
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
import org.session.libsession.messaging.messages.control.TypingIndicator
import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.libsignal.state.PreKeyBundle
import org.session.libsignal.service.internal.push.SignalServiceProtos
import java.security.SecureRandom
class SessionRequest : ControlMessage() { class SessionRequest() : ControlMessage() {
var preKeyBundle: PreKeyBundle? = null
companion object {
const val TAG = "SessionRequest"
fun fromProto(proto: SignalServiceProtos.Content): SessionRequest? {
if (proto.nullMessage == null) return null
val preKeyBundleProto = proto.preKeyBundleMessage ?: return null
val registrationID: Int = 0
//TODO looks like database stuff here
/*iOS code: Configuration.shared.storage.with { transaction in
registrationID = Configuration.shared.storage.getOrGenerateRegistrationID(using: transaction)
}*/
val preKeyBundle = PreKeyBundle(
registrationID,
1,
preKeyBundleProto.preKeyId,
null, //TODO preKeyBundleProto.preKey,
0, //TODO preKeyBundleProto.signedKey,
null, //TODO preKeyBundleProto.signedKeyId,
preKeyBundleProto.signature.toByteArray(),
null, //TODO preKeyBundleProto.identityKey
)
return SessionRequest(preKeyBundle)
}
}
//constructor
internal constructor(preKeyBundle: PreKeyBundle) : this() {
this.preKeyBundle = preKeyBundle
}
// validation
override fun isValid(): Boolean {
if (!super.isValid()) return false
return preKeyBundle != null
}
override fun toProto(): SignalServiceProtos.Content? {
val preKeyBundle = preKeyBundle
if (preKeyBundle == null) {
Log.w(TAG, "Couldn't construct session request proto from: $this")
return null
}
val nullMessageProto = SignalServiceProtos.NullMessage.newBuilder()
val sr = SecureRandom()
val paddingSize = sr.nextInt(512)
val padding = ByteArray(paddingSize)
nullMessageProto.padding = ByteString.copyFrom(padding)
val preKeyBundleProto = SignalServiceProtos.PreKeyBundleMessage.newBuilder()
//TODO preKeyBundleProto.identityKey = preKeyBundle.identityKey
preKeyBundleProto.deviceId = preKeyBundle.deviceId
preKeyBundleProto.preKeyId = preKeyBundle.preKeyId
//TODO preKeyBundleProto.preKey = preKeyBundle.preKeyPublic
preKeyBundleProto.signedKeyId = preKeyBundle.signedPreKeyId
//TODO preKeyBundleProto.signedKey = preKeyBundle.signedPreKeyPublic
preKeyBundleProto.signature = ByteString.copyFrom(preKeyBundle.signedPreKeySignature)
val contentProto = SignalServiceProtos.Content.newBuilder()
try {
contentProto.nullMessage = nullMessageProto.build()
contentProto.preKeyBundleMessage = preKeyBundleProto.build()
return contentProto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct session request proto from: $this")
return null
}
}
} }

View File

@ -0,0 +1,71 @@
package org.session.libsession.messaging.messages.visible
import android.util.Size
import android.webkit.MimeTypeMap
import org.session.libsignal.service.internal.push.SignalServiceProtos
import java.io.File
import java.net.URL
import kotlin.math.absoluteValue
class Attachment : VisibleMessage<SignalServiceProtos.AttachmentPointer?>() {
var fileName: String? = null
var contentType: String? = null
var key: ByteArray? = null
var digest: ByteArray? = null
var kind: Kind? = null
var caption: String? = null
var size: Size? = null
var sizeInBytes: Int? = 0
var url: String? = null
companion object {
fun fromProto(proto: SignalServiceProtos.AttachmentPointer): Attachment? {
val result = Attachment()
result.fileName = proto.fileName
fun inferContentType(): String {
val fileName = result.fileName ?: return "application/octet-stream" //TODO find equivalent to OWSMimeTypeApplicationOctetStream
val fileExtension = File(fileName).extension
val mimeTypeMap = MimeTypeMap.getSingleton()
return mimeTypeMap.getMimeTypeFromExtension(fileExtension) ?: "application/octet-stream" //TODO check that it's correct
}
result.contentType = proto.contentType ?: inferContentType()
result.key = proto.key.toByteArray()
result.digest = proto.digest.toByteArray()
val kind: Kind
if (proto.hasFlags() && (proto.flags and SignalServiceProtos.AttachmentPointer.Flags.VOICE_MESSAGE_VALUE) > 0) { //TODO validate that 'and' operator = swift '&'
kind = Kind.VOICEMESSAGE
} else {
kind = Kind.GENERIC
}
result.kind = kind
result.caption = if (proto.hasCaption()) proto.caption else null
val size: Size
if (proto.hasWidth() && proto.width > 0 && proto.hasHeight() && proto.height > 0) {
size = Size(proto.width, proto.height)
} else {
size = Size(0,0) //TODO check that it's equivalent to swift: CGSize.zero
}
result.size = size
result.sizeInBytes = if (proto.size > 0) proto.size else null
result. url = proto.url
return result
}
}
enum class Kind {
VOICEMESSAGE,
GENERIC
}
// validation
override fun isValid(): Boolean {
if (!super.isValid()) return false
// key and digest can be nil for open group attachments
return (contentType != null && kind != null && size != null && sizeInBytes != null && url != null)
}
override fun toProto(transaction: String): SignalServiceProtos.AttachmentPointer? {
TODO("Not implemented")
}
}

View File

@ -0,0 +1,102 @@
package org.session.libsession.messaging.messages.visible
import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos
class BaseVisibleMessage() : VisibleMessage<SignalServiceProtos.Content?>() {
var text: String? = null
var attachmentIDs = ArrayList<String>()
var quote: Quote? = null
var linkPreview: LinkPreview? = null
var contact: Contact? = null
var profile: Profile? = null
companion object {
const val TAG = "BaseVisibleMessage"
fun fromProto(proto: SignalServiceProtos.Content): BaseVisibleMessage? {
val dataMessage = proto.dataMessage ?: return null
val result = BaseVisibleMessage()
result.text = dataMessage.body
// Attachments are handled in MessageReceiver
val quoteProto = dataMessage.quote
quoteProto?.let {
val quote = Quote.fromProto(quoteProto)
quote?.let { result.quote = quote }
}
val linkPreviewProto = dataMessage.previewList.first()
linkPreviewProto?.let {
val linkPreview = LinkPreview.fromProto(linkPreviewProto)
linkPreview?.let { result.linkPreview = linkPreview }
}
// TODO Contact
val profile = Profile.fromProto(dataMessage)
profile?.let { result.profile = profile }
return result
}
}
// validation
override fun isValid(): Boolean {
if (!super.isValid()) return false
if (attachmentIDs.isNotEmpty()) return true
val text = text?.trim() ?: return false
if (text.isNotEmpty()) return true
return false
}
override fun toProto(transaction: String): SignalServiceProtos.Content? {
val proto = SignalServiceProtos.Content.newBuilder()
var attachmentIDs = this.attachmentIDs
val dataMessage: SignalServiceProtos.DataMessage.Builder
// Profile
val profile = profile
val profileProto = profile?.toSSProto()
if (profileProto != null) {
dataMessage = profileProto.toBuilder()
} else {
dataMessage = SignalServiceProtos.DataMessage.newBuilder()
}
// Text
text?.let { dataMessage.body = text }
// Quote
val quotedAttachmentID = quote?.attachmentID
quotedAttachmentID?.let {
val index = attachmentIDs.indexOf(quotedAttachmentID)
if (index >= 0) { attachmentIDs.removeAt(index) }
}
val quote = quote
quote?.let {
val quoteProto = quote.toProto(transaction)
if (quoteProto != null) dataMessage.quote = quoteProto
}
//Link preview
val linkPreviewAttachmentID = linkPreview?.attachmentID
linkPreviewAttachmentID?.let {
val index = attachmentIDs.indexOf(quotedAttachmentID)
if (index >= 0) { attachmentIDs.removeAt(index) }
}
val linkPreview = linkPreview
linkPreview?.let {
val linkPreviewProto = linkPreview.toProto(transaction)
linkPreviewProto?.let {
dataMessage.addAllPreview(listOf(linkPreviewProto))
}
}
//Attachments
// TODO I'm blocking on that one...
//swift: let attachments = attachmentIDs.compactMap { TSAttachmentStream.fetch(uniqueId: $0, transaction: transaction) }
// TODO Contact
// Build
try {
proto.dataMessage = dataMessage.build()
return proto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct visible message proto from: $this")
return null
}
}
}

View File

@ -1,4 +1,16 @@
package org.session.messaging.messages.visible package org.session.libsession.messaging.messages.visible
internal class Contact { import org.session.libsignal.service.internal.push.SignalServiceProtos
class Contact : VisibleMessage<SignalServiceProtos.DataMessage.Contact?>() {
companion object {
fun fromProto(proto: SignalServiceProtos.Content): Contact? {
TODO("Not yet implemented")
}
}
override fun toProto(transaction: String): SignalServiceProtos.DataMessage.Contact? {
TODO("Not yet implemented")
}
} }

View File

@ -1,4 +1,58 @@
package org.session.messaging.messages.visible package org.session.libsession.messaging.messages.visible
internal class LinkPreview { import org.session.libsession.messaging.messages.control.TypingIndicator
import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos
class LinkPreview() : VisibleMessage<SignalServiceProtos.DataMessage.Preview?>(){
var title: String? = null
var url: String? = null
var attachmentID: String? = null
companion object {
const val TAG = "LinkPreview"
fun fromProto(proto: SignalServiceProtos.DataMessage.Preview): LinkPreview? {
val title = proto.title
val url = proto.url
return LinkPreview(title, url, null)
}
}
//constructor
internal constructor(title: String?, url: String, attachmentID: String?) : this() {
this.title = title
this.url = url
this.attachmentID = attachmentID
}
// validation
override fun isValid(): Boolean {
if (!super.isValid()) return false
return (title != null && url != null && attachmentID != null)
}
override fun toProto(transaction: String): SignalServiceProtos.DataMessage.Preview? {
val url = url
if (url == null) {
Log.w(TAG, "Couldn't construct link preview proto from: $this")
return null
}
val linkPreviewProto = SignalServiceProtos.DataMessage.Preview.newBuilder()
linkPreviewProto.url = url
title?.let { linkPreviewProto.title = title }
val attachmentID = attachmentID
attachmentID?.let {
//TODO database stuff
}
// Build
try {
return linkPreviewProto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct link preview proto from: $this")
return null
}
}
} }

View File

@ -1,4 +1,64 @@
package org.session.messaging.messages.visible package org.session.libsession.messaging.messages.visible
internal class Profile { import com.google.protobuf.ByteString
import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos
class Profile() : VisibleMessage<SignalServiceProtos.DataMessage?>() {
var displayName: String? = null
var profileKey: ByteArray? = null
var profilePictureURL: String? = null
companion object {
const val TAG = "Profile"
fun fromProto(proto: SignalServiceProtos.DataMessage): Profile? {
val profileProto = proto.profile ?: return null
val displayName = profileProto.displayName ?: return null
val profileKey = proto.profileKey
val profilePictureURL = profileProto.profilePictureURL
profileKey?.let {
val profilePictureURL = profilePictureURL
profilePictureURL?.let {
return Profile(displayName = displayName, profileKey = profileKey.toByteArray(), profilePictureURL = profilePictureURL)
}
}
return Profile(displayName)
}
}
//constructor
internal constructor(displayName: String, profileKey: ByteArray? = null, profilePictureURL: String? = null) : this() {
this.displayName = displayName
this.profileKey = profileKey
this.profilePictureURL = profilePictureURL
}
fun toSSProto(): SignalServiceProtos.DataMessage? {
return this.toProto("")
}
override fun toProto(transaction: String): SignalServiceProtos.DataMessage? {
val displayName = displayName
if (displayName == null) {
Log.w(TAG, "Couldn't construct link preview proto from: $this")
return null
}
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
val profileProto = SignalServiceProtos.LokiUserProfile.newBuilder()
profileProto.displayName = displayName
val profileKey = profileKey
profileKey?.let { dataMessageProto.profileKey = ByteString.copyFrom(profileKey) }
val profilePictureURL = profilePictureURL
profilePictureURL?.let { profileProto.profilePictureURL = profilePictureURL }
// Build
try {
dataMessageProto.profile = profileProto.build()
return dataMessageProto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct profile proto from: $this")
return null
}
}
} }

View File

@ -1,4 +1,72 @@
package org.session.messaging.messages.visible package org.session.libsession.messaging.messages.visible
internal class Quote { import org.session.libsignal.libsignal.logging.Log
import org.session.libsignal.service.internal.push.SignalServiceProtos
class Quote() : VisibleMessage<SignalServiceProtos.DataMessage.Quote?>() {
var timestamp: Long? = 0
var publicKey: String? = null
var text: String? = null
var attachmentID: String? = null
companion object {
const val TAG = "Quote"
fun fromProto(proto: SignalServiceProtos.DataMessage.Quote): Quote? {
val timestamp = proto.id
val publicKey = proto.author
val text = proto.text
return Quote(timestamp, publicKey, text, null)
}
}
//constructor
internal constructor(timestamp: Long, publicKey: String, text: String?, attachmentID: String?) : this() {
this.timestamp = timestamp
this.publicKey = publicKey
this.text = text
this.attachmentID = attachmentID
}
// validation
override fun isValid(): Boolean {
if (!super.isValid()) return false
return (timestamp != null && publicKey != null)
}
override fun toProto(transaction: String): SignalServiceProtos.DataMessage.Quote? {
val timestamp = timestamp
val publicKey = publicKey
if (timestamp == null || publicKey == null) {
Log.w(TAG, "Couldn't construct quote proto from: $this")
return null
}
val quoteProto = SignalServiceProtos.DataMessage.Quote.newBuilder()
quoteProto.id = timestamp
quoteProto.author = publicKey
text?.let { quoteProto.text = text }
addAttachmentsIfNeeded(quoteProto, transaction)
// Build
try {
return quoteProto.build()
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct quote proto from: $this")
return null
}
}
private fun addAttachmentsIfNeeded(quoteProto: SignalServiceProtos.DataMessage.Quote.Builder, transaction: String) {
val attachmentID = attachmentID ?: return
//TODO databas stuff
val quotedAttachmentProto = SignalServiceProtos.DataMessage.Quote.QuotedAttachment.newBuilder()
//TODO more database related stuff
//quotedAttachmentProto.contentType =
try {
quoteProto.addAttachments(quotedAttachmentProto.build())
} catch (e: Exception) {
Log.w(TAG, "Couldn't construct quoted attachment proto from: $this")
}
}
} }

View File

@ -1,6 +1,15 @@
package org.session.messaging.messages.visible package org.session.libsession.messaging.messages.visible
import org.session.messaging.messages.Message import org.session.libsession.messaging.messages.Message
import org.session.libsignal.service.internal.push.SignalServiceProtos
class VisibleMessage : Message() { abstract class VisibleMessage<out T: com.google.protobuf.MessageOrBuilder?> : Message() {
abstract fun toProto(transaction: String): T
final override fun toProto(): SignalServiceProtos.Content? {
//we don't need to implement this method in subclasses
//TODO it just needs an equivalent to swift: preconditionFailure("Use toProto(using:) if that exists...
TODO("Not implemented")
}
} }

View File

@ -1,4 +0,0 @@
package org.session.messaging.messages.visible.attachments
internal class Attachment {
}