Add migration for call link recipient link.

This commit is contained in:
Alex Hart
2023-05-17 09:46:36 -03:00
committed by Greyson Parrelli
parent d7dd77a5af
commit 0c57113d8e
11 changed files with 185 additions and 116 deletions

View File

@@ -1,13 +1,16 @@
package org.thoughtcrime.securesms.database
import android.content.Context
import androidx.core.content.contentValuesOf
import org.signal.core.util.logging.Log
import org.signal.core.util.update
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.recipients.RecipientId
/**
* Table containing ad-hoc call link details
*/
class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper) {
class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTable(context, databaseHelper), RecipientIdDatabaseReference {
companion object {
private val TAG = Log.tag(CallLinkTable::class.java)
@@ -22,6 +25,7 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
const val REVOKED = "revoked"
const val EXPIRATION = "expiration"
const val AVATAR_COLOR = "avatar_color"
const val RECIPIENT_ID = "recipient_id"
//language=sql
const val CREATE_TABLE = """
@@ -34,7 +38,8 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
$RESTRICTIONS INTEGER NOT NULL,
$REVOKED INTEGER NOT NULL,
$EXPIRATION INTEGER NOT NULL,
$AVATAR_COLOR TEXT NOT NULL
$AVATAR_COLOR TEXT NOT NULL,
$RECIPIENT_ID INTEGER UNIQUE REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE
)
"""
}
@@ -45,4 +50,15 @@ class CallLinkTable(context: Context, databaseHelper: SignalDatabase) : Database
val avatarColor: AvatarColor,
val isApprovalRequired: Boolean
)
override fun remapRecipient(fromId: RecipientId, toId: RecipientId) {
writableDatabase.update(TABLE_NAME)
.values(
contentValuesOf(
RECIPIENT_ID to toId.toLong()
)
)
.where("$RECIPIENT_ID = ?", fromId.toLong())
.run()
}
}

View File

