mirror of
https://github.com/oxen-io/session-android.git
synced 2024-11-27 12:05:22 +00:00
feat: add open group v2 storage and db methods, starting on new open group v2 poller
This commit is contained in:
parent
201dde7412
commit
0eadc55325
@ -14,6 +14,7 @@ import org.session.libsession.messaging.messages.signal.IncomingTextMessage
|
|||||||
import org.session.libsession.messaging.messages.visible.Attachment
|
import org.session.libsession.messaging.messages.visible.Attachment
|
||||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||||
import org.session.libsession.messaging.opengroups.OpenGroup
|
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||||
|
import org.session.libsession.messaging.opengroups.OpenGroupV2
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
||||||
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.linkpreview.LinkPreview
|
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
|
||||||
@ -221,6 +222,21 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
DatabaseFactory.getLokiAPIDatabase(context).setAuthToken(server, null)
|
DatabaseFactory.getLokiAPIDatabase(context).setAuthToken(server, null)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getAuthToken(room: String, server: String): String? {
|
||||||
|
val id = "$server.$room"
|
||||||
|
return DatabaseFactory.getLokiAPIDatabase(context).getAuthToken(id)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setAuthToken(room: String, server: String, newValue: String) {
|
||||||
|
val id = "$server.$room"
|
||||||
|
DatabaseFactory.getLokiAPIDatabase(context).setAuthToken(id, newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeAuthToken(room: String, server: String) {
|
||||||
|
val id = "$server.$room"
|
||||||
|
DatabaseFactory.getLokiAPIDatabase(context).setAuthToken(id, null)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getOpenGroup(threadID: String): OpenGroup? {
|
override fun getOpenGroup(threadID: String): OpenGroup? {
|
||||||
if (threadID.toInt() < 0) { return null }
|
if (threadID.toInt() < 0) { return null }
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
@ -230,6 +246,15 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getV2OpenGroup(threadId: String): OpenGroupV2? {
|
||||||
|
if (threadId.toInt() < 0) { return null }
|
||||||
|
val database = databaseHelper.readableDatabase
|
||||||
|
return database.get(LokiThreadDatabase.publicChatTable, "${LokiThreadDatabase.threadID} = ?", arrayOf(threadId)) { cursor ->
|
||||||
|
val publicChatAsJson = cursor.getString(LokiThreadDatabase.publicChat)
|
||||||
|
OpenGroupV2.fromJson(publicChatAsJson)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun getThreadID(openGroupID: String): String {
|
override fun getThreadID(openGroupID: String): String {
|
||||||
val address = Address.fromSerialized(openGroupID)
|
val address = Address.fromSerialized(openGroupID)
|
||||||
val recipient = Recipient.from(context, address, false)
|
val recipient = Recipient.from(context, address, false)
|
||||||
@ -254,6 +279,18 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(groupID, publicKey)
|
return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(groupID, publicKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLastMessageServerId(room: String, server: String): Long? {
|
||||||
|
return DatabaseFactory.getLokiAPIDatabase(context).getLastMessageServerID(room, server)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLastMessageServerId(room: String, server: String, newValue: Long) {
|
||||||
|
DatabaseFactory.getLokiAPIDatabase(context).setLastMessageServerID(room, server, newValue)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeLastMessageServerId(room: String, server: String) {
|
||||||
|
DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(room, server)
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLastMessageServerID(group: Long, server: String): Long? {
|
override fun getLastMessageServerID(group: Long, server: String): Long? {
|
||||||
return DatabaseFactory.getLokiAPIDatabase(context).getLastMessageServerID(group, server)
|
return DatabaseFactory.getLokiAPIDatabase(context).getLastMessageServerID(group, server)
|
||||||
}
|
}
|
||||||
@ -266,6 +303,18 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(group, server)
|
DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(group, server)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLastDeletionServerId(room: String, server: String): Long? {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun setLastDeletionServerId(room: String, server: String, newValue: Long) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun removeLastDeletionServerId(room: String, server: String) {
|
||||||
|
TODO("Not yet implemented")
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLastDeletionServerID(group: Long, server: String): Long? {
|
override fun getLastDeletionServerID(group: Long, server: String): Long? {
|
||||||
return DatabaseFactory.getLokiAPIDatabase(context).getLastDeletionServerID(group, server)
|
return DatabaseFactory.getLokiAPIDatabase(context).getLastDeletionServerID(group, server)
|
||||||
}
|
}
|
||||||
@ -471,6 +520,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getAllV2OpenGroups(): Map<Long, OpenGroupV2> {
|
||||||
|
return DatabaseFactory.getLokiThreadDatabase(context).getAllV2OpenGroups()
|
||||||
|
}
|
||||||
|
|
||||||
override fun addOpenGroup(server: String, channel: Long) {
|
override fun addOpenGroup(server: String, channel: Long) {
|
||||||
OpenGroupUtilities.addGroup(context, server, channel)
|
OpenGroupUtilities.addGroup(context, server, channel)
|
||||||
}
|
}
|
||||||
|
@ -24,7 +24,7 @@ class PublicChatManager(private val context: Context) {
|
|||||||
private val pollers = mutableMapOf<Long, OpenGroupPoller>()
|
private val pollers = mutableMapOf<Long, OpenGroupPoller>()
|
||||||
private val observers = mutableMapOf<Long, ContentObserver>()
|
private val observers = mutableMapOf<Long, ContentObserver>()
|
||||||
private var isPolling = false
|
private var isPolling = false
|
||||||
private val executorService = Executors.newScheduledThreadPool(16)
|
private val executorService = Executors.newScheduledThreadPool(4)
|
||||||
|
|
||||||
public fun areAllCaughtUp(): Boolean {
|
public fun areAllCaughtUp(): Boolean {
|
||||||
var areAllCaughtUp = true
|
var areAllCaughtUp = true
|
||||||
|
@ -286,6 +286,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
}?.toLong()
|
}?.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLastMessageServerID(room: String, server: String): Long? {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val index = "$server.$room"
|
||||||
|
return database.get(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index)) { cursor ->
|
||||||
|
cursor.getInt(lastMessageServerID)
|
||||||
|
}?.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
override fun setLastMessageServerID(group: Long, server: String, newValue: Long) {
|
override fun setLastMessageServerID(group: Long, server: String, newValue: Long) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val index = "$server.$group"
|
val index = "$server.$group"
|
||||||
@ -293,12 +301,25 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(lastMessageServerIDTable, row, "$lastMessageServerIDTableIndex = ?", wrap(index))
|
database.insertOrUpdate(lastMessageServerIDTable, row, "$lastMessageServerIDTableIndex = ?", wrap(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setLastMessageServerID(room: String, server: String, newValue: Long) {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val index = "$server.$room"
|
||||||
|
val row = wrap(mapOf( lastMessageServerIDTableIndex to index, lastMessageServerID to newValue.toString() ))
|
||||||
|
database.insertOrUpdate(lastMessageServerIDTable, row, "$lastMessageServerIDTableIndex = ?", wrap(index))
|
||||||
|
}
|
||||||
|
|
||||||
fun removeLastMessageServerID(group: Long, server: String) {
|
fun removeLastMessageServerID(group: Long, server: String) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val index = "$server.$group"
|
val index = "$server.$group"
|
||||||
database.delete(lastMessageServerIDTable,"$lastMessageServerIDTableIndex = ?", wrap(index))
|
database.delete(lastMessageServerIDTable,"$lastMessageServerIDTableIndex = ?", wrap(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun removeLastMessageServerID(room: String, server:String) {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val index = "$server.$channel"
|
||||||
|
database.delete(lastMessageServerIDTable, "$lastMessageServerIDTableIndex = ?", wrap(index))
|
||||||
|
}
|
||||||
|
|
||||||
override fun getLastDeletionServerID(group: Long, server: String): Long? {
|
override fun getLastDeletionServerID(group: Long, server: String): Long? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
val index = "$server.$group"
|
val index = "$server.$group"
|
||||||
@ -307,6 +328,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
}?.toLong()
|
}?.toLong()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun getLastDeletionServerID(room: String, server: String): Long? {
|
||||||
|
val database = databaseHelper.readableDatabase
|
||||||
|
val index = "$server.$room"
|
||||||
|
return database.get(lastDeletionServerIDTable, "$lastDeletionServerIDTableIndex = ?", wrap(index)) { cursor ->
|
||||||
|
cursor.getInt(lastDeletionServerID)
|
||||||
|
}?.toLong()
|
||||||
|
}
|
||||||
|
|
||||||
override fun setLastDeletionServerID(group: Long, server: String, newValue: Long) {
|
override fun setLastDeletionServerID(group: Long, server: String, newValue: Long) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val index = "$server.$group"
|
val index = "$server.$group"
|
||||||
@ -314,6 +343,13 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(lastDeletionServerIDTable, row, "$lastDeletionServerIDTableIndex = ?", wrap(index))
|
database.insertOrUpdate(lastDeletionServerIDTable, row, "$lastDeletionServerIDTableIndex = ?", wrap(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setLastDeletionServerID(room: String, server: String, newValue: Long) {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val index = "$server.$room"
|
||||||
|
val row = wrap(mapOf(lastDeletionServerIDTableIndex to index, lastDeletionServerID to newValue.toString()))
|
||||||
|
database.insertOrUpdate(lastDeletionServerIDTable, row, "$lastDeletionServerIDTableIndex = ?", wrap(index))
|
||||||
|
}
|
||||||
|
|
||||||
fun removeLastDeletionServerID(group: Long, server: String) {
|
fun removeLastDeletionServerID(group: Long, server: String) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val index = "$server.$group"
|
val index = "$server.$group"
|
||||||
@ -328,6 +364,14 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
}?.toInt()
|
}?.toInt()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getUserCount(room: String, server: String): Int? {
|
||||||
|
val database = databaseHelper.readableDatabase
|
||||||
|
val index = "$server.$room"
|
||||||
|
return database.get(userCountTable, "$publicChatID = ?", wrap(index)) { cursor ->
|
||||||
|
cursor.getInt(userCount)
|
||||||
|
}?.toInt()
|
||||||
|
}
|
||||||
|
|
||||||
override fun setUserCount(group: Long, server: String, newValue: Int) {
|
override fun setUserCount(group: Long, server: String, newValue: Int) {
|
||||||
val database = databaseHelper.writableDatabase
|
val database = databaseHelper.writableDatabase
|
||||||
val index = "$server.$group"
|
val index = "$server.$group"
|
||||||
@ -335,6 +379,13 @@ class LokiAPIDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(
|
|||||||
database.insertOrUpdate(userCountTable, row, "$publicChatID = ?", wrap(index))
|
database.insertOrUpdate(userCountTable, row, "$publicChatID = ?", wrap(index))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun setUserCount(room: String, server: String, newValue: Int) {
|
||||||
|
val database = databaseHelper.writableDatabase
|
||||||
|
val index = "$server.$room"
|
||||||
|
val row = wrap(mapOf( publicChatID to index, userCount to newValue.toString() ))
|
||||||
|
database.insertOrUpdate(userCountTable, row, "$publicChatID = ?", wrap(index))
|
||||||
|
}
|
||||||
|
|
||||||
override fun getSessionRequestSentTimestamp(publicKey: String): Long? {
|
override fun getSessionRequestSentTimestamp(publicKey: String): Long? {
|
||||||
val database = databaseHelper.readableDatabase
|
val database = databaseHelper.readableDatabase
|
||||||
return database.get(sessionRequestSentTimestampTable, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey)) { cursor ->
|
return database.get(sessionRequestSentTimestampTable, "${LokiAPIDatabase.publicKey} = ?", wrap(publicKey)) { cursor ->
|
||||||
|
@ -3,20 +3,18 @@ package org.thoughtcrime.securesms.loki.database
|
|||||||
import android.content.ContentValues
|
import android.content.ContentValues
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.database.Cursor
|
import android.database.Cursor
|
||||||
|
import org.session.libsession.messaging.opengroups.OpenGroupV2
|
||||||
|
import org.session.libsession.messaging.threads.Address
|
||||||
|
import org.session.libsession.messaging.threads.recipients.Recipient
|
||||||
|
import org.session.libsignal.service.loki.api.opengroups.PublicChat
|
||||||
|
import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol
|
||||||
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
import org.thoughtcrime.securesms.database.Database
|
import org.thoughtcrime.securesms.database.Database
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
import org.thoughtcrime.securesms.database.DatabaseFactory
|
||||||
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper
|
||||||
import org.thoughtcrime.securesms.loki.utilities.*
|
import org.thoughtcrime.securesms.loki.utilities.get
|
||||||
|
import org.thoughtcrime.securesms.loki.utilities.getString
|
||||||
import org.session.libsession.messaging.threads.Address
|
import org.thoughtcrime.securesms.loki.utilities.insertOrUpdate
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
|
||||||
import org.session.libsignal.service.loki.api.opengroups.PublicChat
|
|
||||||
|
|
||||||
import org.session.libsignal.utilities.JsonUtil
|
|
||||||
import org.session.libsignal.service.loki.database.LokiThreadDatabaseProtocol
|
|
||||||
import org.session.libsignal.service.loki.utilities.PublicKeyValidation
|
|
||||||
|
|
||||||
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
|
class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiThreadDatabaseProtocol {
|
||||||
|
|
||||||
@ -57,6 +55,26 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa
|
|||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun getAllV2OpenGroups(): Map<Long, OpenGroupV2> {
|
||||||
|
val database = databaseHelper.readableDatabase
|
||||||
|
var cursor: Cursor? = null
|
||||||
|
val result = mutableMapOf<Long, OpenGroupV2>()
|
||||||
|
try {
|
||||||
|
cursor = database.rawQuery("select * from $publicChatTable", null)
|
||||||
|
while (cursor != null && cursor.moveToNext()) {
|
||||||
|
val threadID = cursor.getLong(threadID)
|
||||||
|
val string = cursor.getString(publicChat)
|
||||||
|
val openGroup = OpenGroupV2.fromJson(string)
|
||||||
|
if (openGroup != null) result[threadID] = openGroup
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
// do nothing
|
||||||
|
} finally {
|
||||||
|
cursor?.close()
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
fun getAllPublicChatServers(): Set<String> {
|
fun getAllPublicChatServers(): Set<String> {
|
||||||
return getAllPublicChats().values.fold(setOf()) { set, chat -> set.plus(chat.server) }
|
return getAllPublicChats().values.fold(setOf()) { set, chat -> set.plus(chat.server) }
|
||||||
}
|
}
|
||||||
|
@ -1,25 +1,12 @@
|
|||||||
package org.thoughtcrime.securesms.loki.protocol
|
package org.thoughtcrime.securesms.loki.protocol
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import com.google.protobuf.ByteString
|
|
||||||
import org.session.libsession.messaging.MessagingConfiguration
|
|
||||||
import org.session.libsession.messaging.messages.Destination
|
import org.session.libsession.messaging.messages.Destination
|
||||||
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
||||||
import org.session.libsession.messaging.sending_receiving.MessageSender
|
import org.session.libsession.messaging.sending_receiving.MessageSender
|
||||||
import org.session.libsession.messaging.threads.Address
|
import org.session.libsession.messaging.threads.Address
|
||||||
import org.session.libsession.messaging.threads.recipients.Recipient
|
|
||||||
import org.session.libsession.utilities.TextSecurePreferences
|
import org.session.libsession.utilities.TextSecurePreferences
|
||||||
import org.session.libsession.utilities.preferences.ProfileKeyUtil
|
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
|
||||||
import org.session.libsignal.service.internal.push.SignalServiceProtos.DataMessage
|
|
||||||
import org.session.libsignal.service.loki.utilities.removing05PrefixIfNeeded
|
|
||||||
import org.session.libsignal.utilities.Base64
|
|
||||||
import org.session.libsignal.utilities.Hex
|
|
||||||
import org.thoughtcrime.securesms.ApplicationContext
|
|
||||||
import org.thoughtcrime.securesms.database.DatabaseFactory
|
|
||||||
import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob
|
|
||||||
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
|
import org.thoughtcrime.securesms.loki.utilities.ContactUtilities
|
||||||
import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities
|
|
||||||
|
|
||||||
object MultiDeviceProtocol {
|
object MultiDeviceProtocol {
|
||||||
|
|
||||||
@ -51,80 +38,4 @@ object MultiDeviceProtocol {
|
|||||||
TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
|
TextSecurePreferences.setLastConfigurationSyncTime(context, System.currentTimeMillis())
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: remove this after we migrate to new message receiving pipeline
|
|
||||||
@JvmStatic
|
|
||||||
fun handleConfigurationMessage(context: Context, content: SignalServiceProtos.Content, senderPublicKey: String, timestamp: Long) {
|
|
||||||
synchronized(this) {
|
|
||||||
val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return
|
|
||||||
if (TextSecurePreferences.getConfigurationMessageSynced(context) && !TextSecurePreferences.shouldUpdateProfile(context, timestamp)) return
|
|
||||||
if (senderPublicKey != userPublicKey) return
|
|
||||||
TextSecurePreferences.setConfigurationMessageSynced(context, true)
|
|
||||||
TextSecurePreferences.setLastProfileUpdateTime(context, timestamp)
|
|
||||||
|
|
||||||
val configurationMessage = ConfigurationMessage.fromProto(content) ?: return
|
|
||||||
|
|
||||||
val storage = MessagingConfiguration.shared.storage
|
|
||||||
val allClosedGroupPublicKeys = storage.getAllClosedGroupPublicKeys()
|
|
||||||
|
|
||||||
val threadDatabase = DatabaseFactory.getThreadDatabase(context)
|
|
||||||
val recipientDatabase = DatabaseFactory.getRecipientDatabase(context)
|
|
||||||
|
|
||||||
val ourRecipient = Recipient.from(context, Address.fromSerialized(userPublicKey),false)
|
|
||||||
|
|
||||||
for (closedGroup in configurationMessage.closedGroups) {
|
|
||||||
if (allClosedGroupPublicKeys.contains(closedGroup.publicKey)) continue
|
|
||||||
|
|
||||||
val closedGroupUpdate = DataMessage.ClosedGroupControlMessage.newBuilder()
|
|
||||||
closedGroupUpdate.type = DataMessage.ClosedGroupControlMessage.Type.NEW
|
|
||||||
closedGroupUpdate.publicKey = ByteString.copyFrom(Hex.fromStringCondensed(closedGroup.publicKey))
|
|
||||||
closedGroupUpdate.name = closedGroup.name
|
|
||||||
val encryptionKeyPair = SignalServiceProtos.KeyPair.newBuilder()
|
|
||||||
encryptionKeyPair.publicKey = ByteString.copyFrom(closedGroup.encryptionKeyPair!!.publicKey.serialize().removing05PrefixIfNeeded())
|
|
||||||
encryptionKeyPair.privateKey = ByteString.copyFrom(closedGroup.encryptionKeyPair!!.privateKey.serialize())
|
|
||||||
closedGroupUpdate.encryptionKeyPair = encryptionKeyPair.build()
|
|
||||||
closedGroupUpdate.addAllMembers(closedGroup.members.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) })
|
|
||||||
closedGroupUpdate.addAllAdmins(closedGroup.admins.map { ByteString.copyFrom(Hex.fromStringCondensed(it)) })
|
|
||||||
|
|
||||||
ClosedGroupsProtocolV2.handleNewClosedGroup(context, closedGroupUpdate.build(), userPublicKey, timestamp)
|
|
||||||
}
|
|
||||||
val allOpenGroups = storage.getAllOpenGroups().map { it.value.server }
|
|
||||||
for (openGroup in configurationMessage.openGroups) {
|
|
||||||
if (allOpenGroups.contains(openGroup)) continue
|
|
||||||
OpenGroupUtilities.addGroup(context, openGroup, 1)
|
|
||||||
}
|
|
||||||
if (configurationMessage.displayName.isNotEmpty()) {
|
|
||||||
TextSecurePreferences.setProfileName(context, configurationMessage.displayName)
|
|
||||||
recipientDatabase.setProfileName(ourRecipient, configurationMessage.displayName)
|
|
||||||
}
|
|
||||||
if (configurationMessage.profileKey.isNotEmpty()) {
|
|
||||||
val profileKey = Base64.encodeBytes(configurationMessage.profileKey)
|
|
||||||
ProfileKeyUtil.setEncodedProfileKey(context, profileKey)
|
|
||||||
recipientDatabase.setProfileKey(ourRecipient, configurationMessage.profileKey)
|
|
||||||
if (!configurationMessage.profilePicture.isNullOrEmpty() && TextSecurePreferences.getProfilePictureURL(context) != configurationMessage.profilePicture) {
|
|
||||||
TextSecurePreferences.setProfilePictureURL(context, configurationMessage.profilePicture)
|
|
||||||
ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, configurationMessage.profilePicture))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
for (contact in configurationMessage.contacts) {
|
|
||||||
val address = Address.fromSerialized(contact.publicKey)
|
|
||||||
val recipient = Recipient.from(context, address, true)
|
|
||||||
if (!contact.profilePicture.isNullOrEmpty()) {
|
|
||||||
recipientDatabase.setProfileAvatar(recipient, contact.profilePicture)
|
|
||||||
}
|
|
||||||
if (contact.profileKey?.isNotEmpty() == true) {
|
|
||||||
recipientDatabase.setProfileKey(recipient, contact.profileKey)
|
|
||||||
}
|
|
||||||
if (contact.name.isNotEmpty()) {
|
|
||||||
recipientDatabase.setProfileName(recipient, contact.name)
|
|
||||||
}
|
|
||||||
recipientDatabase.setProfileSharing(recipient, true)
|
|
||||||
recipientDatabase.setRegistered(recipient, Recipient.RegisteredState.REGISTERED)
|
|
||||||
// create Thread if needed
|
|
||||||
threadDatabase.getOrCreateThreadIdFor(recipient)
|
|
||||||
}
|
|
||||||
if (configurationMessage.contacts.isNotEmpty()) {
|
|
||||||
threadDatabase.notifyUpdatedFromConfig()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
@ -10,6 +10,7 @@ import org.session.libsession.messaging.messages.control.ConfigurationMessage
|
|||||||
import org.session.libsession.messaging.messages.visible.Attachment
|
import org.session.libsession.messaging.messages.visible.Attachment
|
||||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||||
import org.session.libsession.messaging.opengroups.OpenGroup
|
import org.session.libsession.messaging.opengroups.OpenGroup
|
||||||
|
import org.session.libsession.messaging.opengroups.OpenGroupV2
|
||||||
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId
|
||||||
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.linkpreview.LinkPreview
|
import org.session.libsession.messaging.sending_receiving.linkpreview.LinkPreview
|
||||||
@ -51,14 +52,16 @@ interface StorageProtocol {
|
|||||||
fun isJobCanceled(job: Job): Boolean
|
fun isJobCanceled(job: Job): Boolean
|
||||||
|
|
||||||
// Authorization
|
// Authorization
|
||||||
fun getAuthToken(server: String): String?
|
fun getAuthToken(room: String, server: String): String?
|
||||||
fun setAuthToken(server: String, newValue: String?)
|
fun setAuthToken(room: String, server: String, newValue: String)
|
||||||
fun removeAuthToken(server: String)
|
fun removeAuthToken(room: String, server: String)
|
||||||
|
|
||||||
|
// Open Groups
|
||||||
|
fun getAllV2OpenGroups(): Map<Long, OpenGroupV2>
|
||||||
|
fun getV2OpenGroup(threadId: String): OpenGroupV2?
|
||||||
|
|
||||||
// Open Groups
|
// Open Groups
|
||||||
fun getOpenGroup(threadID: String): OpenGroup?
|
|
||||||
fun getThreadID(openGroupID: String): String?
|
fun getThreadID(openGroupID: String): String?
|
||||||
fun getAllOpenGroups(): Map<Long, OpenGroup>
|
|
||||||
fun addOpenGroup(server: String, channel: Long)
|
fun addOpenGroup(server: String, channel: Long)
|
||||||
fun setOpenGroupServerMessageID(messageID: Long, serverID: Long)
|
fun setOpenGroupServerMessageID(messageID: Long, serverID: Long)
|
||||||
fun getQuoteServerID(quoteID: Long, publicKey: String): Long?
|
fun getQuoteServerID(quoteID: Long, publicKey: String): Long?
|
||||||
@ -72,21 +75,19 @@ interface StorageProtocol {
|
|||||||
fun getOpenGroupDisplayName(publicKey: String, channel: Long, server: String): String?
|
fun getOpenGroupDisplayName(publicKey: String, channel: Long, server: String): String?
|
||||||
|
|
||||||
// Open Group Metadata
|
// Open Group Metadata
|
||||||
fun setUserCount(group: Long, server: String, newValue: Int)
|
|
||||||
fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: String)
|
|
||||||
fun getOpenGroupProfilePictureURL(group: Long, server: String): String?
|
|
||||||
fun updateTitle(groupID: String, newValue: String)
|
fun updateTitle(groupID: String, newValue: String)
|
||||||
fun updateProfilePicture(groupID: String, newValue: ByteArray)
|
fun updateProfilePicture(groupID: String, newValue: ByteArray)
|
||||||
|
|
||||||
// Last Message Server ID
|
// Last Message Server ID
|
||||||
fun getLastMessageServerID(group: Long, server: String): Long?
|
fun getLastMessageServerId(room: String, server: String): Long?
|
||||||
fun setLastMessageServerID(group: Long, server: String, newValue: Long)
|
fun setLastMessageServerId(room: String, server: String, newValue: Long)
|
||||||
fun removeLastMessageServerID(group: Long, server: String)
|
fun removeLastMessageServerId(room: String, server: String)
|
||||||
|
|
||||||
// Last Deletion Server ID
|
// Last Deletion Server ID
|
||||||
fun getLastDeletionServerID(group: Long, server: String): Long?
|
fun getLastDeletionServerId(room: String, server: String): Long?
|
||||||
fun setLastDeletionServerID(group: Long, server: String, newValue: Long)
|
fun setLastDeletionServerId(room: String, server: String, newValue: Long)
|
||||||
fun removeLastDeletionServerID(group: Long, server: String)
|
fun removeLastDeletionServerId(room: String, server: String)
|
||||||
|
|
||||||
// Message Handling
|
// Message Handling
|
||||||
fun isMessageDuplicated(timestamp: Long, sender: String): Boolean
|
fun isMessageDuplicated(timestamp: Long, sender: String): Boolean
|
||||||
@ -158,4 +159,25 @@ interface StorageProtocol {
|
|||||||
// Message Handling
|
// Message Handling
|
||||||
/// Returns the ID of the `TSIncomingMessage` that was constructed.
|
/// Returns the ID of the `TSIncomingMessage` that was constructed.
|
||||||
fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>): Long?
|
fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>): Long?
|
||||||
|
|
||||||
|
// DEPRECATED
|
||||||
|
fun getAuthToken(server: String): String?
|
||||||
|
fun setAuthToken(server: String, newValue: String?)
|
||||||
|
fun removeAuthToken(server: String)
|
||||||
|
|
||||||
|
fun getLastMessageServerID(group: Long, server: String): Long?
|
||||||
|
fun setLastMessageServerID(group: Long, server: String, newValue: Long)
|
||||||
|
fun removeLastMessageServerID(group: Long, server: String)
|
||||||
|
|
||||||
|
fun getLastDeletionServerID(group: Long, server: String): Long?
|
||||||
|
fun setLastDeletionServerID(group: Long, server: String, newValue: Long)
|
||||||
|
fun removeLastDeletionServerID(group: Long, server: String)
|
||||||
|
|
||||||
|
fun getOpenGroup(threadID: String): OpenGroup?
|
||||||
|
fun getAllOpenGroups(): Map<Long, OpenGroup>
|
||||||
|
|
||||||
|
fun setUserCount(group: Long, server: String, newValue: Int)
|
||||||
|
fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: String)
|
||||||
|
fun getOpenGroupProfilePictureURL(group: Long, server: String): String?
|
||||||
|
|
||||||
}
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
package org.session.libsession.messaging.opengroups
|
||||||
|
|
||||||
|
import org.session.libsession.messaging.opengroups.OpenGroupAPIV2.Error
|
||||||
|
import org.session.libsession.messaging.utilities.DotNetAPI
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
class OpenGroupAPIV2: DotNetAPI() {
|
||||||
|
|
||||||
|
enum class Error {
|
||||||
|
GENERIC,
|
||||||
|
PARSING_FAILED,
|
||||||
|
DECRYPTION_FAILED,
|
||||||
|
SIGNING_FAILED,
|
||||||
|
INVALID_URL,
|
||||||
|
NO_PUBLIC_KEY
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private val moderators: HashMap<String, HashMap<String, Set<String>>> = hashMapOf() // Server URL to (channel ID to set of moderator IDs)
|
||||||
|
const val DEFAULT_SERVER = "https://sessionopengroup.com"
|
||||||
|
const val DEFAULT_SERVER_PUBLIC_KEY = "658d29b91892a2389505596b135e76a53db6e11d613a51dbd3d0816adffb231b"
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
data class Info(val id: String, val name: String, val imageId: String?)
|
||||||
|
|
||||||
|
fun Error.errorDescription() = when (this) {
|
||||||
|
Error.GENERIC -> "An error occurred."
|
||||||
|
Error.PARSING_FAILED -> "Invalid response."
|
||||||
|
Error.DECRYPTION_FAILED -> "Couldn't decrypt response."
|
||||||
|
Error.SIGNING_FAILED -> "Couldn't sign message."
|
||||||
|
Error.INVALID_URL -> "Invalid URL."
|
||||||
|
Error.NO_PUBLIC_KEY -> "Couldn't find server public key."
|
||||||
|
}
|
@ -0,0 +1,45 @@
|
|||||||
|
package org.session.libsession.messaging.opengroups
|
||||||
|
|
||||||
|
import org.session.libsignal.utilities.JsonUtil
|
||||||
|
import java.util.*
|
||||||
|
|
||||||
|
data class OpenGroupV2(
|
||||||
|
val server: String,
|
||||||
|
val room: String,
|
||||||
|
val id: String,
|
||||||
|
val name: String,
|
||||||
|
val publicKey: String,
|
||||||
|
val imageId: String?
|
||||||
|
) {
|
||||||
|
|
||||||
|
constructor(server: String, room: String, name: String, publicKey: String, imageId: String?) : this(
|
||||||
|
server = server,
|
||||||
|
room = room,
|
||||||
|
id = "$server.$room",
|
||||||
|
name = name,
|
||||||
|
publicKey = publicKey,
|
||||||
|
imageId = imageId
|
||||||
|
)
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
|
||||||
|
fun fromJson(jsonAsString: String): OpenGroupV2? {
|
||||||
|
return try {
|
||||||
|
val json = JsonUtil.fromJson(jsonAsString)
|
||||||
|
if (!json.has("room")) return null
|
||||||
|
|
||||||
|
val room = json.get("room").asText().toLowerCase(Locale.getDefault())
|
||||||
|
val server = json.get("server").asText().toLowerCase(Locale.getDefault())
|
||||||
|
val displayName = json.get("displayName").asText()
|
||||||
|
val publicKey = json.get("publicKey").asText()
|
||||||
|
val imageId = json.get("imageId").asText().let { str -> if (str.isEmpty()) null else str }
|
||||||
|
|
||||||
|
OpenGroupV2(server, room, displayName, publicKey, imageId)
|
||||||
|
} catch (e: Exception) {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -105,8 +105,10 @@ private fun MessageReceiver.handleConfigurationMessage(message: ConfigurationMes
|
|||||||
handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, closeGroup.publicKey, closeGroup.name, closeGroup.encryptionKeyPair!!, closeGroup.members, closeGroup.admins, message.sentTimestamp!!)
|
handleNewClosedGroup(message.sender!!, message.sentTimestamp!!, closeGroup.publicKey, closeGroup.name, closeGroup.encryptionKeyPair!!, closeGroup.members, closeGroup.admins, message.sentTimestamp!!)
|
||||||
}
|
}
|
||||||
val allOpenGroups = storage.getAllOpenGroups().map { it.value.server }
|
val allOpenGroups = storage.getAllOpenGroups().map { it.value.server }
|
||||||
|
val allV2OpenGroups = storage.getAllV2OpenGroups().map { it.value.server }
|
||||||
for (openGroup in message.openGroups) {
|
for (openGroup in message.openGroups) {
|
||||||
if (allOpenGroups.contains(openGroup)) continue
|
if (allOpenGroups.contains(openGroup)) continue
|
||||||
|
// TODO: add in v2
|
||||||
storage.addOpenGroup(openGroup, 1)
|
storage.addOpenGroup(openGroup, 1)
|
||||||
}
|
}
|
||||||
if (message.displayName.isNotEmpty()) {
|
if (message.displayName.isNotEmpty()) {
|
||||||
|
@ -0,0 +1,225 @@
|
|||||||
|
package org.session.libsession.messaging.sending_receiving.pollers
|
||||||
|
|
||||||
|
import com.google.protobuf.ByteString
|
||||||
|
import nl.komponents.kovenant.Promise
|
||||||
|
import nl.komponents.kovenant.deferred
|
||||||
|
import org.session.libsession.messaging.MessagingConfiguration
|
||||||
|
import org.session.libsession.messaging.jobs.JobQueue
|
||||||
|
import org.session.libsession.messaging.jobs.MessageReceiveJob
|
||||||
|
import org.session.libsession.messaging.opengroups.*
|
||||||
|
import org.session.libsignal.service.internal.push.SignalServiceProtos
|
||||||
|
import org.session.libsignal.utilities.logging.Log
|
||||||
|
import org.session.libsignal.utilities.successBackground
|
||||||
|
import java.util.concurrent.ScheduledExecutorService
|
||||||
|
import java.util.concurrent.ScheduledFuture
|
||||||
|
import java.util.concurrent.TimeUnit
|
||||||
|
|
||||||
|
class OpenGroupV2Poller(private val openGroup: OpenGroupV2, private val executorService: ScheduledExecutorService? = null) {
|
||||||
|
|
||||||
|
private var hasStarted = false
|
||||||
|
@Volatile private var isPollOngoing = false
|
||||||
|
var isCaughtUp = false
|
||||||
|
|
||||||
|
private val cancellableFutures = mutableListOf<ScheduledFuture<out Any>>()
|
||||||
|
|
||||||
|
// region Convenience
|
||||||
|
private val userHexEncodedPublicKey = MessagingConfiguration.shared.storage.getUserPublicKey() ?: ""
|
||||||
|
private var displayNameUpdates = setOf<String>()
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Settings
|
||||||
|
companion object {
|
||||||
|
private val pollForNewMessagesInterval: Long = 10 * 1000
|
||||||
|
private val pollForDeletedMessagesInterval: Long = 60 * 1000
|
||||||
|
private val pollForModeratorsInterval: Long = 10 * 60 * 1000
|
||||||
|
private val pollForDisplayNamesInterval: Long = 60 * 1000
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Lifecycle
|
||||||
|
fun startIfNeeded() {
|
||||||
|
if (hasStarted || executorService == null) return
|
||||||
|
cancellableFutures += listOf(
|
||||||
|
executorService.scheduleAtFixedRate(::pollForNewMessages,0, pollForNewMessagesInterval, TimeUnit.MILLISECONDS),
|
||||||
|
executorService.scheduleAtFixedRate(::pollForDeletedMessages,0, pollForDeletedMessagesInterval, TimeUnit.MILLISECONDS),
|
||||||
|
executorService.scheduleAtFixedRate(::pollForModerators,0, pollForModeratorsInterval, TimeUnit.MILLISECONDS),
|
||||||
|
executorService.scheduleAtFixedRate(::pollForDisplayNames,0, pollForDisplayNamesInterval, TimeUnit.MILLISECONDS)
|
||||||
|
)
|
||||||
|
hasStarted = true
|
||||||
|
}
|
||||||
|
|
||||||
|
fun stop() {
|
||||||
|
cancellableFutures.forEach { future ->
|
||||||
|
future.cancel(false)
|
||||||
|
}
|
||||||
|
cancellableFutures.clear()
|
||||||
|
hasStarted = false
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
|
||||||
|
// region Polling
|
||||||
|
fun pollForNewMessages(): Promise<Unit, Exception> {
|
||||||
|
return pollForNewMessages(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pollForNewMessages(isBackgroundPoll: Boolean): Promise<Unit, Exception> {
|
||||||
|
if (isPollOngoing) { return Promise.of(Unit) }
|
||||||
|
isPollOngoing = true
|
||||||
|
val deferred = deferred<Unit, Exception>()
|
||||||
|
// Kovenant propagates a context to chained promises, so OpenGroupAPI.sharedContext should be used for all of the below
|
||||||
|
OpenGroupAPIV2.getMessages(openGroup.room, openGroup.server).successBackground { messages ->
|
||||||
|
// Process messages in the background
|
||||||
|
Log.d("Loki", "received ${messages.size} messages")
|
||||||
|
messages.forEach { message ->
|
||||||
|
try {
|
||||||
|
val senderPublicKey = message.senderPublicKey
|
||||||
|
fun generateDisplayName(rawDisplayName: String): String {
|
||||||
|
return "$rawDisplayName (...${senderPublicKey.takeLast(8)})"
|
||||||
|
}
|
||||||
|
val senderDisplayName = MessagingConfiguration.shared.storage.getOpenGroupDisplayName(senderPublicKey, openGroup.channel, openGroup.server) ?: generateDisplayName(message.displayName)
|
||||||
|
val id = openGroup.id.toByteArray()
|
||||||
|
// Main message
|
||||||
|
val dataMessageProto = SignalServiceProtos.DataMessage.newBuilder()
|
||||||
|
val body = if (message.body == message.timestamp.toString()) { "" } else { message.body }
|
||||||
|
dataMessageProto.setBody(body)
|
||||||
|
dataMessageProto.setTimestamp(message.timestamp)
|
||||||
|
// Attachments
|
||||||
|
val attachmentProtos = message.attachments.mapNotNull { attachment ->
|
||||||
|
try {
|
||||||
|
if (attachment.kind != OpenGroupMessage.Attachment.Kind.Attachment) { return@mapNotNull null }
|
||||||
|
val attachmentProto = SignalServiceProtos.AttachmentPointer.newBuilder()
|
||||||
|
attachmentProto.setId(attachment.serverID)
|
||||||
|
attachmentProto.setContentType(attachment.contentType)
|
||||||
|
attachmentProto.setSize(attachment.size)
|
||||||
|
attachmentProto.setFileName(attachment.fileName)
|
||||||
|
attachmentProto.setFlags(attachment.flags)
|
||||||
|
attachmentProto.setWidth(attachment.width)
|
||||||
|
attachmentProto.setHeight(attachment.height)
|
||||||
|
attachment.caption?.let { attachmentProto.setCaption(it) }
|
||||||
|
attachmentProto.setUrl(attachment.url)
|
||||||
|
attachmentProto.build()
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Loki","Failed to parse attachment as proto",e)
|
||||||
|
null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
dataMessageProto.addAllAttachments(attachmentProtos)
|
||||||
|
// Link preview
|
||||||
|
val linkPreview = message.attachments.firstOrNull { it.kind == OpenGroupMessage.Attachment.Kind.LinkPreview }
|
||||||
|
if (linkPreview != null) {
|
||||||
|
val linkPreviewProto = SignalServiceProtos.DataMessage.Preview.newBuilder()
|
||||||
|
linkPreviewProto.setUrl(linkPreview.linkPreviewURL!!)
|
||||||
|
linkPreviewProto.setTitle(linkPreview.linkPreviewTitle!!)
|
||||||
|
val attachmentProto = SignalServiceProtos.AttachmentPointer.newBuilder()
|
||||||
|
attachmentProto.setId(linkPreview.serverID)
|
||||||
|
attachmentProto.setContentType(linkPreview.contentType)
|
||||||
|
attachmentProto.setSize(linkPreview.size)
|
||||||
|
attachmentProto.setFileName(linkPreview.fileName)
|
||||||
|
attachmentProto.setFlags(linkPreview.flags)
|
||||||
|
attachmentProto.setWidth(linkPreview.width)
|
||||||
|
attachmentProto.setHeight(linkPreview.height)
|
||||||
|
linkPreview.caption?.let { attachmentProto.setCaption(it) }
|
||||||
|
attachmentProto.setUrl(linkPreview.url)
|
||||||
|
linkPreviewProto.setImage(attachmentProto.build())
|
||||||
|
dataMessageProto.addPreview(linkPreviewProto.build())
|
||||||
|
}
|
||||||
|
// Quote
|
||||||
|
val quote = message.quote
|
||||||
|
if (quote != null) {
|
||||||
|
val quoteProto = SignalServiceProtos.DataMessage.Quote.newBuilder()
|
||||||
|
quoteProto.setId(quote.quotedMessageTimestamp)
|
||||||
|
quoteProto.setAuthor(quote.quoteePublicKey)
|
||||||
|
if (quote.quotedMessageBody != quote.quotedMessageTimestamp.toString()) { quoteProto.setText(quote.quotedMessageBody) }
|
||||||
|
dataMessageProto.setQuote(quoteProto.build())
|
||||||
|
}
|
||||||
|
val messageServerID = message.serverID
|
||||||
|
// Profile
|
||||||
|
val profileProto = SignalServiceProtos.DataMessage.LokiProfile.newBuilder()
|
||||||
|
profileProto.setDisplayName(senderDisplayName)
|
||||||
|
val profilePicture = message.profilePicture
|
||||||
|
if (profilePicture != null) {
|
||||||
|
profileProto.setProfilePicture(profilePicture.url)
|
||||||
|
dataMessageProto.setProfileKey(ByteString.copyFrom(profilePicture.profileKey))
|
||||||
|
}
|
||||||
|
dataMessageProto.setProfile(profileProto.build())
|
||||||
|
/* TODO: the signal service proto needs to be synced with iOS
|
||||||
|
// Open group info
|
||||||
|
if (messageServerID != null) {
|
||||||
|
val openGroupProto = PublicChatInfo.newBuilder()
|
||||||
|
openGroupProto.setServerID(messageServerID)
|
||||||
|
dataMessageProto.setPublicChatInfo(openGroupProto.build())
|
||||||
|
}
|
||||||
|
*/
|
||||||
|
// Signal group context
|
||||||
|
val groupProto = SignalServiceProtos.GroupContext.newBuilder()
|
||||||
|
groupProto.setId(ByteString.copyFrom(id))
|
||||||
|
groupProto.setType(SignalServiceProtos.GroupContext.Type.DELIVER)
|
||||||
|
groupProto.setName(openGroup.displayName)
|
||||||
|
dataMessageProto.setGroup(groupProto.build())
|
||||||
|
// Content
|
||||||
|
val content = SignalServiceProtos.Content.newBuilder()
|
||||||
|
content.setDataMessage(dataMessageProto.build())
|
||||||
|
// Envelope
|
||||||
|
val builder = SignalServiceProtos.Envelope.newBuilder()
|
||||||
|
builder.type = SignalServiceProtos.Envelope.Type.UNIDENTIFIED_SENDER
|
||||||
|
builder.source = senderPublicKey
|
||||||
|
builder.sourceDevice = 1
|
||||||
|
builder.setContent(content.build().toByteString())
|
||||||
|
builder.timestamp = message.timestamp
|
||||||
|
builder.serverTimestamp = message.serverTimestamp
|
||||||
|
val envelope = builder.build()
|
||||||
|
val job = MessageReceiveJob(envelope.toByteArray(), isBackgroundPoll, messageServerID, openGroup.id)
|
||||||
|
Log.d("Loki", "Scheduling Job $job")
|
||||||
|
if (isBackgroundPoll) {
|
||||||
|
job.executeAsync().always { deferred.resolve(Unit) }
|
||||||
|
// The promise is just used to keep track of when we're done
|
||||||
|
} else {
|
||||||
|
JobQueue.shared.add(job)
|
||||||
|
}
|
||||||
|
} catch (e: Exception) {
|
||||||
|
Log.e("Loki", "Exception parsing message", e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
displayNameUpdates = displayNameUpdates + messages.map { it.senderPublicKey }.toSet() - userHexEncodedPublicKey
|
||||||
|
executorService?.schedule(::pollForDisplayNames, 0, TimeUnit.MILLISECONDS)
|
||||||
|
isCaughtUp = true
|
||||||
|
isPollOngoing = false
|
||||||
|
deferred.resolve(Unit)
|
||||||
|
}.fail {
|
||||||
|
Log.d("Loki", "Failed to get messages for group chat with ID: ${openGroup.channel} on server: ${openGroup.server}.")
|
||||||
|
isPollOngoing = false
|
||||||
|
}
|
||||||
|
return deferred.promise
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pollForDisplayNames() {
|
||||||
|
if (displayNameUpdates.isEmpty()) { return }
|
||||||
|
val hexEncodedPublicKeys = displayNameUpdates
|
||||||
|
displayNameUpdates = setOf()
|
||||||
|
OpenGroupAPI.getDisplayNames(hexEncodedPublicKeys, openGroup.server).successBackground { mapping ->
|
||||||
|
for (pair in mapping.entries) {
|
||||||
|
if (pair.key == userHexEncodedPublicKey) continue
|
||||||
|
val senderDisplayName = "${pair.value} (...${pair.key.substring(pair.key.count() - 8)})"
|
||||||
|
MessagingConfiguration.shared.storage.setOpenGroupDisplayName(pair.key, openGroup.channel, openGroup.server, senderDisplayName)
|
||||||
|
}
|
||||||
|
}.fail {
|
||||||
|
displayNameUpdates = displayNameUpdates.union(hexEncodedPublicKeys)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pollForDeletedMessages() {
|
||||||
|
OpenGroupAPI.getDeletedMessageServerIDs(openGroup.channel, openGroup.server).success { deletedMessageServerIDs ->
|
||||||
|
val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { MessagingConfiguration.shared.messageDataProvider.getMessageID(it) }
|
||||||
|
deletedMessageIDs.forEach {
|
||||||
|
MessagingConfiguration.shared.messageDataProvider.deleteMessage(it)
|
||||||
|
}
|
||||||
|
}.fail {
|
||||||
|
Log.d("Loki", "Failed to get deleted messages for group chat with ID: ${openGroup.channel} on server: ${openGroup.server}.")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun pollForModerators() {
|
||||||
|
OpenGroupAPI.getModerators(openGroup.channel, openGroup.server)
|
||||||
|
}
|
||||||
|
// endregion
|
||||||
|
}
|
@ -20,6 +20,7 @@ public data class PublicChat(
|
|||||||
@JvmStatic fun fromJSON(jsonAsString: String): PublicChat? {
|
@JvmStatic fun fromJSON(jsonAsString: String): PublicChat? {
|
||||||
try {
|
try {
|
||||||
val json = JsonUtil.fromJson(jsonAsString)
|
val json = JsonUtil.fromJson(jsonAsString)
|
||||||
|
if (!json.has("channel")) return null
|
||||||
val channel = json.get("channel").asLong()
|
val channel = json.get("channel").asLong()
|
||||||
val server = json.get("server").asText().toLowerCase()
|
val server = json.get("server").asText().toLowerCase()
|
||||||
val displayName = json.get("displayName").asText()
|
val displayName = json.get("displayName").asText()
|
||||||
|
@ -24,6 +24,11 @@ interface LokiAPIDatabaseProtocol {
|
|||||||
fun getLastDeletionServerID(group: Long, server: String): Long?
|
fun getLastDeletionServerID(group: Long, server: String): Long?
|
||||||
fun setLastDeletionServerID(group: Long, server: String, newValue: Long)
|
fun setLastDeletionServerID(group: Long, server: String, newValue: Long)
|
||||||
fun setUserCount(group: Long, server: String, newValue: Int)
|
fun setUserCount(group: Long, server: String, newValue: Int)
|
||||||
|
fun getLastMessageServerID(room: String, server: String): Long?
|
||||||
|
fun setLastMessageServerID(room: String, server: String, newValue: Long)
|
||||||
|
fun getLastDeletionServerID(room: String, server: String): Long?
|
||||||
|
fun setLastDeletionServerID(room: String, server: String, newValue: Long)
|
||||||
|
fun setUserCount(room: String, server: String, newValue: Int)
|
||||||
fun getSessionRequestSentTimestamp(publicKey: String): Long?
|
fun getSessionRequestSentTimestamp(publicKey: String): Long?
|
||||||
fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long)
|
fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long)
|
||||||
fun getSessionRequestProcessedTimestamp(publicKey: String): Long?
|
fun getSessionRequestProcessedTimestamp(publicKey: String): Long?
|
||||||
|
Loading…
Reference in New Issue
Block a user