Add backup support for contact nicknames and notes.

This commit is contained in:
Greyson Parrelli
2025-01-15 13:45:20 -05:00
parent 524fb1aa5a
commit 8d6b8f39ce
48 changed files with 83 additions and 43 deletions

View File

@@ -46,6 +46,9 @@ fun RecipientTable.getContactsForBackup(selfId: Long): ContactArchiveExporter {
"${RecipientTable.TABLE_NAME}.${RecipientTable.PROFILE_GIVEN_NAME}",
"${RecipientTable.TABLE_NAME}.${RecipientTable.PROFILE_FAMILY_NAME}",
"${RecipientTable.TABLE_NAME}.${RecipientTable.PROFILE_JOINED_NAME}",
"${RecipientTable.TABLE_NAME}.${RecipientTable.NICKNAME_GIVEN_NAME}",
"${RecipientTable.TABLE_NAME}.${RecipientTable.NICKNAME_FAMILY_NAME}",
"${RecipientTable.TABLE_NAME}.${RecipientTable.NOTE}",
"${RecipientTable.TABLE_NAME}.${RecipientTable.MUTE_UNTIL}",
"${RecipientTable.TABLE_NAME}.${RecipientTable.CHAT_COLORS}",
"${RecipientTable.TABLE_NAME}.${RecipientTable.CUSTOM_CHAT_COLORS_ID}",

View File

@@ -673,36 +673,34 @@ private fun BackupMessageRecord.toRemotePaymentNotificationUpdate(db: SignalData
}
}
private fun BackupMessageRecord.toRemoteSharedContacts(attachments: List<DatabaseAttachment>?): List<Contact>? {
private fun BackupMessageRecord.toRemoteSharedContact(attachments: List<DatabaseAttachment>?): Contact? {
if (this.sharedContacts.isNullOrEmpty()) {
return emptyList()
return null
}
val attachmentIdMap: Map<AttachmentId, DatabaseAttachment> = attachments?.associateBy { it.attachmentId } ?: emptyMap()
return try {
val contacts: MutableList<Contact> = LinkedList()
val jsonContacts = JSONArray(sharedContacts)
for (i in 0 until jsonContacts.length()) {
val contact: Contact = Contact.deserialize(jsonContacts.getJSONObject(i).toString())
if (contact.avatar != null && contact.avatar!!.attachmentId != null) {
val attachment = attachmentIdMap[contact.avatar!!.attachmentId]
val updatedAvatar = Contact.Avatar(
contact.avatar!!.attachmentId,
attachment,
contact.avatar!!.isProfile
)
contacts += Contact(contact, updatedAvatar)
} else {
contacts += contact
}
if (jsonContacts.length() == 0) {
return null
}
contacts
val contact: Contact = Contact.deserialize(jsonContacts.getJSONObject(0).toString())
return if (contact.avatar != null && contact.avatar!!.attachmentId != null) {
val attachment = attachmentIdMap[contact.avatar!!.attachmentId]
val updatedAvatar = Contact.Avatar(
contact.avatar!!.attachmentId,
attachment,
contact.avatar!!.isProfile
)
Contact(contact, updatedAvatar)
} else {
contact
}
} catch (e: JSONException) {
Log.w(TAG, ExportSkips.failedToParseSharedContact(this.dateSent), e)
null
@@ -768,28 +766,28 @@ private fun BackupMessageRecord.toRemoteViewOnceMessage(mediaArchiveEnabled: Boo
}
private fun BackupMessageRecord.toRemoteContactMessage(mediaArchiveEnabled: Boolean, reactionRecords: List<ReactionRecord>?, attachments: List<DatabaseAttachment>?): ContactMessage? {
val sharedContacts = toRemoteSharedContacts(attachments) ?: return null
val sharedContact = toRemoteSharedContact(attachments) ?: return null
val contacts = sharedContacts.map {
ContactAttachment(
name = it.name.toRemote(),
avatar = (it.avatar?.attachment as? DatabaseAttachment)?.toRemoteMessageAttachment(mediaArchiveEnabled)?.pointer,
organization = it.organization ?: "",
number = it.phoneNumbers.map { phone ->
return ContactMessage(
contact = ContactAttachment(
name = sharedContact.name.toRemote(),
avatar = (sharedContact.avatar?.attachment as? DatabaseAttachment)?.toRemoteMessageAttachment(mediaArchiveEnabled)?.pointer,
organization = sharedContact.organization ?: "",
number = sharedContact.phoneNumbers.map { phone ->
ContactAttachment.Phone(
value_ = phone.number,
type = phone.type.toRemote(),
label = phone.label ?: ""
)
},
email = it.emails.map { email ->
email = sharedContact.emails.map { email ->
ContactAttachment.Email(
value_ = email.email,
label = email.label ?: "",
type = email.type.toRemote()
)
},
address = it.postalAddresses.map { address ->
address = sharedContact.postalAddresses.map { address ->
ContactAttachment.PostalAddress(
type = address.type.toRemote(),
label = address.label ?: "",
@@ -802,10 +800,7 @@ private fun BackupMessageRecord.toRemoteContactMessage(mediaArchiveEnabled: Bool
country = address.country ?: ""
)
}
)
}
return ContactMessage(
contact = contacts,
),
reactions = reactionRecords.toRemote()
)
}

View File

@@ -76,6 +76,8 @@ class ContactArchiveExporter(private val cursor: Cursor, private val selfId: Lon
.hideStory(RecipientTableCursorUtil.getExtras(cursor)?.hideStory() ?: false)
.identityKey(cursor.requireString(IdentityTable.IDENTITY_KEY)?.let { Base64.decode(it).toByteString() })
.identityState(cursor.optionalInt(IdentityTable.VERIFIED).map { IdentityTable.VerifiedStatus.forState(it) }.orElse(IdentityTable.VerifiedStatus.DEFAULT).toRemote())
.note(cursor.requireString(RecipientTable.NOTE) ?: "")
.nickname(cursor.readNickname())
val registeredState = RecipientTable.RegisteredState.fromId(cursor.requireInt(RecipientTable.REGISTERED))
if (registeredState == RecipientTable.RegisteredState.REGISTERED) {
@@ -95,6 +97,20 @@ class ContactArchiveExporter(private val cursor: Cursor, private val selfId: Lon
}
}
private fun Cursor.readNickname(): Contact.Name? {
val given = this.requireString(RecipientTable.NICKNAME_GIVEN_NAME)
val family = this.requireString(RecipientTable.NICKNAME_FAMILY_NAME)
if (given.isNullOrEmpty()) {
return null
}
return Contact.Name(
given = given,
family = family ?: ""
)
}
private fun Recipient.HiddenState.toRemote(): Contact.Visibility {
return when (this) {
Recipient.HiddenState.NOT_HIDDEN -> return Contact.Visibility.VISIBLE

View File

@@ -310,7 +310,7 @@ class ChatItemArchiveImporter(
}
if (this.contactMessage != null) {
val contacts = this.contactMessage.contact.map { backupContact ->
val contact = this.contactMessage.contact?.let { backupContact ->
Contact(
backupContact.name.toLocal(),
backupContact.organization,
@@ -345,18 +345,18 @@ class ChatItemArchiveImporter(
)
}
val contactAttachments = contacts.mapNotNull { it.avatarAttachment }
if (contacts.isNotEmpty()) {
if (contact != null) {
val contactAttachment: Attachment? = contact.avatarAttachment
followUp = { messageRowId ->
val attachmentMap = if (contactAttachments.isNotEmpty()) {
SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, contactAttachments, emptyList())
val attachmentMap = if (contactAttachment != null) {
SignalDatabase.attachments.insertAttachmentsForMessage(messageRowId, listOf(contactAttachment), emptyList())
} else {
emptyMap()
}
db.update(
MessageTable.TABLE_NAME,
contentValuesOf(
MessageTable.SHARED_CONTACTS to SignalDatabase.messages.getSerializedSharedContacts(attachmentMap, contacts)
MessageTable.SHARED_CONTACTS to SignalDatabase.messages.getSerializedSharedContacts(attachmentMap, listOf(contact))
),
"${MessageTable.ID} = ?",
SqlUtil.buildArgs(messageRowId)

View File

@@ -49,7 +49,10 @@ object ContactArchiveImporter {
RecipientTable.PROFILE_KEY to if (profileKey == null) null else Base64.encodeWithPadding(profileKey),
RecipientTable.PROFILE_SHARING to contact.profileSharing.toInt(),
RecipientTable.USERNAME to contact.username,
RecipientTable.EXTRAS to contact.toLocalExtras().encode()
RecipientTable.EXTRAS to contact.toLocalExtras().encode(),
RecipientTable.NOTE to contact.note,
RecipientTable.NICKNAME_GIVEN_NAME to contact.nickname?.given,
RecipientTable.NICKNAME_FAMILY_NAME to contact.nickname?.family
)
if (contact.registered != null) {