Add notification profile id for backupsv2.

This commit is contained in:
Cody Henthorne
2025-04-21 16:43:19 -04:00
parent b4a9189068
commit 4304ae2a96
21 changed files with 114 additions and 10 deletions

View File

@@ -187,6 +187,10 @@ object ImportSkips {
return log(0, "Failed to parse chatFolderId for the provided chat folder.")
}
fun notificationProfileIdNotFound(): String {
return log(0, "Failed to parse notificationProfileId for the provided notification profile.")
}
private fun log(sentTimestamp: Long, message: String): String {
return "[SKIP][$sentTimestamp] $message"
}

View File

@@ -5,10 +5,12 @@
package org.thoughtcrime.securesms.backup.v2.processor
import okio.ByteString.Companion.toByteString
import org.signal.core.util.insertInto
import org.signal.core.util.logging.Log
import org.signal.core.util.toInt
import org.thoughtcrime.securesms.backup.v2.ExportState
import org.thoughtcrime.securesms.backup.v2.ImportSkips
import org.thoughtcrime.securesms.backup.v2.ImportState
import org.thoughtcrime.securesms.backup.v2.proto.Frame
import org.thoughtcrime.securesms.backup.v2.stream.BackupFrameEmitter
@@ -20,7 +22,7 @@ import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.serialize
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.recipients.RecipientId
import java.lang.IllegalStateException
import org.whispersystems.signalservice.api.util.UuidUtil
import java.time.DayOfWeek
import org.thoughtcrime.securesms.backup.v2.proto.NotificationProfile as NotificationProfileProto
@@ -41,6 +43,12 @@ object NotificationProfileProcessor {
}
fun import(profile: NotificationProfileProto, importState: ImportState) {
val notificationProfileUuid = UuidUtil.parseOrNull(profile.id)
if (notificationProfileUuid == null) {
ImportSkips.notificationProfileIdNotFound()
return
}
val profileId = SignalDatabase
.writableDatabase
.insertInto(NotificationProfileTable.TABLE_NAME)
@@ -50,7 +58,8 @@ object NotificationProfileProcessor {
NotificationProfileTable.COLOR to (AvatarColor.fromColor(profile.color) ?: AvatarColor.random()).serialize(),
NotificationProfileTable.CREATED_AT to profile.createdAtMs,
NotificationProfileTable.ALLOW_ALL_CALLS to profile.allowAllCalls.toInt(),
NotificationProfileTable.ALLOW_ALL_MENTIONS to profile.allowAllMentions.toInt()
NotificationProfileTable.ALLOW_ALL_MENTIONS to profile.allowAllMentions.toInt(),
NotificationProfileTable.NOTIFICATION_PROFILE_ID to notificationProfileUuid.toString()
)
.run()
@@ -89,6 +98,7 @@ object NotificationProfileProcessor {
private fun NotificationProfile.toBackupFrame(includeRecipient: (RecipientId) -> Boolean): Frame {
val profile = NotificationProfileProto(
id = UuidUtil.toByteArray(this.notificationProfileId.uuid).toByteString(),
name = this.name,
emoji = this.emoji.takeIf { it.isNotBlank() },
color = this.color.colorInt(),

View File

@@ -11,12 +11,14 @@ import org.signal.core.util.logging.Log
import org.signal.core.util.requireBoolean
import org.signal.core.util.requireInt
import org.signal.core.util.requireLong
import org.signal.core.util.requireNonNullString
import org.signal.core.util.requireString
import org.signal.core.util.toInt
import org.signal.core.util.update
import org.thoughtcrime.securesms.conversation.colors.AvatarColor
import org.thoughtcrime.securesms.dependencies.AppDependencies
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfile
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfileId
import org.thoughtcrime.securesms.notifications.profiles.NotificationProfileSchedule
import org.thoughtcrime.securesms.recipients.RecipientId
import java.time.DayOfWeek
@@ -46,6 +48,7 @@ class NotificationProfileTables(context: Context, databaseHelper: SignalDatabase
const val CREATED_AT = "created_at"
const val ALLOW_ALL_CALLS = "allow_all_calls"
const val ALLOW_ALL_MENTIONS = "allow_all_mentions"
const val NOTIFICATION_PROFILE_ID = "notification_profile_id"
val CREATE_TABLE = """
CREATE TABLE $TABLE_NAME (
@@ -55,7 +58,8 @@ class NotificationProfileTables(context: Context, databaseHelper: SignalDatabase
$COLOR TEXT NOT NULL,
$CREATED_AT INTEGER NOT NULL,
$ALLOW_ALL_CALLS INTEGER NOT NULL DEFAULT 0,
$ALLOW_ALL_MENTIONS INTEGER NOT NULL DEFAULT 0
$ALLOW_ALL_MENTIONS INTEGER NOT NULL DEFAULT 0,
$NOTIFICATION_PROFILE_ID TEXT DEFAULT NULL
)
"""
}
@@ -110,12 +114,14 @@ class NotificationProfileTables(context: Context, databaseHelper: SignalDatabase
db.beginTransaction()
try {
val notificationProfileId = NotificationProfileId.generate()
val profileValues = ContentValues().apply {
put(NotificationProfileTable.NAME, name)
put(NotificationProfileTable.EMOJI, emoji)
put(NotificationProfileTable.COLOR, color.serialize())
put(NotificationProfileTable.CREATED_AT, createdAt)
put(NotificationProfileTable.ALLOW_ALL_CALLS, 1)
put(NotificationProfileTable.NOTIFICATION_PROFILE_ID, notificationProfileId.serialize())
}
val profileId = db.insert(NotificationProfileTable.TABLE_NAME, null, profileValues)
@@ -140,7 +146,8 @@ class NotificationProfileTables(context: Context, databaseHelper: SignalDatabase
emoji = emoji,
createdAt = createdAt,
schedule = getProfileSchedule(profileId),
allowAllCalls = true
allowAllCalls = true,
notificationProfileId = notificationProfileId
)
)
} finally {
@@ -323,7 +330,8 @@ class NotificationProfileTables(context: Context, databaseHelper: SignalDatabase
allowAllCalls = cursor.requireBoolean(NotificationProfileTable.ALLOW_ALL_CALLS),
allowAllMentions = cursor.requireBoolean(NotificationProfileTable.ALLOW_ALL_MENTIONS),
schedule = getProfileSchedule(profileId),
allowedMembers = getProfileAllowedMembers(profileId)
allowedMembers = getProfileAllowedMembers(profileId),
notificationProfileId = NotificationProfileId.from(cursor.requireNonNullString(NotificationProfileTable.NOTIFICATION_PROFILE_ID))
)
}

View File

@@ -125,6 +125,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V267_FixGroupInvita
import org.thoughtcrime.securesms.database.helpers.migration.V268_FixInAppPaymentsErrorStateConsistency
import org.thoughtcrime.securesms.database.helpers.migration.V269_BackupMediaSnapshotChanges
import org.thoughtcrime.securesms.database.helpers.migration.V270_FixChatFolderColumnsForStorageSync
import org.thoughtcrime.securesms.database.helpers.migration.V271_AddNotificationProfileIdColumn
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase
/**
@@ -255,10 +256,11 @@ object SignalDatabaseMigrations {
267 to V267_FixGroupInvitationDeclinedUpdate,
268 to V268_FixInAppPaymentsErrorStateConsistency,
269 to V269_BackupMediaSnapshotChanges,
270 to V270_FixChatFolderColumnsForStorageSync
270 to V270_FixChatFolderColumnsForStorageSync,
271 to V271_AddNotificationProfileIdColumn
)
const val DATABASE_VERSION = 270
const val DATABASE_VERSION = 271
@JvmStatic
fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) {

View File

@@ -0,0 +1,28 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.database.helpers.migration
import android.app.Application
import org.signal.core.util.readToList
import org.signal.core.util.requireLong
import org.thoughtcrime.securesms.database.SQLiteDatabase
import java.util.UUID
/**
* Add notification_profile_id column to Notification Profiles to support backups.
*/
@Suppress("ClassName")
object V271_AddNotificationProfileIdColumn : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("ALTER TABLE notification_profile ADD COLUMN notification_profile_id TEXT DEFAULT NULL")
db.rawQuery("SELECT _id FROM notification_profile")
.readToList { it.requireLong("_id") }
.forEach { id ->
db.execSQL("UPDATE notification_profile SET notification_profile_id = '${UUID.randomUUID()}' WHERE _id = $id")
}
}
}

View File

@@ -12,7 +12,8 @@ data class NotificationProfile(
val allowAllCalls: Boolean = true,
val allowAllMentions: Boolean = false,
val schedule: NotificationProfileSchedule,
val allowedMembers: Set<RecipientId> = emptySet()
val allowedMembers: Set<RecipientId> = emptySet(),
val notificationProfileId: NotificationProfileId
) : Comparable<NotificationProfile> {
fun isRecipientAllowed(id: RecipientId): Boolean {

View File

@@ -0,0 +1,29 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/
package org.thoughtcrime.securesms.notifications.profiles
import org.signal.core.util.DatabaseId
import org.whispersystems.signalservice.api.util.UuidUtil
import java.util.UUID
/**
* Typed wrapper for notification profile uuid.
*/
data class NotificationProfileId(val uuid: UUID) : DatabaseId {
companion object {
fun from(id: String): NotificationProfileId {
return NotificationProfileId(UuidUtil.parseOrThrow(id))
}
fun generate(): NotificationProfileId {
return NotificationProfileId(UUID.randomUUID())
}
}
override fun serialize(): String {
return uuid.toString()
}
}

View File

@@ -724,11 +724,30 @@ message FilePointer {
message InvalidAttachmentLocator {
}
// References attachments in a local encrypted backup.
// Importers should first attempt to read the file from the local backup,
// and on failure fallback to backup and transit cdn if possible.
message LocalLocator {
string mediaName = 1;
// Separate key used to encrypt this file for the local backup.
// Generally required. Missing field indicates attachment was not
// available locally when the backup was generated, but remote
// backup or transit info was available.
optional bytes localKey = 2;
bytes remoteKey = 3;
bytes remoteDigest = 4;
uint32 size = 5;
optional uint32 backupCdnNumber = 6;
optional string transitCdnKey = 7;
optional uint32 transitCdnNumber = 8;
}
// If unset, importers should consider it to be an InvalidAttachmentLocator without throwing an error.
oneof locator {
BackupLocator backupLocator = 1;
AttachmentLocator attachmentLocator = 2;
InvalidAttachmentLocator invalidAttachmentLocator = 3;
LocalLocator localLocator = 12;
}
optional string contentType = 4;
@@ -1291,6 +1310,7 @@ message NotificationProfile {
uint32 scheduleStartTime = 9; // 24-hour clock int, 0000-2359 (e.g., 15, 900, 1130, 2345)
uint32 scheduleEndTime = 10; // 24-hour clock int, 0000-2359 (e.g., 15, 900, 1130, 2345)
repeated DayOfWeek scheduleDaysEnabled = 11;
bytes id = 12; // should be 16 bytes
}
message ChatFolder {

View File

@@ -41,7 +41,8 @@ class NotificationProfilesTest {
"first",
"",
createdAt = 1000L,
schedule = NotificationProfileSchedule(1)
schedule = NotificationProfileSchedule(1),
notificationProfileId = NotificationProfileId.generate()
)
private val second = NotificationProfile(
@@ -49,7 +50,8 @@ class NotificationProfilesTest {
"second",
"",
createdAt = 2000L,
schedule = NotificationProfileSchedule(2)
schedule = NotificationProfileSchedule(2),
notificationProfileId = NotificationProfileId.generate()
)
private lateinit var notificationProfileValues: NotificationProfileValues