From a16e67d1fd47bb582a7f947b76e602dd611d7d11 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Fri, 7 May 2021 16:31:46 +1000 Subject: [PATCH] add new contact database api --- .../securesms/database/DatabaseFactory.java | 7 ++ .../securesms/database/Storage.kt | 13 ++++ .../database/helpers/SQLCipherOpenHelper.java | 9 ++- .../loki/database/SessionContactDatabase.kt | 76 +++++++++++++++++++ .../sskenvironment/ProfileManager.kt | 33 ++++++-- .../libsession/messaging/StorageProtocol.kt | 6 +- .../libsession/messaging/contacts/Contact.kt | 2 +- 7 files changed, 138 insertions(+), 8 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java index ea534c35f8..554a6470b0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/DatabaseFactory.java @@ -34,6 +34,7 @@ import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; import org.thoughtcrime.securesms.loki.database.SessionJobDatabase; +import org.thoughtcrime.securesms.loki.database.SessionContactDatabase; public class DatabaseFactory { @@ -63,6 +64,7 @@ public class DatabaseFactory { private final LokiUserDatabase lokiUserDatabase; private final LokiBackupFilesDatabase lokiBackupFilesDatabase; private final SessionJobDatabase sessionJobDatabase; + private final SessionContactDatabase sessionContactDatabase; // Refactor private final Storage storage; @@ -157,6 +159,10 @@ public class DatabaseFactory { public static SessionJobDatabase getSessionJobDatabase(Context context) { return getInstance(context).sessionJobDatabase; } + + public static SessionContactDatabase getSessionContactDatabase(Context context) { + return getInstance(context).sessionContactDatabase; + } // endregion // region Refactor @@ -202,6 +208,7 @@ public class DatabaseFactory { this.storage = new Storage(context, databaseHelper); this.attachmentProvider = new DatabaseAttachmentProvider(context, databaseHelper); this.sessionJobDatabase = new SessionJobDatabase(context, databaseHelper); + this.sessionContactDatabase = new SessionContactDatabase(context, databaseHelper); } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 1b2722ad5f..5aa12566cd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -4,6 +4,7 @@ import android.content.Context import android.net.Uri import okhttp3.HttpUrl import org.session.libsession.messaging.StorageProtocol +import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.JobQueue @@ -624,6 +625,18 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseFactory.getLokiUserDatabase(context).getProfilePictureURL(publicKey) } + override fun getContactWithSessionID(sessionID: String): Contact? { + return DatabaseFactory.getSessionContactDatabase(context).getContactWithSessionID(sessionID) + } + + override fun getAllContacts(): Set { + return DatabaseFactory.getSessionContactDatabase(context).getAllContacts() + } + + override fun setContact(contact: Contact) { + DatabaseFactory.getSessionContactDatabase(context).setContact(contact) + } + override fun getRecipientSettings(address: Address): Recipient.RecipientSettings? { val recipientSettings = DatabaseFactory.getRecipientDatabase(context).getRecipientSettings(address) return if (recipientSettings.isPresent) { recipientSettings.get() } else null diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index ef04bedbb7..8674011d6a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -28,6 +28,7 @@ import org.thoughtcrime.securesms.loki.database.LokiBackupFilesDatabase; import org.thoughtcrime.securesms.loki.database.LokiMessageDatabase; import org.thoughtcrime.securesms.loki.database.LokiThreadDatabase; import org.thoughtcrime.securesms.loki.database.LokiUserDatabase; +import org.thoughtcrime.securesms.loki.database.SessionContactDatabase; import org.thoughtcrime.securesms.loki.database.SessionJobDatabase; import org.thoughtcrime.securesms.loki.protocol.ClosedGroupsMigration; @@ -56,9 +57,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV22 = 43; private static final int lokiV23 = 44; private static final int lokiV24 = 45; + private static final int lokiV25 = 46; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV24; + private static final int DATABASE_VERSION = lokiV25; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -128,6 +130,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(SessionJobDatabase.getCreateSessionJobTableCommand()); db.execSQL(LokiMessageDatabase.getUpdateMessageIDTableForType()); db.execSQL(LokiMessageDatabase.getUpdateMessageMappingTable()); + db.execSQL(SessionContactDatabase.getCreateSessionContactTableCommand()); executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS); @@ -291,6 +294,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(LokiAPIDatabase.getCreateSwarmTableCommand()); } + if (oldVersion < lokiV25) { + db.execSQL(SessionContactDatabase.getCreateSessionContactTableCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt new file mode 100644 index 0000000000..5a2a4072de --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/SessionContactDatabase.kt @@ -0,0 +1,76 @@ +package org.thoughtcrime.securesms.loki.database + +import android.content.ContentValues +import android.content.Context +import net.sqlcipher.Cursor +import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.messaging.jobs.Job +import org.session.libsignal.utilities.Base64 +import org.thoughtcrime.securesms.database.Database +import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper +import org.thoughtcrime.securesms.loki.utilities.* + +class SessionContactDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) { + companion object { + private const val sessionContactTable = "session_contact_database" + const val sessionID = "session_id" + const val name = "name" + const val nickname = "nickname" + const val profilePictureURL = "profile_picture_url" + const val profilePictureFileName = "profile_picture_file_name" + const val profilePictureEncryptionKey = "profile_picture_encryption_key" + const val threadID = "thread_id" + const val isTrusted = "is_trusted" + @JvmStatic val createSessionContactTableCommand = + "CREATE TABLE $sessionContactTable " + + "($sessionID STRING PRIMARY KEY, " + + "$name TEXT DEFAULT NULL, " + + "$nickname TEXT DEFAULT NULL, " + + "$profilePictureURL TEXT DEFAULT NULL, " + + "$profilePictureFileName TEXT DEFAULT NULL, " + + "$profilePictureEncryptionKey BLOB DEFAULT NULL, " + + "$threadID INTEGER DEFAULT -1, " + + "$isTrusted INTEGER DEFAULT 0);" + } + + fun getContactWithSessionID(sessionID: String): Contact? { + val database = databaseHelper.readableDatabase + return database.get(sessionContactTable, "$sessionID = ?", arrayOf(sessionID)) { cursor -> + contactFromCursor(cursor) + } + } + + fun getAllContacts(): Set { + val database = databaseHelper.readableDatabase + return database.getAll(sessionContactTable, null, null) { cursor -> + contactFromCursor(cursor) + }.toSet() + } + + fun setContact(contact: Contact) { + val database = databaseHelper.writableDatabase + val contentValues = ContentValues(8) + contentValues.put(sessionID, contact.sessionID) + contentValues.put(name, contact.name) + contentValues.put(nickname, contact.nickname) + contentValues.put(profilePictureURL, contact.profilePictureURL) + contentValues.put(profilePictureFileName, contact.profilePictureFileName) + contentValues.put(profilePictureEncryptionKey, Base64.encodeBytes(contact.profilePictureEncryptionKey)) + contentValues.put(threadID, threadID) + contentValues.put(isTrusted, if (contact.isTrusted) 1 else 0) + database.insertOrUpdate(sessionContactTable, contentValues, "$sessionID = ?", arrayOf(contact.sessionID)) + } + + private fun contactFromCursor(cursor: Cursor): Contact { + val sessionID = cursor.getString(sessionID) + val contact = Contact(sessionID) + contact.name = cursor.getString(name) + contact.nickname = cursor.getString(nickname) + contact.profilePictureURL = cursor.getString(profilePictureURL) + contact.profilePictureFileName = cursor.getString(profilePictureFileName) + contact.profilePictureEncryptionKey = Base64.decode(cursor.getString(profilePictureEncryptionKey)) + contact.threadID = cursor.getInt(threadID) + contact.isTrusted = cursor.getInt(isTrusted) != 0 + return contact + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt index ac8b13e865..ee7be93dd1 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/sskenvironment/ProfileManager.kt @@ -1,6 +1,7 @@ package org.thoughtcrime.securesms.sskenvironment import android.content.Context +import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.threads.recipients.Recipient import org.session.libsession.utilities.SSKEnvironment import org.thoughtcrime.securesms.ApplicationContext @@ -10,18 +11,31 @@ import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob class ProfileManager: SSKEnvironment.ProfileManagerProtocol { override fun setDisplayName(context: Context, recipient: Recipient, displayName: String) { val database = DatabaseFactory.getLokiUserDatabase(context) - val publicKey = recipient.address.serialize() - if (recipient.name == null) { - // Migrate the profile name in LokiUserDatabase to recipient - database.getDisplayName(publicKey)?.let { setProfileName(context, recipient, it) } + val sessionID = recipient.address.serialize() + database.setDisplayName(sessionID, displayName) + // New API + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) contact = Contact(sessionID) + if (contact.nickname != displayName) { + contact.nickname = displayName + contactDatabase.setContact(contact) } - database.setDisplayName(publicKey, displayName) } override fun setProfileName(context: Context, recipient: Recipient, profileName: String) { val database = DatabaseFactory.getRecipientDatabase(context) database.setProfileName(recipient, profileName) recipient.notifyListeners() + // New API + val sessionID = recipient.address.serialize() + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) contact = Contact(sessionID) + if (contact.name != profileName) { + contact.name = profileName + contactDatabase.setContact(contact) + } } override fun setProfilePictureURL(context: Context, recipient: Recipient, profilePictureURL: String) { @@ -31,6 +45,15 @@ class ProfileManager: SSKEnvironment.ProfileManagerProtocol { override fun setProfileKey(context: Context, recipient: Recipient, profileKey: ByteArray) { val database = DatabaseFactory.getRecipientDatabase(context) database.setProfileKey(recipient, profileKey) + // New API + val sessionID = recipient.address.serialize() + val contactDatabase = DatabaseFactory.getSessionContactDatabase(context) + var contact = contactDatabase.getContactWithSessionID(sessionID) + if (contact == null) contact = Contact(sessionID) + if (!contact.profilePictureEncryptionKey.contentEquals(profileKey)) { + contact.profilePictureEncryptionKey = profileKey + contactDatabase.setContact(contact) + } } override fun setUnidentifiedAccessMode(context: Context, recipient: Recipient, unidentifiedAccessMode: Recipient.UnidentifiedAccessMode) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt index e7442389c9..fb57f6d875 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/StorageProtocol.kt @@ -3,6 +3,7 @@ package org.session.libsession.messaging import android.content.Context import android.net.Uri +import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.MessageSendJob @@ -146,9 +147,12 @@ interface StorageProtocol { fun getSessionRequestProcessedTimestamp(publicKey: String): Long? fun setSessionRequestProcessedTimestamp(publicKey: String, newValue: Long) - // Loki User + // Session Contact (Loki User) fun getDisplayName(publicKey: String): String? fun getProfilePictureURL(publicKey: String): String? + fun getContactWithSessionID(sessionID: String): Contact? + fun getAllContacts(): Set + fun setContact(contact: Contact) // Recipient fun getRecipientSettings(address: Address): RecipientSettings? diff --git a/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt index a687b3df54..392c57e3d5 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/contacts/Contact.kt @@ -10,7 +10,7 @@ class Contact(val sessionID: String) { // The key with which the profile picture is encrypted. var profilePictureEncryptionKey: ByteArray? = null // The ID of the thread associated with this contact. - var threadID: String? = null + var threadID: Int? = null // This flag is used to determine whether we should auto-download files sent by this contact. var isTrusted = false