@@ -52,7 +52,6 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
private const val CALL_ID = "call_id"
private const val MESSAGE_ID = "message_id"
private const val PEER = "peer"
private const val CALL_LINK = "call_link"
private const val TYPE = "type"
private const val DIRECTION = "direction"
private const val EVENT = "event"
@@ -67,22 +66,19 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
$CALL_ID INTEGER NOT NULL,
$MESSAGE_ID INTEGER DEFAULT NULL REFERENCES ${MessageTable.TABLE_NAME} (${MessageTable.ID}) ON DELETE SET NULL,
$PEER INTEGER DEFAULT NULL REFERENCES ${RecipientTable.TABLE_NAME} (${RecipientTable.ID}) ON DELETE CASCADE,
$CALL_LINK TEXT DEFAULT NULL REFERENCES ${CallLinkTable.TABLE_NAME} (${CallLinkTable.ROOM_ID}) ON DELETE CASCADE,
$TYPE INTEGER NOT NULL,
$DIRECTION INTEGER NOT NULL,
$EVENT INTEGER NOT NULL,
$TIMESTAMP INTEGER NOT NULL,
$RINGER INTEGER DEFAULT NULL,
$DELETION_TIMESTAMP INTEGER DEFAULT 0,
UNIQUE ($CALL_ID, $PEER, $CALL_LINK) ON CONFLICT FAIL,
CHECK (($PEER IS NULL AND $CALL_LINK IS NOT NULL) OR ($PEER IS NOT NULL AND $CALL_LINK IS NULL))
UNIQUE ($CALL_ID, $PEER) ON CONFLICT FAIL
)
"""
val CREATE_INDEXES = arrayOf(
"CREATE INDEX call_call_id_index ON $TABLE_NAME ($CALL_ID)",
"CREATE INDEX call_message_id_index ON $TABLE_NAME ($MESSAGE_ID)",
"CREATE INDEX call_call_link_index ON $TABLE_NAME ($CALL_LINK)",
"CREATE INDEX call_peer_index ON $TABLE_NAME ($PEER)"
)
}
@@ -139,8 +135,8 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
}
}
fun getCallById(callId: Long, conversationId: CallConversationId): Call? {
val query = getCallSelectionQuery(callId, conversationId)
fun getCallById(callId: Long, recipientId: RecipientId): Call? {
val query = getCallSelectionQuery(callId, recipientId)
return readableDatabase
.select()
@@ -393,7 +389,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
}
val callId = CallId.fromEra(peekGroupCallEraId).longValue()
val call = getCallById(callId, CallConversationId.Peer(groupRecipientId))
val call = getCallById(callId, groupRecipientId)
val messageId: MessageId = if (call != null) {
if (call.event == Event.DELETE) {
Log.d(TAG, "Dropping group call update for deleted call.")
@@ -444,9 +440,8 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
groupRecipientId: RecipientId,
timestamp: Long
) {
val conversationId = CallConversationId.Peer(groupRecipientId)
if (messageId != null) {
val call = getCallById(callId, conversationId)
val call = getCallById(callId, groupRecipientId)
if (call == null) {
val direction = if (sender == Recipient.self().id) Direction.OUTGOING else Direction.INCOMING
@@ -467,7 +462,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
Log.d(TAG, "Inserted new call event from group call update message. Call Id: $callId")
} else {
if (timestamp < call.timestamp) {
setTimestamp(callId, conversationId, timestamp)
setTimestamp(callId, groupRecipientId, timestamp)
Log.d(TAG, "Updated call event timestamp for call id $callId")
}
@@ -519,7 +514,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
}
fun isRingCancelled(ringId: Long, groupRecipientId: RecipientId): Boolean {
val call = getCallById(ringId, CallConversationId.Peer(groupRecipientId)) ?: return false
val call = getCallById(ringId, groupRecipientId) ?: return false
return call.event != Event.RINGING && call.event != Event.GENERIC_GROUP_CALL
}
@@ -532,7 +527,7 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
) {
Log.d(TAG, "Processing group ring state update for $ringId in state $ringState")
val call = getCallById(ringId, CallConversationId.Peer(groupRecipientId))
val call = getCallById(ringId, groupRecipientId)
if (call != null) {
if (call.event == Event.DELETE) {
Log.d(TAG, "Ignoring ring request for $ringId since its event has been deleted.")
@@ -676,9 +671,9 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
Log.d(TAG, "Inserted a new group ring event for $callId with event $event")
}
fun setTimestamp(callId: Long, conversationId: CallConversationId, timestamp: Long) {
fun setTimestamp(callId: Long, recipientId: RecipientId, timestamp: Long) {
writableDatabase.withinTransaction { db ->
val call = getCallById(callId, conversationId)
val call = getCallById(callId, recipientId)
if (call == null || call.event == Event.DELETE) {
Log.d(TAG, "Refusing to update deleted call event.")
return@withinTransaction
@@ -788,11 +783,8 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
.run()
}
private fun getCallSelectionQuery(callId: Long, conversationId: CallConversationId): SqlUtil.Query {
return when (conversationId) {
is CallConversationId.CallLink -> SqlUtil.Query("$CALL_ID = ? AND $CALL_LINK = ?", SqlUtil.buildArgs(callId, conversationId.callLinkId))
is CallConversationId.Peer -> SqlUtil.Query("$CALL_ID = ? AND $PEER = ?", SqlUtil.buildArgs(callId, conversationId.recipientId))
}
private fun getCallSelectionQuery(callId: Long, recipientId: RecipientId): SqlUtil.Query {
return SqlUtil.Query("$CALL_ID = ? AND $PEER = ?", SqlUtil.buildArgs(callId, recipientId))
}
private fun getMessageIds(callRowIds: Set<Long>): Set<Long> {
@@ -1099,11 +1091,6 @@ class CallTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTabl
}
}
sealed interface CallConversationId {
data class Peer(val recipientId: RecipientId) : CallConversationId
data class CallLink(val callLinkId: Int) : CallConversationId
}
enum class Event(private val code: Int) {
/**
* 1:1 Calls only.

View File

@@ -136,6 +136,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
const val EMAIL = "email"
const val GROUP_ID = "group_id"
const val DISTRIBUTION_LIST_ID = "distribution_list_id"
private const val CALL_LINK_ROOM_ID = "call_link_room_id"
const val GROUP_TYPE = "group_type"
const val BLOCKED = "blocked"
private const val MESSAGE_RINGTONE = "message_ringtone"
@@ -252,7 +253,8 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
$UNREGISTERED_TIMESTAMP INTEGER DEFAULT 0,
$HIDDEN INTEGER DEFAULT 0,
$REPORTING_TOKEN BLOB DEFAULT NULL,
$SYSTEM_NICKNAME TEXT DEFAULT NULL
$SYSTEM_NICKNAME TEXT DEFAULT NULL,
$CALL_LINK_ROOM_ID TEXT DEFAULT NULL
)
"""
@@ -4574,7 +4576,7 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
enum class GroupType(val id: Int) {
NONE(0), MMS(1), SIGNAL_V1(2), SIGNAL_V2(3), DISTRIBUTION_LIST(4);
NONE(0), MMS(1), SIGNAL_V1(2), SIGNAL_V2(3), DISTRIBUTION_LIST(4), CALL_LINK(5);
companion object {
fun fromId(id: Int): GroupType {

View File

@@ -48,6 +48,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V189_CreateCallLink
import org.thoughtcrime.securesms.database.helpers.migration.V190_UniqueMessageMigration
import org.thoughtcrime.securesms.database.helpers.migration.V191_UniqueMessageMigrationV2
import org.thoughtcrime.securesms.database.helpers.migration.V192_CallLinkTableNullableRootKeys
import org.thoughtcrime.securesms.database.helpers.migration.V193_BackCallLinksWithRecipient
/**
* Contains all of the database migrations for [SignalDatabase]. Broken into a separate file for cleanliness.
@@ -56,7 +57,7 @@ object SignalDatabaseMigrations {
val TAG: String = Log.tag(SignalDatabaseMigrations.javaClass)
const val DATABASE_VERSION = 192
const val DATABASE_VERSION = 193
@JvmStatic
fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
@@ -235,6 +236,10 @@ object SignalDatabaseMigrations {
if (oldVersion < 192) {
V192_CallLinkTableNullableRootKeys.migrate(context, db, oldVersion, newVersion)
}
if (oldVersion < 193) {
V193_BackCallLinksWithRecipient.migrate(context, db, oldVersion, newVersion)
}
}
@JvmStatic

View File

@@ -0,0 +1,94 @@
/*
* Copyright 2023 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import net.zetetic.database.sqlcipher.SQLiteDatabase
import org.signal.core.util.SqlUtil
import org.signal.core.util.logging.Log
/**
* Back CallLinks with a Recipient to ease integration and ensure we can support
* different features which would require that relation in the future.
*/
object V193_BackCallLinksWithRecipient : SignalDatabaseMigration {
private val TAG = Log.tag(V193_BackCallLinksWithRecipient::class.java)
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
// add new column to recipient table to store the room id
db.execSQL("ALTER TABLE recipient ADD COLUMN call_link_room_id TEXT DEFAULT NULL")
// recreate the call link table with the new recipient id reference.
db.execSQL("DROP TABLE call_link")
db.execSQL(
"""
CREATE TABLE call_link (
_id INTEGER PRIMARY KEY,
root_key BLOB,
room_id TEXT NOT NULL UNIQUE,
admin_key BLOB,
name TEXT NOT NULL,
restrictions INTEGER NOT NULL,
revoked INTEGER NOT NULL,
expiration INTEGER NOT NULL,
avatar_color TEXT NOT NULL,
recipient_id INTEGER UNIQUE REFERENCES recipient (_id) ON DELETE CASCADE
)
""".trimIndent()
)
// recreate the call table dropping the call_link column
db.execSQL(
"""
CREATE TABLE call_tmp (
_id INTEGER PRIMARY KEY,
call_id INTEGER NOT NULL,
message_id INTEGER DEFAULT NULL REFERENCES message (_id) ON DELETE SET NULL,
peer INTEGER DEFAULT NULL REFERENCES recipient (_id) ON DELETE CASCADE,
type INTEGER NOT NULL,
direction INTEGER NOT NULL,
event INTEGER NOT NULL,
timestamp INTEGER NOT NULL,
ringer INTEGER DEFAULT NULL,
deletion_timestamp INTEGER DEFAULT 0,
UNIQUE (call_id, peer) ON CONFLICT FAIL
)
""".trimIndent()
)
db.execSQL(
"""
INSERT INTO call_tmp
SELECT
_id,
call_id,
message_id,
peer,
type,
direction,
event,
timestamp,
ringer,
deletion_timestamp
FROM call
""".trimIndent()
)
db.execSQL("DROP TABLE call")
db.execSQL("ALTER TABLE call_tmp RENAME TO call")
db.execSQL("CREATE INDEX IF NOT EXISTS call_call_id_index ON call (call_id)")
db.execSQL("CREATE INDEX IF NOT EXISTS call_message_id_index ON call (message_id)")
db.execSQL("CREATE INDEX IF NOT EXISTS call_peer_index ON call (peer)")
val foreignKeyViolations: List<SqlUtil.ForeignKeyViolation> = SqlUtil.getForeignKeyViolations(db, "call")
if (foreignKeyViolations.isNotEmpty()) {
Log.w(TAG, "Foreign key violations!\n${foreignKeyViolations.joinToString(separator = "\n")}")
throw IllegalStateException("Foreign key violations!")
}
}
}

View File

@@ -101,7 +101,7 @@ class CallSyncEventJob private constructor(
}
private fun processEvent(callSyncEvent: CallSyncEventJobRecord): CallSyncEventJobRecord? {
val call = SignalDatabase.calls.getCallById(callSyncEvent.callId, CallTable.CallConversationId.Peer(callSyncEvent.deserializeRecipientId()))
val call = SignalDatabase.calls.getCallById(callSyncEvent.callId, callSyncEvent.deserializeRecipientId())
if (call == null) {
Log.w(TAG, "Cannot process event for call that does not exist. Dropping.")
return null

View File

@@ -1294,7 +1294,7 @@ public class MessageContentProcessor {
log(envelopeTimestamp, "Synchronize call event call: " + callId);
CallTable.Call call = SignalDatabase.calls().getCallById(callId, new CallTable.CallConversationId.Peer(recipientId));
CallTable.Call call = SignalDatabase.calls().getCallById(callId, recipientId);
if (call != null) {
boolean typeMismatch = call.getType() != type;
boolean directionMismatch = call.getDirection() != direction;
@@ -1337,9 +1337,8 @@ public class MessageContentProcessor {
GroupId groupId = GroupId.push(callEvent.getConversationId().toByteArray());
RecipientId recipientId = Recipient.externalGroupExact(groupId).getId();
CallTable.CallConversationId callConversationId = new CallTable.CallConversationId.Peer(recipientId);
CallTable.Call call = SignalDatabase.calls().getCallById(callId, callConversationId);
CallTable.Call call = SignalDatabase.calls().getCallById(callId, recipientId);
if (call != null) {
if (call.getType() != type) {
warn(envelopeTimestamp, "Group/Ad-hoc call event type mismatch, ignoring. timestamp: " + timestamp + " type: " + type + " direction: " + direction + " event: " + event + " hasPeer: " + callEvent.hasConversationId());
@@ -1352,7 +1351,7 @@ public class MessageContentProcessor {
break;
case ACCEPTED:
if (call.getTimestamp() < callEvent.getTimestamp()) {
SignalDatabase.calls().setTimestamp(call.getCallId(), callConversationId, callEvent.getTimestamp());
SignalDatabase.calls().setTimestamp(call.getCallId(), recipientId, callEvent.getTimestamp());
}
if (callEvent.getDirection() == SyncMessage.CallEvent.Direction.INCOMING) {

View File

@@ -1172,7 +1172,7 @@ object SyncMessageProcessor {
log(envelopeTimestamp, "Synchronize call event call: $callId")
val call = SignalDatabase.calls.getCallById(callId, CallTable.CallConversationId.Peer(recipientId))
val call = SignalDatabase.calls.getCallById(callId, recipientId)
if (call != null) {
val typeMismatch = call.type !== type
val directionMismatch = call.direction !== direction
@@ -1209,9 +1209,8 @@ object SyncMessageProcessor {
val groupId: GroupId = GroupId.push(callEvent.conversationId.toByteArray())
val recipientId = Recipient.externalGroupExact(groupId).id
val conversationId = CallTable.CallConversationId.Peer(recipientId)
val call = SignalDatabase.calls.getCallById(callId, CallTable.CallConversationId.Peer(recipientId))
val call = SignalDatabase.calls.getCallById(callId, recipientId)
if (call != null) {
if (call.type !== type) {
@@ -1222,7 +1221,7 @@ object SyncMessageProcessor {
CallTable.Event.DELETE -> SignalDatabase.calls.deleteGroupCall(call)
CallTable.Event.ACCEPTED -> {
if (call.timestamp < callEvent.timestamp) {
SignalDatabase.calls.setTimestamp(call.callId, conversationId, callEvent.timestamp)
SignalDatabase.calls.setTimestamp(call.callId, recipientId, callEvent.timestamp)
}
if (callEvent.direction == SyncMessage.CallEvent.Direction.INCOMING) {
SignalDatabase.calls.acceptIncomingGroupCall(call)