Remove cruft around SignalAccountRecord.

This commit is contained in:
Greyson Parrelli
2024-11-11 11:01:34 -05:00
parent 5e8318d63f
commit ae37c4019f
30 changed files with 536 additions and 1523 deletions

View File

@@ -17,8 +17,11 @@ import org.signal.core.util.SqlUtil
import org.signal.core.util.delete
import org.signal.core.util.exists
import org.signal.core.util.forEach
import org.signal.core.util.hasUnknownFields
import org.signal.core.util.isNotEmpty
import org.signal.core.util.logging.Log
import org.signal.core.util.nullIfBlank
import org.signal.core.util.nullIfEmpty
import org.signal.core.util.optionalString
import org.signal.core.util.or
import org.signal.core.util.orNull
@@ -1024,12 +1027,13 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
}
fun applyStorageSyncAccountUpdate(update: StorageRecordUpdate<SignalAccountRecord>) {
val profileName = ProfileName.fromParts(update.new.givenName.orElse(null), update.new.familyName.orElse(null))
val localKey = ProfileKeyUtil.profileKeyOptional(update.old.profileKey.orElse(null))
val remoteKey = ProfileKeyUtil.profileKeyOptional(update.new.profileKey.orElse(null))
val profileKey: String? = remoteKey.or(localKey).map { obj: ProfileKey -> obj.serialize() }.map { source: ByteArray? -> Base64.encodeWithPadding(source!!) }.orElse(null)
if (!remoteKey.isPresent) {
Log.w(TAG, "Got an empty profile key while applying an account record update! The parsed local key is ${if (localKey.isPresent) "present" else "not present"}. The raw local key is ${if (update.old.profileKey.isPresent) "present" else "not present"}. The resulting key is ${if (profileKey != null) "present" else "not present"}.")
val profileName = ProfileName.fromParts(update.new.proto.givenName, update.new.proto.familyName)
val localKey = update.old.proto.profileKey.nullIfEmpty()?.toByteArray()?.let { ProfileKeyUtil.profileKeyOrNull(it) }
val remoteKey = update.new.proto.profileKey.nullIfEmpty()?.toByteArray()?.let { ProfileKeyUtil.profileKeyOrNull(it) }
val profileKey: String? = (remoteKey ?: localKey)?.let { Base64.encodeWithPadding(it.serialize()) }
if (remoteKey == null) {
Log.w(TAG, "Got an empty profile key while applying an account record update! The parsed local key is ${if (localKey != null) "present" else "not present"}. The raw local key is ${if (update.old.proto.profileKey.isNotEmpty()) "present" else "not present"}. The resulting key is ${if (profileKey != null) "present" else "not present"}.")
}
val values = ContentValues().apply {
@@ -1043,21 +1047,21 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
Log.w(TAG, "Avoided attempt to apply null profile key in account record update!")
}
put(USERNAME, update.new.username)
put(USERNAME, update.new.proto.username.nullIfBlank())
put(STORAGE_SERVICE_ID, Base64.encodeWithPadding(update.new.id.raw))
if (update.new.hasUnknownFields()) {
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(Objects.requireNonNull(update.new.serializeUnknownFields())))
if (update.new.proto.hasUnknownFields()) {
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(update.new.serializeUnknownFields()!!))
} else {
putNull(STORAGE_SERVICE_PROTO)
}
}
if (update.new.username != null) {
if (update.new.proto.username.nullIfBlank() != null) {
writableDatabase
.update(TABLE_NAME)
.values(USERNAME to null)
.where("$USERNAME = ?", update.new.username!!)
.where("$USERNAME = ?", update.new.proto.username)
.run()
}

View File

@@ -71,10 +71,11 @@ import org.thoughtcrime.securesms.util.LRUCache
import org.thoughtcrime.securesms.util.TextSecurePreferences
import org.thoughtcrime.securesms.util.isScheduled
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
import org.whispersystems.signalservice.api.storage.SignalAccountRecord.PinnedConversation
import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record
import org.whispersystems.signalservice.api.storage.toSignalServiceAddress
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord
import java.io.Closeable
import java.io.IOException
import java.util.Collections
@@ -1522,7 +1523,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
fun applyStorageSyncUpdate(recipientId: RecipientId, record: SignalAccountRecord) {
writableDatabase.withinTransaction { db ->
applyStorageSyncUpdate(recipientId, record.isNoteToSelfArchived, record.isNoteToSelfForcedUnread)
applyStorageSyncUpdate(recipientId, record.proto.noteToSelfArchived, record.proto.noteToSelfMarkedUnread)
db.updateAll(TABLE_NAME)
.values(PINNED to 0)
@@ -1530,19 +1531,19 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
var pinnedPosition = 1
for (pinned: PinnedConversation in record.pinnedConversations) {
val pinnedRecipient: Recipient? = if (pinned.contact.isPresent) {
Recipient.externalPush(pinned.contact.get())
} else if (pinned.groupV1Id.isPresent) {
for (pinned: AccountRecord.PinnedConversation in record.proto.pinnedConversations) {
val pinnedRecipient: Recipient? = if (pinned.contact != null) {
Recipient.externalPush(pinned.contact!!.toSignalServiceAddress())
} else if (pinned.legacyGroupId != null) {
try {
Recipient.externalGroupExact(GroupId.v1(pinned.groupV1Id.get()))
Recipient.externalGroupExact(GroupId.v1(pinned.legacyGroupId!!.toByteArray()))
} catch (e: BadGroupIdException) {
Log.w(TAG, "Failed to parse pinned groupV1 ID!", e)
null
}
} else if (pinned.groupV2MasterKey.isPresent) {
} else if (pinned.groupMasterKey != null) {
try {
Recipient.externalGroupExact(GroupId.v2(GroupMasterKey(pinned.groupV2MasterKey.get())))
Recipient.externalGroupExact(GroupId.v2(GroupMasterKey(pinned.groupMasterKey!!.toByteArray())))
} catch (e: InvalidInputException) {
Log.w(TAG, "Failed to parse pinned groupV2 master key!", e)
null

View File

@@ -124,9 +124,9 @@ public class StorageAccountRestoreJob extends BaseJob {
JobManager jobManager = AppDependencies.getJobManager();
if (accountRecord.getAvatarUrlPath().isPresent()) {
if (!accountRecord.getProto().avatarUrlPath.isEmpty()) {
Log.i(TAG, "Fetching avatar...");
Optional<JobTracker.JobState> state = jobManager.runSynchronously(new RetrieveProfileAvatarJob(Recipient.self(), accountRecord.getAvatarUrlPath().get()), LIFESPAN/2);
Optional<JobTracker.JobState> state = jobManager.runSynchronously(new RetrieveProfileAvatarJob(Recipient.self(), accountRecord.getProto().avatarUrlPath), LIFESPAN / 2);
if (state.isPresent()) {
Log.i(TAG, "Avatar retrieved successfully. " + state.get());

View File

@@ -100,13 +100,11 @@ import java.util.stream.Collectors
* - Update the respective model (i.e. [SignalContactRecord])
* - Add getters
* - Update the builder
* - Update [SignalRecord.describeDiff].
* - Update the respective record processor (i.e [ContactRecordProcessor]). You need to make
* sure that you're:
* - Merging the attributes, likely preferring remote
* - Adding to doParamsMatch()
* - Adding the parameter to the builder chain when creating a merged model
* - Update builder usage in StorageSyncModels
* - Update the respective record processor (i.e [ContactRecordProcessor]). You need to make sure that you're:
* - Merging the attributes, likely preferring remote
* - Adding to doParamsMatch()
* - Adding the parameter to the builder chain when creating a merged model
* - Update builder usage in StorageSyncModels
* - Handle the new data when writing to the local storage
* (i.e. [RecipientTable.applyStorageSyncContactUpdate]).
* - Make sure that whenever you change the field in the UI, we rotate the storageId for that row

View File

@@ -6,6 +6,7 @@ import android.content.SharedPreferences
import android.preference.PreferenceManager
import org.signal.core.util.Base64
import org.signal.core.util.logging.Log
import org.signal.core.util.nullIfBlank
import org.signal.libsignal.protocol.IdentityKey
import org.signal.libsignal.protocol.IdentityKeyPair
import org.signal.libsignal.protocol.ecc.Curve
@@ -401,10 +402,10 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
var username: String?
get() {
val value = getString(KEY_USERNAME, null)
return if (value.isNullOrBlank()) null else value
return value.nullIfBlank()
}
set(value) {
putString(KEY_USERNAME, value)
putString(KEY_USERNAME, value.nullIfBlank())
}
/** The local user's username link components, if set. */

View File

@@ -1,14 +1,20 @@
package org.thoughtcrime.securesms.storage
import android.content.Context
import okio.ByteString
import org.signal.core.util.isNotEmpty
import org.signal.core.util.logging.Log
import org.signal.core.util.nullIfEmpty
import org.thoughtcrime.securesms.keyvalue.SignalStore
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.storage.StorageSyncHelper.applyAccountStorageSyncUpdates
import org.thoughtcrime.securesms.storage.StorageSyncHelper.buildAccountRecord
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
import org.whispersystems.signalservice.api.util.OptionalUtil
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord
import org.whispersystems.signalservice.api.storage.StorageId
import org.whispersystems.signalservice.api.storage.safeSetBackupsSubscriber
import org.whispersystems.signalservice.api.storage.safeSetPayments
import org.whispersystems.signalservice.api.storage.safeSetSubscriber
import org.whispersystems.signalservice.api.storage.toSignalAccountRecord
import org.whispersystems.signalservice.internal.storage.protos.OptionalBool
import java.util.Optional
@@ -45,184 +51,100 @@ class AccountRecordProcessor(
return false
}
override fun getMatching(record: SignalAccountRecord, keyGenerator: StorageKeyGenerator): Optional<SignalAccountRecord> {
override fun getMatching(remote: SignalAccountRecord, keyGenerator: StorageKeyGenerator): Optional<SignalAccountRecord> {
return Optional.of(localAccountRecord)
}
override fun merge(remote: SignalAccountRecord, local: SignalAccountRecord, keyGenerator: StorageKeyGenerator): SignalAccountRecord {
val givenName: String
val familyName: String
val mergedGivenName: String
val mergedFamilyName: String
if (remote.givenName.isPresent || remote.familyName.isPresent) {
givenName = remote.givenName.orElse("")
familyName = remote.familyName.orElse("")
if (remote.proto.givenName.isNotBlank() || remote.proto.familyName.isNotBlank()) {
mergedGivenName = remote.proto.givenName
mergedFamilyName = remote.proto.familyName
} else {
givenName = local.givenName.orElse("")
familyName = local.familyName.orElse("")
mergedGivenName = local.proto.givenName
mergedFamilyName = local.proto.familyName
}
val payments = if (remote.payments.entropy.isPresent) {
remote.payments
val payments = if (remote.proto.payments?.entropy != null) {
remote.proto.payments
} else {
local.payments
local.proto.payments
}
val subscriber = if (remote.subscriber.id.isPresent) {
remote.subscriber
val donationSubscriberId: ByteString
val donationSubscriberCurrencyCode: String
if (remote.proto.subscriberId.isNotEmpty()) {
donationSubscriberId = remote.proto.subscriberId
donationSubscriberCurrencyCode = remote.proto.subscriberCurrencyCode
} else {
local.subscriber
donationSubscriberId = local.proto.subscriberId
donationSubscriberCurrencyCode = remote.proto.subscriberCurrencyCode
}
val backupsSubscriber = if (remote.subscriber.id.isPresent) {
remote.subscriber
val backupsSubscriberId: ByteString
val backupsSubscriberCurrencyCode: String
if (remote.proto.backupsSubscriberId.isNotEmpty()) {
backupsSubscriberId = remote.proto.backupsSubscriberId
backupsSubscriberCurrencyCode = remote.proto.backupsSubscriberCurrencyCode
} else {
local.subscriber
backupsSubscriberId = local.proto.backupsSubscriberId
backupsSubscriberCurrencyCode = remote.proto.backupsSubscriberCurrencyCode
}
val storyViewReceiptsState = if (remote.storyViewReceiptsState == OptionalBool.UNSET) {
local.storyViewReceiptsState
val storyViewReceiptsState = if (remote.proto.storyViewReceiptsEnabled == OptionalBool.UNSET) {
local.proto.storyViewReceiptsEnabled
} else {
remote.storyViewReceiptsState
remote.proto.storyViewReceiptsEnabled
}
val unknownFields = remote.serializeUnknownFields()
val avatarUrlPath = OptionalUtil.or(remote.avatarUrlPath, local.avatarUrlPath).orElse("")
val profileKey = OptionalUtil.or(remote.profileKey, local.profileKey).orElse(null)
val noteToSelfArchived = remote.isNoteToSelfArchived
val noteToSelfForcedUnread = remote.isNoteToSelfForcedUnread
val readReceipts = remote.isReadReceiptsEnabled
val typingIndicators = remote.isTypingIndicatorsEnabled
val sealedSenderIndicators = remote.isSealedSenderIndicatorsEnabled
val linkPreviews = remote.isLinkPreviewsEnabled
val unlisted = remote.isPhoneNumberUnlisted
val pinnedConversations = remote.pinnedConversations
val phoneNumberSharingMode = remote.phoneNumberSharingMode
val preferContactAvatars = remote.isPreferContactAvatars
val universalExpireTimer = remote.universalExpireTimer
val primarySendsSms = if (SignalStore.account.isPrimaryDevice) local.isPrimarySendsSms else remote.isPrimarySendsSms
val e164 = if (SignalStore.account.isPrimaryDevice) local.e164 else remote.e164
val defaultReactions = if (remote.defaultReactions.size > 0) remote.defaultReactions else local.defaultReactions
val displayBadgesOnProfile = remote.isDisplayBadgesOnProfile
val subscriptionManuallyCancelled = remote.isSubscriptionManuallyCancelled
val keepMutedChatsArchived = remote.isKeepMutedChatsArchived
val hasSetMyStoriesPrivacy = remote.hasSetMyStoriesPrivacy()
val hasViewedOnboardingStory = remote.hasViewedOnboardingStory() || local.hasViewedOnboardingStory()
val storiesDisabled = remote.isStoriesDisabled
val hasSeenGroupStoryEducation = remote.hasSeenGroupStoryEducationSheet() || local.hasSeenGroupStoryEducationSheet()
val hasSeenUsernameOnboarding = remote.hasCompletedUsernameOnboarding() || local.hasCompletedUsernameOnboarding()
val username = remote.username
val usernameLink = remote.usernameLink
val matchesRemote = doParamsMatch(
contact = remote,
unknownFields = unknownFields,
givenName = givenName,
familyName = familyName,
avatarUrlPath = avatarUrlPath,
profileKey = profileKey,
noteToSelfArchived = noteToSelfArchived,
noteToSelfForcedUnread = noteToSelfForcedUnread,
readReceipts = readReceipts,
typingIndicators = typingIndicators,
sealedSenderIndicators = sealedSenderIndicators,
linkPreviewsEnabled = linkPreviews,
phoneNumberSharingMode = phoneNumberSharingMode,
unlistedPhoneNumber = unlisted,
pinnedConversations = pinnedConversations,
preferContactAvatars = preferContactAvatars,
payments = payments,
universalExpireTimer = universalExpireTimer,
primarySendsSms = primarySendsSms,
e164 = e164,
defaultReactions = defaultReactions,
subscriber = subscriber,
displayBadgesOnProfile = displayBadgesOnProfile,
subscriptionManuallyCancelled = subscriptionManuallyCancelled,
keepMutedChatsArchived = keepMutedChatsArchived,
hasSetMyStoriesPrivacy = hasSetMyStoriesPrivacy,
hasViewedOnboardingStory = hasViewedOnboardingStory,
hasCompletedUsernameOnboarding = hasSeenUsernameOnboarding,
storiesDisabled = storiesDisabled,
storyViewReceiptsState = storyViewReceiptsState,
username = username,
usernameLink = usernameLink,
backupsSubscriber = backupsSubscriber
)
val matchesLocal = doParamsMatch(
contact = local,
unknownFields = unknownFields,
givenName = givenName,
familyName = familyName,
avatarUrlPath = avatarUrlPath,
profileKey = profileKey,
noteToSelfArchived = noteToSelfArchived,
noteToSelfForcedUnread = noteToSelfForcedUnread,
readReceipts = readReceipts,
typingIndicators = typingIndicators,
sealedSenderIndicators = sealedSenderIndicators,
linkPreviewsEnabled = linkPreviews,
phoneNumberSharingMode = phoneNumberSharingMode,
unlistedPhoneNumber = unlisted,
pinnedConversations = pinnedConversations,
preferContactAvatars = preferContactAvatars,
payments = payments,
universalExpireTimer = universalExpireTimer,
primarySendsSms = primarySendsSms,
e164 = e164,
defaultReactions = defaultReactions,
subscriber = subscriber,
displayBadgesOnProfile = displayBadgesOnProfile,
subscriptionManuallyCancelled = subscriptionManuallyCancelled,
keepMutedChatsArchived = keepMutedChatsArchived,
hasSetMyStoriesPrivacy = hasSetMyStoriesPrivacy,
hasViewedOnboardingStory = hasViewedOnboardingStory,
hasCompletedUsernameOnboarding = hasSeenUsernameOnboarding,
storiesDisabled = storiesDisabled,
storyViewReceiptsState = storyViewReceiptsState,
username = username,
usernameLink = usernameLink,
backupsSubscriber = backupsSubscriber
)
val merged = SignalAccountRecord.newBuilder(unknownFields).apply {
givenName = mergedGivenName
familyName = mergedFamilyName
avatarUrlPath = remote.proto.avatarUrlPath.nullIfEmpty() ?: local.proto.avatarUrlPath
profileKey = remote.proto.profileKey.nullIfEmpty() ?: local.proto.profileKey
noteToSelfArchived = remote.proto.noteToSelfArchived
noteToSelfMarkedUnread = remote.proto.noteToSelfMarkedUnread
readReceipts = remote.proto.readReceipts
typingIndicators = remote.proto.typingIndicators
sealedSenderIndicators = remote.proto.sealedSenderIndicators
linkPreviews = remote.proto.linkPreviews
unlistedPhoneNumber = remote.proto.unlistedPhoneNumber
pinnedConversations = remote.proto.pinnedConversations
phoneNumberSharingMode = remote.proto.phoneNumberSharingMode
preferContactAvatars = remote.proto.preferContactAvatars
universalExpireTimer = remote.proto.universalExpireTimer
primarySendsSms = false
e164 = if (SignalStore.account.isPrimaryDevice) local.proto.e164 else remote.proto.e164
preferredReactionEmoji = remote.proto.preferredReactionEmoji.takeIf { it.isNotEmpty() } ?: local.proto.preferredReactionEmoji
displayBadgesOnProfile = remote.proto.displayBadgesOnProfile
subscriptionManuallyCancelled = remote.proto.subscriptionManuallyCancelled
keepMutedChatsArchived = remote.proto.keepMutedChatsArchived
hasSetMyStoriesPrivacy = remote.proto.hasSetMyStoriesPrivacy
hasViewedOnboardingStory = remote.proto.hasViewedOnboardingStory || local.proto.hasViewedOnboardingStory
storiesDisabled = remote.proto.storiesDisabled
storyViewReceiptsEnabled = storyViewReceiptsState
hasSeenGroupStoryEducationSheet = remote.proto.hasSeenGroupStoryEducationSheet || local.proto.hasSeenGroupStoryEducationSheet
hasCompletedUsernameOnboarding = remote.proto.hasCompletedUsernameOnboarding || local.proto.hasCompletedUsernameOnboarding
username = remote.proto.username
usernameLink = remote.proto.usernameLink
if (matchesRemote) {
return remote
} else if (matchesLocal) {
return local
safeSetPayments(payments?.enabled == true, payments?.entropy?.toByteArray())
safeSetSubscriber(donationSubscriberId, donationSubscriberCurrencyCode)
safeSetBackupsSubscriber(backupsSubscriberId, backupsSubscriberCurrencyCode)
}.toSignalAccountRecord(StorageId.forAccount(keyGenerator.generate()))
return if (doParamsMatch(remote, merged)) {
remote
} else if (doParamsMatch(local, merged)) {
local
} else {
val builder = SignalAccountRecord.Builder(keyGenerator.generate(), unknownFields)
.setGivenName(givenName)
.setFamilyName(familyName)
.setAvatarUrlPath(avatarUrlPath)
.setProfileKey(profileKey)
.setNoteToSelfArchived(noteToSelfArchived)
.setNoteToSelfForcedUnread(noteToSelfForcedUnread)
.setReadReceiptsEnabled(readReceipts)
.setTypingIndicatorsEnabled(typingIndicators)
.setSealedSenderIndicatorsEnabled(sealedSenderIndicators)
.setLinkPreviewsEnabled(linkPreviews)
.setUnlistedPhoneNumber(unlisted)
.setPhoneNumberSharingMode(phoneNumberSharingMode)
.setUnlistedPhoneNumber(unlisted)
.setPinnedConversations(pinnedConversations)
.setPreferContactAvatars(preferContactAvatars)
.setPayments(payments.isEnabled, payments.entropy.orElse(null))
.setUniversalExpireTimer(universalExpireTimer)
.setPrimarySendsSms(primarySendsSms)
.setDefaultReactions(defaultReactions)
.setSubscriber(subscriber)
.setStoryViewReceiptsState(storyViewReceiptsState)
.setDisplayBadgesOnProfile(displayBadgesOnProfile)
.setSubscriptionManuallyCancelled(subscriptionManuallyCancelled)
.setKeepMutedChatsArchived(keepMutedChatsArchived)
.setHasSetMyStoriesPrivacy(hasSetMyStoriesPrivacy)
.setHasViewedOnboardingStory(hasViewedOnboardingStory)
.setStoriesDisabled(storiesDisabled)
.setHasSeenGroupStoryEducationSheet(hasSeenGroupStoryEducation)
.setHasCompletedUsernameOnboarding(hasSeenUsernameOnboarding)
.setUsername(username)
.setUsernameLink(usernameLink)
.setBackupsSubscriber(backupsSubscriber)
return builder.build()
merged
}
}
@@ -238,72 +160,7 @@ class AccountRecordProcessor(
return 0
}
private fun doParamsMatch(
contact: SignalAccountRecord,
unknownFields: ByteArray?,
givenName: String,
familyName: String,
avatarUrlPath: String,
profileKey: ByteArray?,
noteToSelfArchived: Boolean,
noteToSelfForcedUnread: Boolean,
readReceipts: Boolean,
typingIndicators: Boolean,
sealedSenderIndicators: Boolean,
linkPreviewsEnabled: Boolean,
phoneNumberSharingMode: AccountRecord.PhoneNumberSharingMode,
unlistedPhoneNumber: Boolean,
pinnedConversations: List<SignalAccountRecord.PinnedConversation>,
preferContactAvatars: Boolean,
payments: SignalAccountRecord.Payments,
universalExpireTimer: Int,
primarySendsSms: Boolean,
e164: String,
defaultReactions: List<String>,
subscriber: SignalAccountRecord.Subscriber,
displayBadgesOnProfile: Boolean,
subscriptionManuallyCancelled: Boolean,
keepMutedChatsArchived: Boolean,
hasSetMyStoriesPrivacy: Boolean,
hasViewedOnboardingStory: Boolean,
hasCompletedUsernameOnboarding: Boolean,
storiesDisabled: Boolean,
storyViewReceiptsState: OptionalBool,
username: String?,
usernameLink: AccountRecord.UsernameLink?,
backupsSubscriber: SignalAccountRecord.Subscriber
): Boolean {
return contact.serializeUnknownFields().contentEquals(unknownFields) &&
contact.givenName.orElse("") == givenName &&
contact.familyName.orElse("") == familyName &&
contact.avatarUrlPath.orElse("") == avatarUrlPath &&
contact.payments == payments &&
contact.e164 == e164 &&
contact.defaultReactions == defaultReactions &&
contact.profileKey.orElse(null).contentEquals(profileKey) &&
contact.isNoteToSelfArchived == noteToSelfArchived &&
contact.isNoteToSelfForcedUnread == noteToSelfForcedUnread &&
contact.isReadReceiptsEnabled == readReceipts &&
contact.isTypingIndicatorsEnabled == typingIndicators &&
contact.isSealedSenderIndicatorsEnabled == sealedSenderIndicators &&
contact.isLinkPreviewsEnabled == linkPreviewsEnabled &&
contact.phoneNumberSharingMode == phoneNumberSharingMode &&
contact.isPhoneNumberUnlisted == unlistedPhoneNumber &&
contact.isPreferContactAvatars == preferContactAvatars &&
contact.universalExpireTimer == universalExpireTimer &&
contact.isPrimarySendsSms == primarySendsSms &&
contact.pinnedConversations == pinnedConversations &&
contact.subscriber == subscriber &&
contact.isDisplayBadgesOnProfile == displayBadgesOnProfile &&
contact.isSubscriptionManuallyCancelled == subscriptionManuallyCancelled &&
contact.isKeepMutedChatsArchived == keepMutedChatsArchived &&
contact.hasSetMyStoriesPrivacy() == hasSetMyStoriesPrivacy &&
contact.hasViewedOnboardingStory() == hasViewedOnboardingStory &&
contact.hasCompletedUsernameOnboarding() == hasCompletedUsernameOnboarding &&
contact.isStoriesDisabled == storiesDisabled &&
contact.storyViewReceiptsState == storyViewReceiptsState &&
contact.username == username &&
contact.usernameLink == usernameLink &&
contact.backupsSubscriber == backupsSubscriber
private fun doParamsMatch(base: SignalAccountRecord, test: SignalAccountRecord): Boolean {
return base.serializeUnknownFields().contentEquals(test.serializeUnknownFields()) && base.proto == test.proto
}
}

View File

@@ -15,7 +15,7 @@ import java.util.TreeSet
* our local store). We use it for a [TreeSet], so mainly it's just important that the '0'
* case is correct. Other cases are whatever, just make it something stable.
*/
abstract class DefaultStorageRecordProcessor<E : SignalRecord> : StorageRecordProcessor<E>, Comparator<E> {
abstract class DefaultStorageRecordProcessor<E : SignalRecord<*>> : StorageRecordProcessor<E>, Comparator<E> {
companion object {
private val TAG = Log.tag(DefaultStorageRecordProcessor::class.java)
}
@@ -37,16 +37,15 @@ abstract class DefaultStorageRecordProcessor<E : SignalRecord> : StorageRecordPr
@Throws(IOException::class)
override fun process(remoteRecords: Collection<E>, keyGenerator: StorageKeyGenerator) {
val matchedRecords: MutableSet<E> = TreeSet(this)
var i = 0
for (remote in remoteRecords) {
for ((i, remote) in remoteRecords.withIndex()) {
if (isInvalid(remote)) {
warn(i, remote, "Found invalid key! Ignoring it.")
} else {
val local = getMatching(remote, keyGenerator)
if (local.isPresent) {
val merged = merge(remote, local.get(), keyGenerator)
val merged: E = merge(remote, local.get(), keyGenerator)
if (matchedRecords.contains(local.get())) {
warn(i, remote, "Multiple remote records map to the same local record! Ignoring this one.")
@@ -54,7 +53,7 @@ abstract class DefaultStorageRecordProcessor<E : SignalRecord> : StorageRecordPr
matchedRecords.add(local.get())
if (merged != remote) {
info(i, remote, "[Remote Update] " + StorageRecordUpdate(remote, merged).toString())
info(i, remote, "[Remote Update] " + remote.describeDiff(merged))
}
if (merged != local.get()) {
@@ -68,8 +67,6 @@ abstract class DefaultStorageRecordProcessor<E : SignalRecord> : StorageRecordPr
insertLocal(remote)
}
}
i++
}
}

View File

@@ -7,7 +7,7 @@ import java.io.IOException
* Handles processing a remote record, which involves applying any local changes that need to be
* made based on the remote records.
*/
interface StorageRecordProcessor<E : SignalRecord?> {
interface StorageRecordProcessor<E : SignalRecord<*>> {
@Throws(IOException::class)
fun process(remoteRecords: Collection<E>, keyGenerator: StorageKeyGenerator)
}

View File

@@ -1,48 +0,0 @@
package org.thoughtcrime.securesms.storage;
import androidx.annotation.NonNull;
import androidx.annotation.VisibleForTesting;
import org.whispersystems.signalservice.api.storage.SignalRecord;
import java.util.Objects;
/**
* Represents a pair of records: one old, and one new. The new record should replace the old.
*/
public class StorageRecordUpdate<E extends SignalRecord> {
private final E oldRecord;
private final E newRecord;
public StorageRecordUpdate(@NonNull E oldRecord, @NonNull E newRecord) {
this.oldRecord = oldRecord;
this.newRecord = newRecord;
}
public @NonNull E getOld() {
return oldRecord;
}
public @NonNull E getNew() {
return newRecord;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
StorageRecordUpdate that = (StorageRecordUpdate) o;
return oldRecord.equals(that.oldRecord) &&
newRecord.equals(that.newRecord);
}
@Override
public int hashCode() {
return Objects.hash(oldRecord, newRecord);
}
@Override
public @NonNull String toString() {
return newRecord.describeDiff(oldRecord);
}
}

View File

@@ -0,0 +1,27 @@
package org.thoughtcrime.securesms.storage
import org.whispersystems.signalservice.api.storage.SignalRecord
/**
* Represents a pair of records: one old, and one new. The new record should replace the old.
*/
class StorageRecordUpdate<E : SignalRecord<*>>(val old: E, val new: E) {
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as StorageRecordUpdate<*>
if (old != other.old) return false
if (new != other.new) return false
return true
}
override fun hashCode(): Int {
var result = old.hashCode()
result = 31 * result + new.hashCode()
return result
}
}

View File

@@ -2,6 +2,7 @@ package org.thoughtcrime.securesms.storage
import android.content.Context
import androidx.annotation.VisibleForTesting
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.signal.core.util.Base64.encodeWithPadding
import org.signal.core.util.logging.Log
@@ -28,6 +29,10 @@ import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.api.storage.SignalStorageManifest
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
import org.whispersystems.signalservice.api.storage.StorageId
import org.whispersystems.signalservice.api.storage.safeSetBackupsSubscriber
import org.whispersystems.signalservice.api.storage.safeSetPayments
import org.whispersystems.signalservice.api.storage.safeSetSubscriber
import org.whispersystems.signalservice.api.storage.toSignalAccountRecord
import org.whispersystems.signalservice.api.util.OptionalUtil.byteArrayEquals
import org.whispersystems.signalservice.api.util.UuidUtil
import org.whispersystems.signalservice.api.util.toByteArray
@@ -130,52 +135,54 @@ object StorageSyncHelper {
val storageId = selfRecord?.storageId ?: self.storageId
val account = SignalAccountRecord.Builder(storageId, selfRecord?.syncExtras?.storageProto)
.setProfileKey(self.profileKey)
.setGivenName(self.profileName.givenName)
.setFamilyName(self.profileName.familyName)
.setAvatarUrlPath(self.profileAvatar)
.setNoteToSelfArchived(selfRecord != null && selfRecord.syncExtras.isArchived)
.setNoteToSelfForcedUnread(selfRecord != null && selfRecord.syncExtras.isForcedUnread)
.setTypingIndicatorsEnabled(TextSecurePreferences.isTypingIndicatorsEnabled(context))
.setReadReceiptsEnabled(TextSecurePreferences.isReadReceiptsEnabled(context))
.setSealedSenderIndicatorsEnabled(TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context))
.setLinkPreviewsEnabled(SignalStore.settings.isLinkPreviewsEnabled)
.setUnlistedPhoneNumber(SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode == PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE)
.setPhoneNumberSharingMode(StorageSyncModels.localToRemotePhoneNumberSharingMode(SignalStore.phoneNumberPrivacy.phoneNumberSharingMode))
.setPinnedConversations(StorageSyncModels.localToRemotePinnedConversations(pinned))
.setPreferContactAvatars(SignalStore.settings.isPreferSystemContactPhotos)
.setPayments(SignalStore.payments.mobileCoinPaymentsEnabled(), Optional.ofNullable(SignalStore.payments.paymentsEntropy).map { obj: Entropy -> obj.bytes }.orElse(null))
.setPrimarySendsSms(false)
.setUniversalExpireTimer(SignalStore.settings.universalExpireTimer)
.setDefaultReactions(SignalStore.emoji.reactions)
.setSubscriber(StorageSyncModels.localToRemoteSubscriber(getSubscriber(InAppPaymentSubscriberRecord.Type.DONATION)))
.setBackupsSubscriber(StorageSyncModels.localToRemoteSubscriber(getSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP)))
.setDisplayBadgesOnProfile(SignalStore.inAppPayments.getDisplayBadgesOnProfile())
.setSubscriptionManuallyCancelled(isUserManuallyCancelled(InAppPaymentSubscriberRecord.Type.DONATION))
.setKeepMutedChatsArchived(SignalStore.settings.shouldKeepMutedChatsArchived())
.setHasSetMyStoriesPrivacy(SignalStore.story.userHasBeenNotifiedAboutStories)
.setHasViewedOnboardingStory(SignalStore.story.userHasViewedOnboardingStory)
.setStoriesDisabled(SignalStore.story.isFeatureDisabled)
.setStoryViewReceiptsState(storyViewReceiptsState)
.setHasSeenGroupStoryEducationSheet(SignalStore.story.userHasSeenGroupStoryEducationSheet)
.setUsername(SignalStore.account.username)
.setHasCompletedUsernameOnboarding(SignalStore.uiHints.hasCompletedUsernameOnboarding())
val accountRecord = SignalAccountRecord.newBuilder(selfRecord?.syncExtras?.storageProto).apply {
profileKey = self.profileKey?.toByteString() ?: ByteString.EMPTY
givenName = self.profileName.givenName
familyName = self.profileName.familyName
avatarUrlPath = self.profileAvatar ?: ""
noteToSelfArchived = selfRecord != null && selfRecord.syncExtras.isArchived
noteToSelfMarkedUnread = selfRecord != null && selfRecord.syncExtras.isForcedUnread
typingIndicators = TextSecurePreferences.isTypingIndicatorsEnabled(context)
readReceipts = TextSecurePreferences.isReadReceiptsEnabled(context)
sealedSenderIndicators = TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context)
linkPreviews = SignalStore.settings.isLinkPreviewsEnabled
unlistedPhoneNumber = SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode == PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE
phoneNumberSharingMode = StorageSyncModels.localToRemotePhoneNumberSharingMode(SignalStore.phoneNumberPrivacy.phoneNumberSharingMode)
pinnedConversations = StorageSyncModels.localToRemotePinnedConversations(pinned)
preferContactAvatars = SignalStore.settings.isPreferSystemContactPhotos
primarySendsSms = false
universalExpireTimer = SignalStore.settings.universalExpireTimer
preferredReactionEmoji = SignalStore.emoji.reactions
displayBadgesOnProfile = SignalStore.inAppPayments.getDisplayBadgesOnProfile()
subscriptionManuallyCancelled = isUserManuallyCancelled(InAppPaymentSubscriberRecord.Type.DONATION)
keepMutedChatsArchived = SignalStore.settings.shouldKeepMutedChatsArchived()
hasSetMyStoriesPrivacy = SignalStore.story.userHasBeenNotifiedAboutStories
hasViewedOnboardingStory = SignalStore.story.userHasViewedOnboardingStory
storiesDisabled = SignalStore.story.isFeatureDisabled
storyViewReceiptsEnabled = storyViewReceiptsState
hasSeenGroupStoryEducationSheet = SignalStore.story.userHasSeenGroupStoryEducationSheet
hasCompletedUsernameOnboarding = SignalStore.uiHints.hasCompletedUsernameOnboarding()
username = SignalStore.account.username ?: ""
usernameLink = SignalStore.account.usernameLink?.let { linkComponents ->
AccountRecord.UsernameLink(
entropy = linkComponents.entropy.toByteString(),
serverId = linkComponents.serverId.toByteArray().toByteString(),
color = StorageSyncModels.localToRemoteUsernameColor(SignalStore.misc.usernameQrCodeColorScheme)
)
}
val linkComponents = SignalStore.account.usernameLink
if (linkComponents != null) {
account.setUsernameLink(
AccountRecord.UsernameLink.Builder()
.entropy(linkComponents.entropy.toByteString())
.serverId(linkComponents.serverId.toByteArray().toByteString())
.color(StorageSyncModels.localToRemoteUsernameColor(SignalStore.misc.usernameQrCodeColorScheme))
.build()
)
} else {
account.setUsernameLink(null)
getSubscriber(InAppPaymentSubscriberRecord.Type.DONATION)?.let {
safeSetSubscriber(it.subscriberId.bytes.toByteString(), it.currency.currencyCode)
}
getSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP)?.let {
safeSetBackupsSubscriber(it.subscriberId.bytes.toByteString(), it.currency.currencyCode)
}
safeSetPayments(SignalStore.payments.mobileCoinPaymentsEnabled(), Optional.ofNullable(SignalStore.payments.paymentsEntropy).map { obj: Entropy -> obj.bytes }.orElse(null))
}
return SignalStorageRecord.forAccount(account.build())
return SignalStorageRecord.forAccount(accountRecord.toSignalAccountRecord(StorageId.forAccount(storageId)))
}
@JvmStatic
@@ -188,62 +195,56 @@ object StorageSyncHelper {
fun applyAccountStorageSyncUpdates(context: Context, self: Recipient, update: StorageRecordUpdate<SignalAccountRecord>, fetchProfile: Boolean) {
SignalDatabase.recipients.applyStorageSyncAccountUpdate(update)
TextSecurePreferences.setReadReceiptsEnabled(context, update.new.isReadReceiptsEnabled)
TextSecurePreferences.setTypingIndicatorsEnabled(context, update.new.isTypingIndicatorsEnabled)
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, update.new.isSealedSenderIndicatorsEnabled)
SignalStore.settings.isLinkPreviewsEnabled = update.new.isLinkPreviewsEnabled
SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode = if (update.new.isPhoneNumberUnlisted) PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE else PhoneNumberDiscoverabilityMode.DISCOVERABLE
SignalStore.phoneNumberPrivacy.phoneNumberSharingMode = StorageSyncModels.remoteToLocalPhoneNumberSharingMode(update.new.phoneNumberSharingMode)
SignalStore.settings.isPreferSystemContactPhotos = update.new.isPreferContactAvatars
SignalStore.payments.setEnabledAndEntropy(update.new.payments.isEnabled, Entropy.fromBytes(update.new.payments.entropy.orElse(null)))
SignalStore.settings.universalExpireTimer = update.new.universalExpireTimer
SignalStore.emoji.reactions = update.new.defaultReactions
SignalStore.inAppPayments.setDisplayBadgesOnProfile(update.new.isDisplayBadgesOnProfile)
SignalStore.settings.setKeepMutedChatsArchived(update.new.isKeepMutedChatsArchived)
SignalStore.story.userHasBeenNotifiedAboutStories = update.new.hasSetMyStoriesPrivacy()
SignalStore.story.userHasViewedOnboardingStory = update.new.hasViewedOnboardingStory()
SignalStore.story.isFeatureDisabled = update.new.isStoriesDisabled
SignalStore.story.userHasSeenGroupStoryEducationSheet = update.new.hasSeenGroupStoryEducationSheet()
SignalStore.uiHints.setHasCompletedUsernameOnboarding(update.new.hasCompletedUsernameOnboarding())
TextSecurePreferences.setReadReceiptsEnabled(context, update.new.proto.readReceipts)
TextSecurePreferences.setTypingIndicatorsEnabled(context, update.new.proto.typingIndicators)
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, update.new.proto.sealedSenderIndicators)
SignalStore.settings.isLinkPreviewsEnabled = update.new.proto.linkPreviews
SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode = if (update.new.proto.unlistedPhoneNumber) PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE else PhoneNumberDiscoverabilityMode.DISCOVERABLE
SignalStore.phoneNumberPrivacy.phoneNumberSharingMode = StorageSyncModels.remoteToLocalPhoneNumberSharingMode(update.new.proto.phoneNumberSharingMode)
SignalStore.settings.isPreferSystemContactPhotos = update.new.proto.preferContactAvatars
SignalStore.payments.setEnabledAndEntropy(update.new.proto.payments?.enabled == true, Entropy.fromBytes(update.new.proto.payments?.entropy?.toByteArray()))
SignalStore.settings.universalExpireTimer = update.new.proto.universalExpireTimer
SignalStore.emoji.reactions = update.new.proto.preferredReactionEmoji
SignalStore.inAppPayments.setDisplayBadgesOnProfile(update.new.proto.displayBadgesOnProfile)
SignalStore.settings.setKeepMutedChatsArchived(update.new.proto.keepMutedChatsArchived)
SignalStore.story.userHasBeenNotifiedAboutStories = update.new.proto.hasSetMyStoriesPrivacy
SignalStore.story.userHasViewedOnboardingStory = update.new.proto.hasViewedOnboardingStory
SignalStore.story.isFeatureDisabled = update.new.proto.storiesDisabled
SignalStore.story.userHasSeenGroupStoryEducationSheet = update.new.proto.hasSeenGroupStoryEducationSheet
SignalStore.uiHints.setHasCompletedUsernameOnboarding(update.new.proto.hasCompletedUsernameOnboarding)
if (update.new.storyViewReceiptsState == OptionalBool.UNSET) {
SignalStore.story.viewedReceiptsEnabled = update.new.isReadReceiptsEnabled
if (update.new.proto.storyViewReceiptsEnabled == OptionalBool.UNSET) {
SignalStore.story.viewedReceiptsEnabled = update.new.proto.readReceipts
} else {
SignalStore.story.viewedReceiptsEnabled = update.new.storyViewReceiptsState == OptionalBool.ENABLED
SignalStore.story.viewedReceiptsEnabled = update.new.proto.storyViewReceiptsEnabled == OptionalBool.ENABLED
}
if (update.new.storyViewReceiptsState == OptionalBool.UNSET) {
SignalStore.story.viewedReceiptsEnabled = update.new.isReadReceiptsEnabled
} else {
SignalStore.story.viewedReceiptsEnabled = update.new.storyViewReceiptsState == OptionalBool.ENABLED
}
val remoteSubscriber = StorageSyncModels.remoteToLocalSubscriber(update.new.subscriber, InAppPaymentSubscriberRecord.Type.DONATION)
val remoteSubscriber = StorageSyncModels.remoteToLocalSubscriber(update.new.proto.subscriberId, update.new.proto.subscriberCurrencyCode, InAppPaymentSubscriberRecord.Type.DONATION)
if (remoteSubscriber != null) {
setSubscriber(remoteSubscriber)
}
if (update.new.isSubscriptionManuallyCancelled && !update.old.isSubscriptionManuallyCancelled) {
if (update.new.proto.subscriptionManuallyCancelled && !update.old.proto.subscriptionManuallyCancelled) {
SignalStore.inAppPayments.updateLocalStateForManualCancellation(InAppPaymentSubscriberRecord.Type.DONATION)
}
if (fetchProfile && update.new.avatarUrlPath.isPresent) {
AppDependencies.jobManager.add(RetrieveProfileAvatarJob(self, update.new.avatarUrlPath.get()))
if (fetchProfile && update.new.proto.avatarUrlPath.isNotBlank()) {
AppDependencies.jobManager.add(RetrieveProfileAvatarJob(self, update.new.proto.avatarUrlPath))
}
if (update.new.username != update.old.username) {
SignalStore.account.username = update.new.username
if (update.new.proto.username != update.old.proto.username) {
SignalStore.account.username = update.new.proto.username
SignalStore.account.usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
SignalStore.account.usernameSyncErrorCount = 0
}
if (update.new.usernameLink != null) {
if (update.new.proto.usernameLink != null) {
SignalStore.account.usernameLink = UsernameLinkComponents(
update.new.usernameLink!!.entropy.toByteArray(),
UuidUtil.parseOrThrow(update.new.usernameLink!!.serverId.toByteArray())
update.new.proto.usernameLink!!.entropy.toByteArray(),
UuidUtil.parseOrThrow(update.new.proto.usernameLink!!.serverId.toByteArray())
)
SignalStore.misc.usernameQrCodeColorScheme = StorageSyncModels.remoteToLocalUsernameColor(update.new.usernameLink!!.color)
SignalStore.misc.usernameQrCodeColorScheme = StorageSyncModels.remoteToLocalUsernameColor(update.new.proto.usernameLink!!.color)
}
}

View File

@@ -1,5 +1,8 @@
package org.thoughtcrime.securesms.storage
import okio.ByteString
import okio.ByteString.Companion.toByteString
import org.signal.core.util.isNotEmpty
import org.signal.libsignal.zkgroup.groups.GroupMasterKey
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.UsernameQrCodeColorScheme
import org.thoughtcrime.securesms.database.GroupTable.ShowAsStoryState
@@ -16,7 +19,6 @@ import org.thoughtcrime.securesms.database.model.databaseprotos.InAppPaymentData
import org.thoughtcrime.securesms.keyvalue.PhoneNumberPrivacyValues
import org.thoughtcrime.securesms.recipients.Recipient
import org.whispersystems.signalservice.api.push.SignalServiceAddress
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
import org.whispersystems.signalservice.api.storage.SignalCallLinkRecord
import org.whispersystems.signalservice.api.storage.SignalContactRecord
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record
@@ -82,18 +84,33 @@ object StorageSyncModels {
}
@JvmStatic
fun localToRemotePinnedConversations(records: List<RecipientRecord>): List<SignalAccountRecord.PinnedConversation> {
fun localToRemotePinnedConversations(records: List<RecipientRecord>): List<AccountRecord.PinnedConversation> {
return records
.filter { it.recipientType == RecipientType.GV1 || it.recipientType == RecipientType.GV2 || it.registered == RecipientTable.RegisteredState.REGISTERED }
.map { localToRemotePinnedConversation(it) }
}
@JvmStatic
private fun localToRemotePinnedConversation(settings: RecipientRecord): SignalAccountRecord.PinnedConversation {
private fun localToRemotePinnedConversation(settings: RecipientRecord): AccountRecord.PinnedConversation {
return when (settings.recipientType) {
RecipientType.INDIVIDUAL -> SignalAccountRecord.PinnedConversation.forContact(SignalServiceAddress(settings.serviceId, settings.e164))
RecipientType.GV1 -> SignalAccountRecord.PinnedConversation.forGroupV1(settings.groupId!!.requireV1().decodedId)
RecipientType.GV2 -> SignalAccountRecord.PinnedConversation.forGroupV2(settings.syncExtras.groupMasterKey!!.serialize())
RecipientType.INDIVIDUAL -> {
AccountRecord.PinnedConversation(
contact = AccountRecord.PinnedConversation.Contact(
serviceId = settings.serviceId?.toString() ?: "",
e164 = settings.e164 ?: ""
)
)
}
RecipientType.GV1 -> {
AccountRecord.PinnedConversation(
legacyGroupId = settings.groupId!!.requireV1().decodedId.toByteString()
)
}
RecipientType.GV2 -> {
AccountRecord.PinnedConversation(
groupMasterKey = settings.syncExtras.groupMasterKey!!.serialize().toByteString()
)
}
else -> throw AssertionError("Unexpected group type!")
}
}
@@ -271,33 +288,23 @@ object StorageSyncModels {
}
}
/**
* TODO - need to store the subscriber type
*/
fun localToRemoteSubscriber(subscriber: InAppPaymentSubscriberRecord?): SignalAccountRecord.Subscriber {
return if (subscriber == null) {
SignalAccountRecord.Subscriber(null, null)
} else {
SignalAccountRecord.Subscriber(subscriber.currency.currencyCode, subscriber.subscriberId.bytes)
}
}
fun remoteToLocalSubscriber(
subscriber: SignalAccountRecord.Subscriber,
subscriberId: ByteString,
subscriberCurrencyCode: String,
type: InAppPaymentSubscriberRecord.Type
): InAppPaymentSubscriberRecord? {
if (subscriber.id.isPresent) {
val subscriberId = SubscriberId.fromBytes(subscriber.id.get())
if (subscriberId.isNotEmpty()) {
val subscriberId = SubscriberId.fromBytes(subscriberId.toByteArray())
val localSubscriberRecord = inAppPaymentSubscribers.getBySubscriberId(subscriberId)
val requiresCancel = localSubscriberRecord != null && localSubscriberRecord.requiresCancel
val paymentMethodType = localSubscriberRecord?.paymentMethodType ?: InAppPaymentData.PaymentMethodType.UNKNOWN
val currency: Currency
if (subscriber.currencyCode.isEmpty) {
if (subscriberCurrencyCode.isBlank()) {
return null
} else {
try {
currency = Currency.getInstance(subscriber.currencyCode.get())
currency = Currency.getInstance(subscriberCurrencyCode)
} catch (e: IllegalArgumentException) {
return null
}

View File

@@ -177,7 +177,7 @@ public final class StorageSyncValidations {
}
}
if (insert.getAccount().isPresent() && !insert.getAccount().get().getProfileKey().isPresent()) {
if (insert.getAccount().isPresent() && insert.getAccount().get().getProto().profileKey.size() == 0) {
Log.w(TAG, "Uploading a null profile key in our AccountRecord!");
}
}