package org.thoughtcrime.securesms.database import android.content.Context import android.net.Uri import org.session.libsession.messaging.StorageProtocol import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.signal.* import org.session.libsession.messaging.messages.signal.IncomingTextMessage import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroupV2 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.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.threads.Address import org.session.libsession.messaging.threads.Address.Companion.fromSerialized import org.session.libsession.messaging.threads.GroupRecord import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.messaging.utilities.ClosedGroupUpdateMessageData import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.IdentityKeyUtil import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.preferences.ProfileKeyUtil import org.session.libsignal.libsignal.ecc.ECKeyPair import org.session.libsignal.libsignal.util.KeyHelper import org.session.libsignal.libsignal.util.guava.Optional import org.session.libsignal.service.api.messages.SignalServiceAttachmentPointer import org.session.libsignal.service.api.messages.SignalServiceGroup import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase import org.thoughtcrime.securesms.loki.protocol.SessionMetaProtocol import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities import org.thoughtcrime.securesms.loki.utilities.get import org.thoughtcrime.securesms.loki.utilities.getString import org.thoughtcrime.securesms.mms.PartAuthority class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), StorageProtocol { override fun getUserPublicKey(): String? { return TextSecurePreferences.getLocalNumber(context) } override fun getUserKeyPair(): Pair? { val userPublicKey = TextSecurePreferences.getLocalNumber(context) ?: return null val userPrivateKey = IdentityKeyUtil.getIdentityKeyPair(context).privateKey.serialize() return Pair(userPublicKey, userPrivateKey) } override fun getUserX25519KeyPair(): ECKeyPair { return DatabaseFactory.getLokiAPIDatabase(context).getUserX25519KeyPair() } override fun getUserDisplayName(): String? { return TextSecurePreferences.getProfileName(context) } override fun getUserProfileKey(): ByteArray? { return ProfileKeyUtil.getProfileKey(context) } override fun getUserProfilePictureURL(): String? { return TextSecurePreferences.getProfilePictureURL(context) } override fun setUserProfilePictureUrl(newProfilePicture: String) { val ourRecipient = Address.fromSerialized(getUserPublicKey()!!).let { Recipient.from(context, it, false) } TextSecurePreferences.setProfilePictureURL(context, newProfilePicture) RetrieveProfileAvatarJob(ourRecipient, newProfilePicture) ApplicationContext.getInstance(context).jobManager.add(RetrieveProfileAvatarJob(ourRecipient, newProfilePicture)) } override fun getProfileKeyForRecipient(recipientPublicKey: String): ByteArray? { val address = Address.fromSerialized(recipientPublicKey) val recipient = Recipient.from(context, address, false) return recipient.profileKey } override fun getDisplayNameForRecipient(recipientPublicKey: String): String? { val database = DatabaseFactory.getLokiUserDatabase(context) return database.getDisplayName(recipientPublicKey) } override fun setProfileKeyForRecipient(recipientPublicKey: String, profileKey: ByteArray) { val address = Address.fromSerialized(recipientPublicKey) val recipient = Recipient.from(context, address, false) DatabaseFactory.getRecipientDatabase(context).setProfileKey(recipient, profileKey) } override fun getOrGenerateRegistrationID(): Int { var registrationID = TextSecurePreferences.getLocalRegistrationId(context) if (registrationID == 0) { registrationID = KeyHelper.generateRegistrationId(false) TextSecurePreferences.setLocalRegistrationId(context, registrationID) } return registrationID } override fun persistAttachments(messageId: Long, attachments: List): List { val database = DatabaseFactory.getAttachmentDatabase(context) val databaseAttachments = attachments.mapNotNull { it.toSignalAttachment() } return database.insertAttachments(messageId, databaseAttachments) } override fun getAttachmentsForMessage(messageId: Long): List { val database = DatabaseFactory.getAttachmentDatabase(context) return database.getAttachmentsForMessage(messageId) } override fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? { var messageID: Long? = null val senderAddress = Address.fromSerialized(message.sender!!) val isUserSender = message.sender!! == getUserPublicKey() val group: Optional = when { openGroupID != null -> Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT)) groupPublicKey != null -> { val doubleEncoded = GroupUtil.doubleEncodeGroupID(groupPublicKey) Optional.of(SignalServiceGroup(GroupUtil.getDecodedGroupIDAsData(doubleEncoded), SignalServiceGroup.GroupType.SIGNAL)) } else -> Optional.absent() } val pointerAttachments = attachments.mapNotNull { it.toSignalAttachment() } val targetAddress = if (isUserSender && !message.syncTarget.isNullOrEmpty()) { Address.fromSerialized(message.syncTarget!!) } else if (group.isPresent) { Address.fromSerialized(GroupUtil.getEncodedId(group.get())) } else { senderAddress } val targetRecipient = Recipient.from(context, targetAddress, false) if (message.isMediaMessage() || attachments.isNotEmpty()) { val quote: Optional = if (quotes != null) Optional.of(quotes) else Optional.absent() val linkPreviews: Optional> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! }) val mmsDatabase = DatabaseFactory.getMmsDatabase(context) val insertResult = if (message.sender == getUserPublicKey()) { val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointerAttachments, quote.orNull(), linkPreviews.orNull()?.firstOrNull()) mmsDatabase.beginTransaction() mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) } else { // It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment val signalServiceAttachments = attachments.mapNotNull { it.toSignalPointer() } val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews) mmsDatabase.beginTransaction() mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0) } if (insertResult.isPresent) { mmsDatabase.setTransactionSuccessful() messageID = insertResult.get().messageId } mmsDatabase.endTransaction() } else { val smsDatabase = DatabaseFactory.getSmsDatabase(context) val insertResult = if (message.sender == getUserPublicKey()) { val textMessage = OutgoingTextMessage.from(message, targetRecipient) smsDatabase.insertMessageOutbox(message.threadID ?: -1, textMessage, message.sentTimestamp!!) } else { val textMessage = IncomingTextMessage.from(message, senderAddress, group, targetRecipient.expireMessages * 1000L) val encrypted = IncomingEncryptedMessage(textMessage, textMessage.messageBody) smsDatabase.insertMessageInbox(encrypted, message.receivedTimestamp ?: 0) } insertResult.orNull()?.let { result -> messageID = result.messageId } } return messageID } // JOBS override fun persistJob(job: Job) { DatabaseFactory.getSessionJobDatabase(context).persistJob(job) } override fun markJobAsSucceeded(job: Job) { DatabaseFactory.getSessionJobDatabase(context).markJobAsSucceeded(job) } override fun markJobAsFailed(job: Job) { DatabaseFactory.getSessionJobDatabase(context).markJobAsFailed(job) } override fun getAllPendingJobs(type: String): List { return DatabaseFactory.getSessionJobDatabase(context).getAllPendingJobs(type) } override fun getAttachmentUploadJob(attachmentID: Long): AttachmentUploadJob? { return DatabaseFactory.getSessionJobDatabase(context).getAttachmentUploadJob(attachmentID) } override fun getMessageSendJob(messageSendJobID: String): MessageSendJob? { return DatabaseFactory.getSessionJobDatabase(context).getMessageSendJob(messageSendJobID) } override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) { val job = DatabaseFactory.getSessionJobDatabase(context).getMessageSendJob(messageSendJobID) ?: return JobQueue.shared.add(job) } override fun isJobCanceled(job: Job): Boolean { return DatabaseFactory.getSessionJobDatabase(context).isJobCanceled(job) } // Authorization override fun getAuthToken(server: String): String? { return DatabaseFactory.getLokiAPIDatabase(context).getAuthToken(server) } override fun setAuthToken(server: String, newValue: String?) { DatabaseFactory.getLokiAPIDatabase(context).setAuthToken(server, newValue) } override fun removeAuthToken(server: String) { 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? { 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) OpenGroup.fromJSON(publicChatAsJSON) } } 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 { val address = Address.fromSerialized(openGroupID) val recipient = Recipient.from(context, address, false) return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient).toString() } override fun getOpenGroupPublicKey(server: String): String? { return DatabaseFactory.getLokiAPIDatabase(context).getOpenGroupPublicKey(server) } override fun setOpenGroupPublicKey(server: String, newValue: String) { DatabaseFactory.getLokiAPIDatabase(context).setOpenGroupPublicKey(server, newValue) } override fun setOpenGroupDisplayName(publicKey: String, channel: Long, server: String, displayName: String) { val groupID = "$server.$channel" DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(groupID, publicKey, displayName) } override fun setOpenGroupDisplayName(publicKey: String, room: String, server: String, displayName: String) { val groupID = "$server.$room" DatabaseFactory.getLokiUserDatabase(context).setServerDisplayName(groupID, publicKey, displayName) } override fun getOpenGroupDisplayName(publicKey: String, channel: Long, server: String): String? { val groupID = "$server.$channel" return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(groupID, publicKey) } override fun getOpenGroupDisplayName(publicKey: String, room: String, server: String): String? { val groupID = "$server.$room" 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? { return DatabaseFactory.getLokiAPIDatabase(context).getLastMessageServerID(group, server) } override fun setLastMessageServerID(group: Long, server: String, newValue: Long) { DatabaseFactory.getLokiAPIDatabase(context).setLastMessageServerID(group, server, newValue) } override fun removeLastMessageServerID(group: Long, server: String) { DatabaseFactory.getLokiAPIDatabase(context).removeLastMessageServerID(group, server) } override fun getLastDeletionServerId(room: String, server: String): Long? { return DatabaseFactory.getLokiAPIDatabase(context).getLastDeletionServerID(room, server) } override fun setLastDeletionServerId(room: String, server: String, newValue: Long) { DatabaseFactory.getLokiAPIDatabase(context).setLastDeletionServerID(room, server, newValue) } override fun removeLastDeletionServerId(room: String, server: String) { DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(room, server) } override fun setUserCount(room: String, server: String, newValue: Int) { DatabaseFactory.getLokiAPIDatabase(context).setUserCount(room, server, newValue) } override fun getLastDeletionServerID(group: Long, server: String): Long? { return DatabaseFactory.getLokiAPIDatabase(context).getLastDeletionServerID(group, server) } override fun setLastDeletionServerID(group: Long, server: String, newValue: Long) { DatabaseFactory.getLokiAPIDatabase(context).setLastDeletionServerID(group, server, newValue) } override fun removeLastDeletionServerID(group: Long, server: String) { DatabaseFactory.getLokiAPIDatabase(context).removeLastDeletionServerID(group, server) } override fun isMessageDuplicated(timestamp: Long, sender: String): Boolean { return getReceivedMessageTimestamps().contains(timestamp) } override fun setUserCount(group: Long, server: String, newValue: Int) { DatabaseFactory.getLokiAPIDatabase(context).setUserCount(group, server, newValue) } override fun setOpenGroupProfilePictureURL(group: Long, server: String, newValue: String) { DatabaseFactory.getLokiAPIDatabase(context).setOpenGroupProfilePictureURL(group, server, newValue) } override fun getOpenGroupProfilePictureURL(group: Long, server: String): String? { return DatabaseFactory.getLokiAPIDatabase(context).getOpenGroupProfilePictureURL(group, server) } override fun updateTitle(groupID: String, newValue: String) { DatabaseFactory.getGroupDatabase(context).updateTitle(groupID, newValue) } override fun updateProfilePicture(groupID: String, newValue: ByteArray) { DatabaseFactory.getGroupDatabase(context).updateProfilePicture(groupID, newValue) } override fun getReceivedMessageTimestamps(): Set { return SessionMetaProtocol.getTimestamps() } override fun addReceivedMessageTimestamp(timestamp: Long) { SessionMetaProtocol.addTimestamp(timestamp) } // override fun removeReceivedMessageTimestamps(timestamps: Set) { // TODO("Not yet implemented") // } override fun getMessageIdInDatabase(timestamp: Long, author: String): Long? { val database = DatabaseFactory.getMmsSmsDatabase(context) val address = Address.fromSerialized(author) return database.getMessageFor(timestamp, address)?.getId() } override fun setOpenGroupServerMessageID(messageID: Long, serverID: Long, threadID: Long, isSms: Boolean) { DatabaseFactory.getLokiMessageDatabase(context).setServerID(messageID, serverID, isSms) DatabaseFactory.getLokiMessageDatabase(context).setOriginalThreadID(messageID, serverID, threadID) } override fun getQuoteServerID(quoteID: Long, publicKey: String): Long? { return DatabaseFactory.getLokiMessageDatabase(context).getQuoteServerID(quoteID, publicKey) } override fun markAsSent(timestamp: Long, author: String) { val database = DatabaseFactory.getMmsSmsDatabase(context) val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { val mmsDatabase = DatabaseFactory.getMmsDatabase(context) mmsDatabase.markAsSent(messageRecord.getId(), true) } else { val smsDatabase = DatabaseFactory.getSmsDatabase(context) smsDatabase.markAsSent(messageRecord.getId(), true) } } override fun markUnidentified(timestamp: Long, author: String) { val database = DatabaseFactory.getMmsSmsDatabase(context) val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { val mmsDatabase = DatabaseFactory.getMmsDatabase(context) mmsDatabase.markUnidentified(messageRecord.getId(), true) } else { val smsDatabase = DatabaseFactory.getSmsDatabase(context) smsDatabase.markUnidentified(messageRecord.getId(), true) } } override fun setErrorMessage(timestamp: Long, author: String, error: Exception) { val database = DatabaseFactory.getMmsSmsDatabase(context) val messageRecord = database.getMessageFor(timestamp, author) ?: return if (messageRecord.isMms) { val mmsDatabase = DatabaseFactory.getMmsDatabase(context) mmsDatabase.markAsSentFailed(messageRecord.getId()) } else { val smsDatabase = DatabaseFactory.getSmsDatabase(context) smsDatabase.markAsSentFailed(messageRecord.getId()) } if (error.localizedMessage != null) { DatabaseFactory.getLokiMessageDatabase(context).setErrorMessage(messageRecord.getId(), error.localizedMessage!!) } else { DatabaseFactory.getLokiMessageDatabase(context).setErrorMessage(messageRecord.getId(), error.javaClass.simpleName) } } override fun getGroup(groupID: String): GroupRecord? { val group = DatabaseFactory.getGroupDatabase(context).getGroup(groupID) return if (group.isPresent) { group.get() } else null } override fun createGroup(groupId: String, title: String?, members: List
, avatar: SignalServiceAttachmentPointer?, relay: String?, admins: List
, formationTimestamp: Long) { DatabaseFactory.getGroupDatabase(context).create(groupId, title, members, avatar, relay, admins, formationTimestamp) } override fun isGroupActive(groupPublicKey: String): Boolean { return DatabaseFactory.getGroupDatabase(context).getGroup(GroupUtil.doubleEncodeGroupID(groupPublicKey)).orNull()?.isActive == true } override fun setActive(groupID: String, value: Boolean) { DatabaseFactory.getGroupDatabase(context).setActive(groupID, value) } override fun getZombieMember(groupID: String): Set { return DatabaseFactory.getGroupDatabase(context).getGroupZombieMembers(groupID).map { it.address.serialize() }.toHashSet() } override fun removeMember(groupID: String, member: Address) { DatabaseFactory.getGroupDatabase(context).removeMember(groupID, member) } override fun updateMembers(groupID: String, members: List
) { DatabaseFactory.getGroupDatabase(context).updateMembers(groupID, members) } override fun updateZombieMembers(groupID: String, members: List
) { DatabaseFactory.getGroupDatabase(context).updateZombieMembers(groupID, members) } override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, sentTimestamp: Long) { val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList()) val m = IncomingTextMessage(Address.fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true) val updateData = ClosedGroupUpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() val infoMessage = IncomingGroupMessage(m, groupID, updateData, true) val smsDB = DatabaseFactory.getSmsDatabase(context) smsDB.insertMessageInbox(infoMessage) } override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection, admins: Collection, threadID: Long, sentTimestamp: Long) { val userPublicKey = getUserPublicKey() val recipient = Recipient.from(context, Address.fromSerialized(groupID), false) val updateData = ClosedGroupUpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() ?: "" val infoMessage = OutgoingGroupMediaMessage(recipient, updateData, groupID, null, sentTimestamp, 0, true, null, listOf(), listOf()) val mmsDB = DatabaseFactory.getMmsDatabase(context) val mmsSmsDB = DatabaseFactory.getMmsSmsDatabase(context) if (mmsSmsDB.getMessageFor(sentTimestamp, userPublicKey) != null) return val infoMessageID = mmsDB.insertMessageOutbox(infoMessage, threadID, false, null) mmsDB.markAsSent(infoMessageID, true) } override fun isClosedGroup(publicKey: String): Boolean { val isClosedGroup = DatabaseFactory.getLokiAPIDatabase(context).isClosedGroup(publicKey) val address = Address.fromSerialized(publicKey) return address.isClosedGroup || isClosedGroup } override fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList { return DatabaseFactory.getLokiAPIDatabase(context).getClosedGroupEncryptionKeyPairs(groupPublicKey).toMutableList() } override fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair? { return DatabaseFactory.getLokiAPIDatabase(context).getLatestClosedGroupEncryptionKeyPair(groupPublicKey) } override fun getAllClosedGroupPublicKeys(): Set { return DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys() } override fun getAllActiveClosedGroupPublicKeys(): Set { return DatabaseFactory.getLokiAPIDatabase(context).getAllClosedGroupPublicKeys().filter { getGroup(GroupUtil.doubleEncodeGroupID(it))?.isActive == true }.toSet() } override fun addClosedGroupPublicKey(groupPublicKey: String) { DatabaseFactory.getLokiAPIDatabase(context).addClosedGroupPublicKey(groupPublicKey) } override fun removeClosedGroupPublicKey(groupPublicKey: String) { DatabaseFactory.getLokiAPIDatabase(context).removeClosedGroupPublicKey(groupPublicKey) } override fun addClosedGroupEncryptionKeyPair(encryptionKeyPair: ECKeyPair, groupPublicKey: String) { DatabaseFactory.getLokiAPIDatabase(context).addClosedGroupEncryptionKeyPair(encryptionKeyPair, groupPublicKey) } override fun removeAllClosedGroupEncryptionKeyPairs(groupPublicKey: String) { DatabaseFactory.getLokiAPIDatabase(context).removeAllClosedGroupEncryptionKeyPairs(groupPublicKey) } override fun getAllOpenGroups(): Map { return DatabaseFactory.getLokiThreadDatabase(context).getAllPublicChats().mapValues { (_,chat)-> OpenGroup(chat.channel, chat.server, chat.displayName, chat.isDeletable) } } override fun getAllV2OpenGroups(): Map { return DatabaseFactory.getLokiThreadDatabase(context).getAllV2OpenGroups() } override fun addOpenGroup(server: String, channel: Long) { OpenGroupUtilities.addGroup(context, server, channel) } override fun getAllGroups(): List { return DatabaseFactory.getGroupDatabase(context).allGroups } override fun setProfileSharing(address: Address, value: Boolean) { val recipient = Recipient.from(context, address, false) DatabaseFactory.getRecipientDatabase(context).setProfileSharing(recipient, value) } override fun getOrCreateThreadIdFor(address: Address): Long { val recipient = Recipient.from(context, address, false) return DatabaseFactory.getThreadDatabase(context).getOrCreateThreadIdFor(recipient) } override fun getOrCreateThreadIdFor(publicKey: String, groupPublicKey: String?, openGroupID: String?): Long { val database = DatabaseFactory.getThreadDatabase(context) if (!openGroupID.isNullOrEmpty()) { val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray())), false) return database.getOrCreateThreadIdFor(recipient) } else if (!groupPublicKey.isNullOrEmpty()) { val recipient = Recipient.from(context, Address.fromSerialized(GroupUtil.doubleEncodeGroupID(groupPublicKey)), false) return database.getOrCreateThreadIdFor(recipient) } else { val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false) return database.getOrCreateThreadIdFor(recipient) } } override fun getThreadIdFor(address: Address): Long? { val recipient = Recipient.from(context, address, false) val threadID = DatabaseFactory.getThreadDatabase(context).getThreadIdIfExistsFor(recipient) return if (threadID < 0) null else threadID } override fun getThreadIdForMms(mmsId: Long): Long { val mmsDb = DatabaseFactory.getMmsDatabase(context) val cursor = mmsDb.getMessage(mmsId) val reader = mmsDb.readerFor(cursor) val threadId = reader.next.threadId cursor.close() return threadId } override fun getSessionRequestSentTimestamp(publicKey: String): Long? { return DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestSentTimestamp(publicKey) } override fun setSessionRequestSentTimestamp(publicKey: String, newValue: Long) { DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestSentTimestamp(publicKey, newValue) } override fun getSessionRequestProcessedTimestamp(publicKey: String): Long? { return DatabaseFactory.getLokiAPIDatabase(context).getSessionRequestProcessedTimestamp(publicKey) } override fun setSessionRequestProcessedTimestamp(publicKey: String, newValue: Long) { DatabaseFactory.getLokiAPIDatabase(context).setSessionRequestProcessedTimestamp(publicKey, newValue) } override fun getDisplayName(publicKey: String): String? { return DatabaseFactory.getLokiUserDatabase(context).getDisplayName(publicKey) } override fun setDisplayName(publicKey: String, newName: String) { DatabaseFactory.getLokiUserDatabase(context).setDisplayName(publicKey, newName) } override fun getServerDisplayName(serverID: String, publicKey: String): String? { return DatabaseFactory.getLokiUserDatabase(context).getServerDisplayName(serverID, publicKey) } override fun getProfilePictureURL(publicKey: String): String? { return DatabaseFactory.getLokiUserDatabase(context).getProfilePictureURL(publicKey) } override fun getRecipientSettings(address: Address): Recipient.RecipientSettings? { val recipientSettings = DatabaseFactory.getRecipientDatabase(context).getRecipientSettings(address) return if (recipientSettings.isPresent) { recipientSettings.get() } else null } override fun addContacts(contacts: List) { val recipientDatabase = DatabaseFactory.getRecipientDatabase(context) val threadDatabase = DatabaseFactory.getThreadDatabase(context) for (contact in 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 (contacts.isNotEmpty()) { threadDatabase.notifyUpdatedFromConfig() } } override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri { return PartAuthority.getAttachmentDataUri(attachmentId) } override fun getAttachmentThumbnailUri(attachmentId: AttachmentId): Uri { return PartAuthority.getAttachmentThumbnailUri(attachmentId) } // Data Extraction Notification override fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) { val database = DatabaseFactory.getMmsDatabase(context) val address = fromSerialized(senderPublicKey) val recipient = Recipient.from(context, address, false) if (recipient.isBlocked) return val mediaMessage = IncomingMediaMessage(address, sentTimestamp, -1, 0, false, false, Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent(), Optional.absent(), Optional.of(message)) database.insertSecureDecryptedMessageInbox(mediaMessage, -1) } }