Remove cruft around SignalAccountRecord.
This commit is contained in:
parent
5e8318d63f
commit
ae37c4019f
30 changed files with 536 additions and 1523 deletions
|
@ -37,7 +37,7 @@ class RecipientTableTest_applyStorageSyncContactUpdate {
|
|||
val oldRecord: SignalContactRecord = StorageSyncModels.localToRemoteRecord(SignalDatabase.recipients.getRecordForSync(harness.others[0])!!).contact.get()
|
||||
|
||||
val newProto = oldRecord
|
||||
.toProto()
|
||||
.proto
|
||||
.newBuilder()
|
||||
.identityState(ContactRecord.IdentityState.DEFAULT)
|
||||
.build()
|
||||
|
|
|
@ -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()
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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());
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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. */
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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++
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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!");
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.thoughtcrime.securesms.storage
|
||||
|
||||
import junit.framework.TestCase.assertEquals
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.junit.Test
|
||||
import org.thoughtcrime.securesms.util.Util
|
||||
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
|
||||
import org.whispersystems.signalservice.api.storage.SignalContactRecord
|
||||
import org.whispersystems.signalservice.api.storage.StorageId
|
||||
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord
|
||||
import org.whispersystems.signalservice.internal.storage.protos.ContactRecord
|
||||
|
||||
class StorageRecordTest {
|
||||
|
||||
@Test
|
||||
fun `describeDiff - general test`() {
|
||||
val a = SignalAccountRecord(
|
||||
StorageId.forAccount(Util.getSecretBytes(16)),
|
||||
AccountRecord(
|
||||
profileKey = ByteString.EMPTY,
|
||||
givenName = "First",
|
||||
familyName = "Last"
|
||||
)
|
||||
)
|
||||
|
||||
val b = SignalAccountRecord(
|
||||
StorageId.forAccount(Util.getSecretBytes(16)),
|
||||
AccountRecord(
|
||||
profileKey = Util.getSecretBytes(16).toByteString(),
|
||||
givenName = "First",
|
||||
familyName = "LastB"
|
||||
)
|
||||
)
|
||||
|
||||
assertEquals("Some fields differ: familyName, id, profileKey", a.describeDiff(b))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `describeDiff - different class`() {
|
||||
val a = SignalAccountRecord(
|
||||
StorageId.forAccount(Util.getSecretBytes(16)),
|
||||
AccountRecord()
|
||||
)
|
||||
|
||||
val b = SignalContactRecord(
|
||||
StorageId.forAccount(Util.getSecretBytes(16)),
|
||||
ContactRecord()
|
||||
)
|
||||
|
||||
assertEquals("Classes are different!", a.describeDiff(b))
|
||||
}
|
||||
}
|
|
@ -180,7 +180,7 @@ public final class StorageSyncHelperTest {
|
|||
.setProfileGivenName(profileName);
|
||||
}
|
||||
|
||||
private static <E extends SignalRecord> StorageRecordUpdate<E> update(E oldRecord, E newRecord) {
|
||||
private static <E extends SignalRecord<?>> StorageRecordUpdate<E> update(E oldRecord, E newRecord) {
|
||||
return new StorageRecordUpdate<>(oldRecord, newRecord);
|
||||
}
|
||||
|
||||
|
|
|
@ -32,6 +32,14 @@ fun ByteString?.isNullOrEmpty(): Boolean {
|
|||
return this == null || this.size == 0
|
||||
}
|
||||
|
||||
fun ByteString.nullIfEmpty(): ByteString? {
|
||||
return if (this.isEmpty()) {
|
||||
null
|
||||
} else {
|
||||
this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Performs the common pattern of attempting to decode a serialized proto and returning null if it fails to decode.
|
||||
*/
|
||||
|
|
|
@ -33,6 +33,7 @@ java {
|
|||
tasks.withType<KotlinCompile>().configureEach {
|
||||
kotlinOptions {
|
||||
jvmTarget = signalKotlinJvmTarget
|
||||
freeCompilerArgs = listOf("-Xjvm-default=all")
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -0,0 +1,63 @@
|
|||
/*
|
||||
* Copyright 2024 Signal Messenger, LLC
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import okio.ByteString
|
||||
import okio.ByteString.Companion.toByteString
|
||||
import org.signal.core.util.isNotEmpty
|
||||
import org.whispersystems.signalservice.api.payments.PaymentsConstants
|
||||
import org.whispersystems.signalservice.api.push.ServiceId
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
||||
import org.whispersystems.signalservice.api.storage.StorageRecordProtoUtil.defaultAccountRecord
|
||||
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord
|
||||
import org.whispersystems.signalservice.internal.storage.protos.Payments
|
||||
|
||||
fun AccountRecord.Builder.safeSetPayments(enabled: Boolean, entropy: ByteArray?): AccountRecord.Builder {
|
||||
val paymentsBuilder = Payments.Builder()
|
||||
val entropyPresent = entropy != null && entropy.size == PaymentsConstants.PAYMENTS_ENTROPY_LENGTH
|
||||
|
||||
paymentsBuilder.enabled(enabled && entropyPresent)
|
||||
|
||||
if (entropyPresent) {
|
||||
paymentsBuilder.entropy(entropy!!.toByteString())
|
||||
}
|
||||
|
||||
this.payments = paymentsBuilder.build()
|
||||
|
||||
return this
|
||||
}
|
||||
fun AccountRecord.Builder.safeSetSubscriber(subscriberId: ByteString, subscriberCurrencyCode: String): AccountRecord.Builder {
|
||||
if (subscriberId.isNotEmpty() && subscriberId.size == 32 && subscriberCurrencyCode.isNotBlank()) {
|
||||
this.subscriberId = subscriberId
|
||||
this.subscriberCurrencyCode = subscriberCurrencyCode
|
||||
} else {
|
||||
this.subscriberId = defaultAccountRecord.subscriberId
|
||||
this.subscriberCurrencyCode = defaultAccountRecord.subscriberCurrencyCode
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun AccountRecord.Builder.safeSetBackupsSubscriber(subscriberId: ByteString, subscriberCurrencyCode: String): AccountRecord.Builder {
|
||||
if (subscriberId.isNotEmpty() && subscriberId.size == 32 && subscriberCurrencyCode.isNotBlank()) {
|
||||
this.backupsSubscriberId = subscriberId
|
||||
this.backupsSubscriberCurrencyCode = subscriberCurrencyCode
|
||||
} else {
|
||||
this.backupsSubscriberId = defaultAccountRecord.backupsSubscriberId
|
||||
this.backupsSubscriberCurrencyCode = defaultAccountRecord.backupsSubscriberCurrencyCode
|
||||
}
|
||||
|
||||
return this
|
||||
}
|
||||
|
||||
fun AccountRecord.Builder.toSignalAccountRecord(storageId: StorageId): SignalAccountRecord {
|
||||
return SignalAccountRecord(storageId, this.build())
|
||||
}
|
||||
|
||||
fun AccountRecord.PinnedConversation.Contact.toSignalServiceAddress(): SignalServiceAddress {
|
||||
val serviceId = ServiceId.parseOrNull(this.serviceId)
|
||||
return SignalServiceAddress(serviceId, this.e164)
|
||||
}
|
|
@ -1,769 +0,0 @@
|
|||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.signal.core.util.ProtoUtil;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.whispersystems.signalservice.api.payments.PaymentsConstants;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
|
||||
import org.whispersystems.signalservice.api.util.OptionalUtil;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
|
||||
import org.whispersystems.signalservice.internal.storage.protos.OptionalBool;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
import okio.ByteString;
|
||||
|
||||
public final class SignalAccountRecord implements SignalRecord {
|
||||
|
||||
private static final String TAG = SignalAccountRecord.class.getSimpleName();
|
||||
|
||||
private final StorageId id;
|
||||
private final AccountRecord proto;
|
||||
private final boolean hasUnknownFields;
|
||||
|
||||
private final Optional<String> givenName;
|
||||
private final Optional<String> familyName;
|
||||
private final Optional<String> avatarUrlPath;
|
||||
private final Optional<byte[]> profileKey;
|
||||
private final List<PinnedConversation> pinnedConversations;
|
||||
private final Payments payments;
|
||||
private final List<String> defaultReactions;
|
||||
private final Subscriber subscriber;
|
||||
private final Subscriber backupsSubscriber;
|
||||
|
||||
public SignalAccountRecord(StorageId id, AccountRecord proto) {
|
||||
this.id = id;
|
||||
this.proto = proto;
|
||||
this.hasUnknownFields = ProtoUtil.hasUnknownFields(proto);
|
||||
|
||||
this.givenName = OptionalUtil.absentIfEmpty(proto.givenName);
|
||||
this.familyName = OptionalUtil.absentIfEmpty(proto.familyName);
|
||||
this.profileKey = OptionalUtil.absentIfEmpty(proto.profileKey);
|
||||
this.avatarUrlPath = OptionalUtil.absentIfEmpty(proto.avatarUrlPath);
|
||||
this.pinnedConversations = new ArrayList<>(proto.pinnedConversations.size());
|
||||
this.defaultReactions = new ArrayList<>(proto.preferredReactionEmoji);
|
||||
this.subscriber = new Subscriber(proto.subscriberCurrencyCode, proto.subscriberId.toByteArray());
|
||||
this.backupsSubscriber = new Subscriber(proto.backupsSubscriberCurrencyCode, proto.backupsSubscriberId.toByteArray());
|
||||
|
||||
if (proto.payments != null) {
|
||||
this.payments = new Payments(proto.payments.enabled, OptionalUtil.absentIfEmpty(proto.payments.entropy));
|
||||
} else {
|
||||
this.payments = new Payments(false, Optional.empty());
|
||||
}
|
||||
|
||||
for (AccountRecord.PinnedConversation conversation : proto.pinnedConversations) {
|
||||
pinnedConversations.add(PinnedConversation.fromRemote(conversation));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignalStorageRecord asStorageRecord() {
|
||||
return SignalStorageRecord.forAccount(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describeDiff(SignalRecord other) {
|
||||
if (other instanceof SignalAccountRecord) {
|
||||
SignalAccountRecord that = (SignalAccountRecord) other;
|
||||
List<String> diff = new LinkedList<>();
|
||||
|
||||
if (!Arrays.equals(this.id.getRaw(), that.id.getRaw())) {
|
||||
diff.add("ID");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.givenName, that.givenName)) {
|
||||
diff.add("GivenName");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.familyName, that.familyName)) {
|
||||
diff.add("FamilyName");
|
||||
}
|
||||
|
||||
if (!OptionalUtil.byteArrayEquals(this.profileKey, that.profileKey)) {
|
||||
diff.add("ProfileKey");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.avatarUrlPath, that.avatarUrlPath)) {
|
||||
diff.add("AvatarUrlPath");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isNoteToSelfArchived(), that.isNoteToSelfArchived())) {
|
||||
diff.add("NoteToSelfArchived");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isNoteToSelfForcedUnread(), that.isNoteToSelfForcedUnread())) {
|
||||
diff.add("NoteToSelfForcedUnread");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isReadReceiptsEnabled(), that.isReadReceiptsEnabled())) {
|
||||
diff.add("ReadReceipts");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isTypingIndicatorsEnabled(), that.isTypingIndicatorsEnabled())) {
|
||||
diff.add("TypingIndicators");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isSealedSenderIndicatorsEnabled(), that.isSealedSenderIndicatorsEnabled())) {
|
||||
diff.add("SealedSenderIndicators");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isLinkPreviewsEnabled(), that.isLinkPreviewsEnabled())) {
|
||||
diff.add("LinkPreviews");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getPhoneNumberSharingMode(), that.getPhoneNumberSharingMode())) {
|
||||
diff.add("PhoneNumberSharingMode");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isPhoneNumberUnlisted(), that.isPhoneNumberUnlisted())) {
|
||||
diff.add("PhoneNumberUnlisted");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.pinnedConversations, that.pinnedConversations)) {
|
||||
diff.add("PinnedConversations");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isPreferContactAvatars(), that.isPreferContactAvatars())) {
|
||||
diff.add("PreferContactAvatars");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.payments, that.payments)) {
|
||||
diff.add("Payments");
|
||||
}
|
||||
|
||||
if (this.getUniversalExpireTimer() != that.getUniversalExpireTimer()) {
|
||||
diff.add("UniversalExpireTimer");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isPrimarySendsSms(), that.isPrimarySendsSms())) {
|
||||
diff.add("PrimarySendsSms");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getE164(), that.getE164())) {
|
||||
diff.add("E164");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getDefaultReactions(), that.getDefaultReactions())) {
|
||||
diff.add("DefaultReactions");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) {
|
||||
diff.add("UnknownFields");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getSubscriber(), that.getSubscriber())) {
|
||||
diff.add("Subscriber");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isDisplayBadgesOnProfile(), that.isDisplayBadgesOnProfile())) {
|
||||
diff.add("DisplayBadgesOnProfile");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isSubscriptionManuallyCancelled(), that.isSubscriptionManuallyCancelled())) {
|
||||
diff.add("SubscriptionManuallyCancelled");
|
||||
}
|
||||
|
||||
if (isKeepMutedChatsArchived() != that.isKeepMutedChatsArchived()) {
|
||||
diff.add("KeepMutedChatsArchived");
|
||||
}
|
||||
|
||||
if (hasSetMyStoriesPrivacy() != that.hasSetMyStoriesPrivacy()) {
|
||||
diff.add("HasSetMyStoryPrivacy");
|
||||
}
|
||||
|
||||
if (hasViewedOnboardingStory() != that.hasViewedOnboardingStory()) {
|
||||
diff.add("HasViewedOnboardingStory");
|
||||
}
|
||||
|
||||
if (isStoriesDisabled() != that.isStoriesDisabled()) {
|
||||
diff.add("StoriesDisabled");
|
||||
}
|
||||
|
||||
if (getStoryViewReceiptsState() != that.getStoryViewReceiptsState()) {
|
||||
diff.add("StoryViewedReceipts");
|
||||
}
|
||||
|
||||
if (hasSeenGroupStoryEducationSheet() != that.hasSeenGroupStoryEducationSheet()) {
|
||||
diff.add("HasSeenGroupStoryEducationSheet");
|
||||
}
|
||||
|
||||
if (!Objects.equals(getUsername(), that.getUsername())) {
|
||||
diff.add("Username");
|
||||
}
|
||||
|
||||
if (hasCompletedUsernameOnboarding() != that.hasCompletedUsernameOnboarding()) {
|
||||
diff.add("HasCompletedUsernameOnboarding");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getBackupsSubscriber(), that.getBackupsSubscriber())) {
|
||||
diff.add("BackupsSubscriber");
|
||||
}
|
||||
|
||||
return diff.toString();
|
||||
} else {
|
||||
return "Different class. " + getClass().getSimpleName() + " | " + other.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
public boolean hasUnknownFields() {
|
||||
return hasUnknownFields;
|
||||
}
|
||||
|
||||
public byte[] serializeUnknownFields() {
|
||||
return hasUnknownFields ? proto.encode() : null;
|
||||
}
|
||||
|
||||
public Optional<String> getGivenName() {
|
||||
return givenName;
|
||||
}
|
||||
|
||||
public Optional<String> getFamilyName() {
|
||||
return familyName;
|
||||
}
|
||||
|
||||
public Optional<byte[]> getProfileKey() {
|
||||
return profileKey;
|
||||
}
|
||||
|
||||
public Optional<String> getAvatarUrlPath() {
|
||||
return avatarUrlPath;
|
||||
}
|
||||
|
||||
public boolean isNoteToSelfArchived() {
|
||||
return proto.noteToSelfArchived;
|
||||
}
|
||||
|
||||
public boolean isNoteToSelfForcedUnread() {
|
||||
return proto.noteToSelfMarkedUnread;
|
||||
}
|
||||
|
||||
public boolean isReadReceiptsEnabled() {
|
||||
return proto.readReceipts;
|
||||
}
|
||||
|
||||
public boolean isTypingIndicatorsEnabled() {
|
||||
return proto.typingIndicators;
|
||||
}
|
||||
|
||||
public boolean isSealedSenderIndicatorsEnabled() {
|
||||
return proto.sealedSenderIndicators;
|
||||
}
|
||||
|
||||
public boolean isLinkPreviewsEnabled() {
|
||||
return proto.linkPreviews;
|
||||
}
|
||||
|
||||
public AccountRecord.PhoneNumberSharingMode getPhoneNumberSharingMode() {
|
||||
return proto.phoneNumberSharingMode;
|
||||
}
|
||||
|
||||
public boolean isPhoneNumberUnlisted() {
|
||||
return proto.unlistedPhoneNumber;
|
||||
}
|
||||
|
||||
public List<PinnedConversation> getPinnedConversations() {
|
||||
return pinnedConversations;
|
||||
}
|
||||
|
||||
public boolean isPreferContactAvatars() {
|
||||
return proto.preferContactAvatars;
|
||||
}
|
||||
|
||||
public Payments getPayments() {
|
||||
return payments;
|
||||
}
|
||||
|
||||
public int getUniversalExpireTimer() {
|
||||
return proto.universalExpireTimer;
|
||||
}
|
||||
|
||||
public boolean isPrimarySendsSms() {
|
||||
return proto.primarySendsSms;
|
||||
}
|
||||
|
||||
public String getE164() {
|
||||
return proto.e164;
|
||||
}
|
||||
|
||||
public List<String> getDefaultReactions() {
|
||||
return defaultReactions;
|
||||
}
|
||||
|
||||
public Subscriber getSubscriber() {
|
||||
return subscriber;
|
||||
}
|
||||
|
||||
public Subscriber getBackupsSubscriber() {
|
||||
return backupsSubscriber;
|
||||
}
|
||||
|
||||
public boolean isDisplayBadgesOnProfile() {
|
||||
return proto.displayBadgesOnProfile;
|
||||
}
|
||||
|
||||
public boolean isSubscriptionManuallyCancelled() {
|
||||
return proto.subscriptionManuallyCancelled;
|
||||
}
|
||||
|
||||
public boolean isKeepMutedChatsArchived() {
|
||||
return proto.keepMutedChatsArchived;
|
||||
}
|
||||
|
||||
public boolean hasSetMyStoriesPrivacy() {
|
||||
return proto.hasSetMyStoriesPrivacy;
|
||||
}
|
||||
|
||||
public boolean hasViewedOnboardingStory() {
|
||||
return proto.hasViewedOnboardingStory;
|
||||
}
|
||||
|
||||
public boolean isStoriesDisabled() {
|
||||
return proto.storiesDisabled;
|
||||
}
|
||||
|
||||
public OptionalBool getStoryViewReceiptsState() {
|
||||
return proto.storyViewReceiptsEnabled;
|
||||
}
|
||||
|
||||
public boolean hasSeenGroupStoryEducationSheet() {
|
||||
return proto.hasSeenGroupStoryEducationSheet;
|
||||
}
|
||||
|
||||
public boolean hasCompletedUsernameOnboarding() {
|
||||
return proto.hasCompletedUsernameOnboarding;
|
||||
}
|
||||
|
||||
public @Nullable String getUsername() {
|
||||
return proto.username;
|
||||
}
|
||||
|
||||
public @Nullable AccountRecord.UsernameLink getUsernameLink() {
|
||||
return proto.usernameLink;
|
||||
}
|
||||
|
||||
public AccountRecord toProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
SignalAccountRecord that = (SignalAccountRecord) o;
|
||||
return id.equals(that.id) &&
|
||||
proto.equals(that.proto);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(id, proto);
|
||||
}
|
||||
|
||||
public static class PinnedConversation {
|
||||
private final Optional<SignalServiceAddress> contact;
|
||||
private final Optional<byte[]> groupV1Id;
|
||||
private final Optional<byte[]> groupV2MasterKey;
|
||||
|
||||
private PinnedConversation(Optional<SignalServiceAddress> contact, Optional<byte[]> groupV1Id, Optional<byte[]> groupV2MasterKey) {
|
||||
this.contact = contact;
|
||||
this.groupV1Id = groupV1Id;
|
||||
this.groupV2MasterKey = groupV2MasterKey;
|
||||
}
|
||||
|
||||
public static PinnedConversation forContact(SignalServiceAddress address) {
|
||||
return new PinnedConversation(Optional.of(address), Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
public static PinnedConversation forGroupV1(byte[] groupId) {
|
||||
return new PinnedConversation(Optional.empty(), Optional.of(groupId), Optional.empty());
|
||||
}
|
||||
|
||||
public static PinnedConversation forGroupV2(byte[] masterKey) {
|
||||
return new PinnedConversation(Optional.empty(), Optional.empty(), Optional.of(masterKey));
|
||||
}
|
||||
|
||||
private static PinnedConversation forEmpty() {
|
||||
return new PinnedConversation(Optional.empty(), Optional.empty(), Optional.empty());
|
||||
}
|
||||
|
||||
static PinnedConversation fromRemote(AccountRecord.PinnedConversation remote) {
|
||||
if (remote.contact != null) {
|
||||
ServiceId serviceId = ServiceId.parseOrNull(remote.contact.serviceId);
|
||||
if (serviceId != null) {
|
||||
return forContact(new SignalServiceAddress(serviceId, remote.contact.e164));
|
||||
} else {
|
||||
Log.w(TAG, "Bad serviceId on pinned contact! Length: " + remote.contact.serviceId);
|
||||
return PinnedConversation.forEmpty();
|
||||
}
|
||||
} else if (remote.legacyGroupId != null && remote.legacyGroupId.size() > 0) {
|
||||
return forGroupV1(remote.legacyGroupId.toByteArray());
|
||||
} else if (remote.groupMasterKey != null && remote.groupMasterKey.size() > 0) {
|
||||
return forGroupV2(remote.groupMasterKey.toByteArray());
|
||||
} else {
|
||||
return PinnedConversation.forEmpty();
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<SignalServiceAddress> getContact() {
|
||||
return contact;
|
||||
}
|
||||
|
||||
public Optional<byte[]> getGroupV1Id() {
|
||||
return groupV1Id;
|
||||
}
|
||||
|
||||
public Optional<byte[]> getGroupV2MasterKey() {
|
||||
return groupV2MasterKey;
|
||||
}
|
||||
|
||||
public boolean isValid() {
|
||||
return contact.isPresent() || groupV1Id.isPresent() || groupV2MasterKey.isPresent();
|
||||
}
|
||||
|
||||
private AccountRecord.PinnedConversation toRemote() {
|
||||
if (contact.isPresent()) {
|
||||
AccountRecord.PinnedConversation.Contact.Builder contactBuilder = new AccountRecord.PinnedConversation.Contact.Builder();
|
||||
|
||||
contactBuilder.serviceId(contact.get().getServiceId().toString());
|
||||
|
||||
if (contact.get().getNumber().isPresent()) {
|
||||
contactBuilder.e164(contact.get().getNumber().get());
|
||||
}
|
||||
return new AccountRecord.PinnedConversation.Builder().contact(contactBuilder.build()).build();
|
||||
} else if (groupV1Id.isPresent()) {
|
||||
return new AccountRecord.PinnedConversation.Builder().legacyGroupId(ByteString.of(groupV1Id.get())).build();
|
||||
} else if (groupV2MasterKey.isPresent()) {
|
||||
return new AccountRecord.PinnedConversation.Builder().groupMasterKey(ByteString.of(groupV2MasterKey.get())).build();
|
||||
} else {
|
||||
return new AccountRecord.PinnedConversation.Builder().build();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
PinnedConversation that = (PinnedConversation) o;
|
||||
return contact.equals(that.contact) &&
|
||||
groupV1Id.equals(that.groupV1Id) &&
|
||||
groupV2MasterKey.equals(that.groupV2MasterKey);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(contact, groupV1Id, groupV2MasterKey);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Subscriber {
|
||||
private final Optional<String> currencyCode;
|
||||
private final Optional<byte[]> id;
|
||||
|
||||
public Subscriber(String currencyCode, byte[] id) {
|
||||
if (currencyCode != null && id != null && id.length == 32) {
|
||||
this.currencyCode = Optional.of(currencyCode);
|
||||
this.id = Optional.of(id);
|
||||
} else {
|
||||
this.currencyCode = Optional.empty();
|
||||
this.id = Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<String> getCurrencyCode() {
|
||||
return currencyCode;
|
||||
}
|
||||
|
||||
public Optional<byte[]> getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
final Subscriber that = (Subscriber) o;
|
||||
return Objects.equals(currencyCode, that.currencyCode) && OptionalUtil.byteArrayEquals(id, that.id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(currencyCode, id);
|
||||
}
|
||||
}
|
||||
|
||||
public static class Payments {
|
||||
private static final String TAG = Payments.class.getSimpleName();
|
||||
|
||||
private final boolean enabled;
|
||||
private final Optional<byte[]> entropy;
|
||||
|
||||
public Payments(boolean enabled, Optional<byte[]> entropy) {
|
||||
byte[] entropyBytes = entropy.orElse(null);
|
||||
if (entropyBytes != null && entropyBytes.length != PaymentsConstants.PAYMENTS_ENTROPY_LENGTH) {
|
||||
Log.w(TAG, "Blocked entropy of length " + entropyBytes.length);
|
||||
entropyBytes = null;
|
||||
}
|
||||
this.entropy = Optional.ofNullable(entropyBytes);
|
||||
this.enabled = enabled && this.entropy.isPresent();
|
||||
}
|
||||
|
||||
public boolean isEnabled() {
|
||||
return enabled;
|
||||
}
|
||||
|
||||
public Optional<byte[]> getEntropy() {
|
||||
return entropy;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
if (o == null || getClass() != o.getClass()) return false;
|
||||
Payments payments = (Payments) o;
|
||||
return enabled == payments.enabled &&
|
||||
OptionalUtil.byteArrayEquals(entropy, payments.entropy);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(enabled, entropy);
|
||||
}
|
||||
}
|
||||
|
||||
public static final class Builder {
|
||||
private final StorageId id;
|
||||
private final AccountRecord.Builder builder;
|
||||
|
||||
public Builder(byte[] rawId, byte[] serializedUnknowns) {
|
||||
this.id = StorageId.forAccount(rawId);
|
||||
|
||||
if (serializedUnknowns != null) {
|
||||
this.builder = parseUnknowns(serializedUnknowns);
|
||||
} else {
|
||||
this.builder = new AccountRecord.Builder();
|
||||
}
|
||||
}
|
||||
|
||||
public Builder setGivenName(String givenName) {
|
||||
builder.givenName(givenName == null ? "" : givenName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setFamilyName(String familyName) {
|
||||
builder.familyName(familyName == null ? "" : familyName);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setProfileKey(byte[] profileKey) {
|
||||
builder.profileKey(profileKey == null ? ByteString.EMPTY : ByteString.of(profileKey));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setAvatarUrlPath(String urlPath) {
|
||||
builder.avatarUrlPath(urlPath == null ? "" : urlPath);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNoteToSelfArchived(boolean archived) {
|
||||
builder.noteToSelfArchived(archived);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setNoteToSelfForcedUnread(boolean forcedUnread) {
|
||||
builder.noteToSelfMarkedUnread(forcedUnread);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setReadReceiptsEnabled(boolean enabled) {
|
||||
builder.readReceipts(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setTypingIndicatorsEnabled(boolean enabled) {
|
||||
builder.typingIndicators(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSealedSenderIndicatorsEnabled(boolean enabled) {
|
||||
builder.sealedSenderIndicators(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setLinkPreviewsEnabled(boolean enabled) {
|
||||
builder.linkPreviews(enabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPhoneNumberSharingMode(AccountRecord.PhoneNumberSharingMode mode) {
|
||||
builder.phoneNumberSharingMode(mode);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUnlistedPhoneNumber(boolean unlisted) {
|
||||
builder.unlistedPhoneNumber(unlisted);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPinnedConversations(List<PinnedConversation> pinnedConversations) {
|
||||
builder.pinnedConversations(pinnedConversations.stream().map(PinnedConversation::toRemote).collect(Collectors.toList()));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPreferContactAvatars(boolean preferContactAvatars) {
|
||||
builder.preferContactAvatars(preferContactAvatars);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPayments(boolean enabled, byte[] entropy) {
|
||||
org.whispersystems.signalservice.internal.storage.protos.Payments.Builder paymentsBuilder = new org.whispersystems.signalservice.internal.storage.protos.Payments.Builder();
|
||||
|
||||
boolean entropyPresent = entropy != null && entropy.length == PaymentsConstants.PAYMENTS_ENTROPY_LENGTH;
|
||||
|
||||
paymentsBuilder.enabled(enabled && entropyPresent);
|
||||
|
||||
if (entropyPresent) {
|
||||
paymentsBuilder.entropy(ByteString.of(entropy));
|
||||
}
|
||||
|
||||
builder.payments(paymentsBuilder.build());
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUniversalExpireTimer(int timer) {
|
||||
builder.universalExpireTimer(timer);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setPrimarySendsSms(boolean primarySendsSms) {
|
||||
builder.primarySendsSms(primarySendsSms);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setE164(String e164) {
|
||||
builder.e164(e164);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDefaultReactions(List<String> defaultReactions) {
|
||||
builder.preferredReactionEmoji(new ArrayList<>(defaultReactions));
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSubscriber(Subscriber subscriber) {
|
||||
if (subscriber.id.isPresent() && subscriber.currencyCode.isPresent()) {
|
||||
builder.subscriberId(ByteString.of(subscriber.id.get()));
|
||||
builder.subscriberCurrencyCode(subscriber.currencyCode.get());
|
||||
} else {
|
||||
builder.subscriberId(StorageRecordProtoUtil.getDefaultAccountRecord().subscriberId);
|
||||
builder.subscriberCurrencyCode(StorageRecordProtoUtil.getDefaultAccountRecord().subscriberCurrencyCode);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setBackupsSubscriber(Subscriber subscriber) {
|
||||
if (subscriber.id.isPresent() && subscriber.currencyCode.isPresent()) {
|
||||
builder.backupsSubscriberId(ByteString.of(subscriber.id.get()));
|
||||
builder.backupsSubscriberCurrencyCode(subscriber.currencyCode.get());
|
||||
} else {
|
||||
builder.backupsSubscriberId(StorageRecordProtoUtil.getDefaultAccountRecord().subscriberId);
|
||||
builder.backupsSubscriberCurrencyCode(StorageRecordProtoUtil.getDefaultAccountRecord().subscriberCurrencyCode);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setDisplayBadgesOnProfile(boolean displayBadgesOnProfile) {
|
||||
builder.displayBadgesOnProfile(displayBadgesOnProfile);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setSubscriptionManuallyCancelled(boolean subscriptionManuallyCancelled) {
|
||||
builder.subscriptionManuallyCancelled(subscriptionManuallyCancelled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setKeepMutedChatsArchived(boolean keepMutedChatsArchived) {
|
||||
builder.keepMutedChatsArchived(keepMutedChatsArchived);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHasSetMyStoriesPrivacy(boolean hasSetMyStoriesPrivacy) {
|
||||
builder.hasSetMyStoriesPrivacy(hasSetMyStoriesPrivacy);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHasViewedOnboardingStory(boolean hasViewedOnboardingStory) {
|
||||
builder.hasViewedOnboardingStory(hasViewedOnboardingStory);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setStoriesDisabled(boolean storiesDisabled) {
|
||||
builder.storiesDisabled(storiesDisabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setStoryViewReceiptsState(OptionalBool storyViewedReceiptsEnabled) {
|
||||
builder.storyViewReceiptsEnabled(storyViewedReceiptsEnabled);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHasSeenGroupStoryEducationSheet(boolean hasSeenGroupStoryEducationSheet) {
|
||||
builder.hasSeenGroupStoryEducationSheet(hasSeenGroupStoryEducationSheet);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setHasCompletedUsernameOnboarding(boolean hasCompletedUsernameOnboarding) {
|
||||
builder.hasCompletedUsernameOnboarding(hasCompletedUsernameOnboarding);
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUsername(@Nullable String username) {
|
||||
if (username == null || username.isEmpty()) {
|
||||
builder.username(StorageRecordProtoUtil.getDefaultAccountRecord().username);
|
||||
} else {
|
||||
builder.username(username);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder setUsernameLink(@Nullable AccountRecord.UsernameLink link) {
|
||||
if (link == null) {
|
||||
builder.usernameLink(StorageRecordProtoUtil.getDefaultAccountRecord().usernameLink);
|
||||
} else {
|
||||
builder.usernameLink(link);
|
||||
}
|
||||
|
||||
return this;
|
||||
}
|
||||
|
||||
private static AccountRecord.Builder parseUnknowns(byte[] serializedUnknowns) {
|
||||
try {
|
||||
return AccountRecord.ADAPTER.decode(serializedUnknowns).newBuilder();
|
||||
} catch (IOException e) {
|
||||
Log.w(TAG, "Failed to combine unknown fields!", e);
|
||||
return new AccountRecord.Builder();
|
||||
}
|
||||
}
|
||||
|
||||
public SignalAccountRecord build() {
|
||||
return new SignalAccountRecord(id, builder.build());
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,59 @@
|
|||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import org.signal.core.util.hasUnknownFields
|
||||
import org.signal.libsignal.protocol.logging.Log
|
||||
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord
|
||||
import java.io.IOException
|
||||
|
||||
class SignalAccountRecord(
|
||||
override val id: StorageId,
|
||||
override val proto: AccountRecord
|
||||
) : SignalRecord<AccountRecord> {
|
||||
|
||||
companion object {
|
||||
private val TAG: String = SignalAccountRecord::class.java.simpleName
|
||||
|
||||
fun newBuilder(serializedUnknowns: ByteArray?): AccountRecord.Builder {
|
||||
return if (serializedUnknowns != null) {
|
||||
parseUnknowns(serializedUnknowns)
|
||||
} else {
|
||||
AccountRecord.Builder()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseUnknowns(serializedUnknowns: ByteArray): AccountRecord.Builder {
|
||||
try {
|
||||
return AccountRecord.ADAPTER.decode(serializedUnknowns).newBuilder()
|
||||
} catch (e: IOException) {
|
||||
Log.w(TAG, "Failed to combine unknown fields!", e)
|
||||
return AccountRecord.Builder()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun asStorageRecord(): SignalStorageRecord {
|
||||
return SignalStorageRecord.forAccount(this)
|
||||
}
|
||||
|
||||
fun serializeUnknownFields(): ByteArray? {
|
||||
return if (proto.hasUnknownFields()) proto.encode() else null
|
||||
}
|
||||
|
||||
override fun equals(other: Any?): Boolean {
|
||||
if (this === other) return true
|
||||
if (javaClass != other?.javaClass) return false
|
||||
|
||||
other as SignalAccountRecord
|
||||
|
||||
if (id != other.id) return false
|
||||
if (proto != other.proto) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
override fun hashCode(): Int {
|
||||
var result = id.hashCode()
|
||||
result = 31 * result + proto.hashCode()
|
||||
return result
|
||||
}
|
||||
}
|
|
@ -8,58 +8,23 @@ package org.whispersystems.signalservice.api.storage
|
|||
import okio.ByteString.Companion.toByteString
|
||||
import org.whispersystems.signalservice.internal.storage.protos.CallLinkRecord
|
||||
import java.io.IOException
|
||||
import java.util.LinkedList
|
||||
|
||||
/**
|
||||
* A record in storage service that represents a call link that was already created.
|
||||
*/
|
||||
class SignalCallLinkRecord(private val id: StorageId, private val proto: CallLinkRecord) : SignalRecord {
|
||||
class SignalCallLinkRecord(
|
||||
override val id: StorageId,
|
||||
override val proto: CallLinkRecord
|
||||
) : SignalRecord<CallLinkRecord> {
|
||||
|
||||
val rootKey: ByteArray = proto.rootKey.toByteArray()
|
||||
val adminPassKey: ByteArray = proto.adminPasskey.toByteArray()
|
||||
val deletionTimestamp: Long = proto.deletedAtTimestampMs
|
||||
|
||||
fun toProto(): CallLinkRecord {
|
||||
return proto
|
||||
}
|
||||
|
||||
override fun getId(): StorageId {
|
||||
return id
|
||||
}
|
||||
|
||||
override fun asStorageRecord(): SignalStorageRecord {
|
||||
return SignalStorageRecord.forCallLink(this)
|
||||
}
|
||||
|
||||
override fun describeDiff(other: SignalRecord?): String {
|
||||
return when (other) {
|
||||
is SignalCallLinkRecord -> {
|
||||
val diff = LinkedList<String>()
|
||||
if (!rootKey.contentEquals(other.rootKey)) {
|
||||
diff.add("RootKey")
|
||||
}
|
||||
|
||||
if (!adminPassKey.contentEquals(other.adminPassKey)) {
|
||||
diff.add("AdminPassKey")
|
||||
}
|
||||
|
||||
if (deletionTimestamp != other.deletionTimestamp) {
|
||||
diff.add("DeletionTimestamp")
|
||||
}
|
||||
|
||||
diff.toString()
|
||||
}
|
||||
|
||||
null -> {
|
||||
"Other was null!"
|
||||
}
|
||||
|
||||
else -> {
|
||||
"Different class. ${this::class.java.getSimpleName()} | ${other::class.java.getSimpleName()}"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fun isDeleted(): Boolean {
|
||||
return deletionTimestamp > 0
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.signal.core.util.ProtoUtil;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
@ -20,7 +21,7 @@ import javax.annotation.Nullable;
|
|||
|
||||
import okio.ByteString;
|
||||
|
||||
public final class SignalContactRecord implements SignalRecord {
|
||||
public final class SignalContactRecord implements SignalRecord<ContactRecord> {
|
||||
|
||||
private static final String TAG = SignalContactRecord.class.getSimpleName();
|
||||
|
||||
|
@ -69,124 +70,13 @@ public final class SignalContactRecord implements SignalRecord {
|
|||
}
|
||||
|
||||
@Override
|
||||
public SignalStorageRecord asStorageRecord() {
|
||||
return SignalStorageRecord.forContact(this);
|
||||
public ContactRecord getProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describeDiff(SignalRecord other) {
|
||||
if (other instanceof SignalContactRecord) {
|
||||
SignalContactRecord that = (SignalContactRecord) other;
|
||||
List<String> diff = new LinkedList<>();
|
||||
|
||||
if (!Arrays.equals(this.id.getRaw(), that.id.getRaw())) {
|
||||
diff.add("ID");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getAci(), that.getAci())) {
|
||||
diff.add("ACI");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getPni(), that.getPni())) {
|
||||
diff.add("PNI");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getNumber(), that.getNumber())) {
|
||||
diff.add("E164");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.profileGivenName, that.profileGivenName)) {
|
||||
diff.add("ProfileGivenName");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.profileFamilyName, that.profileFamilyName)) {
|
||||
diff.add("ProfileFamilyName");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.systemGivenName, that.systemGivenName)) {
|
||||
diff.add("SystemGivenName");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.systemFamilyName, that.systemFamilyName)) {
|
||||
diff.add("SystemFamilyName");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.systemNickname, that.systemNickname)) {
|
||||
diff.add("SystemNickname");
|
||||
}
|
||||
|
||||
if (!OptionalUtil.byteArrayEquals(this.profileKey, that.profileKey)) {
|
||||
diff.add("ProfileKey");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.username, that.username)) {
|
||||
diff.add("Username");
|
||||
}
|
||||
|
||||
if (!OptionalUtil.byteArrayEquals(this.identityKey, that.identityKey)) {
|
||||
diff.add("IdentityKey");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getIdentityState(), that.getIdentityState())) {
|
||||
diff.add("IdentityState");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isBlocked(), that.isBlocked())) {
|
||||
diff.add("Blocked");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isProfileSharingEnabled(), that.isProfileSharingEnabled())) {
|
||||
diff.add("ProfileSharing");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isArchived(), that.isArchived())) {
|
||||
diff.add("Archived");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isForcedUnread(), that.isForcedUnread())) {
|
||||
diff.add("ForcedUnread");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getMuteUntil(), that.getMuteUntil())) {
|
||||
diff.add("MuteUntil");
|
||||
}
|
||||
|
||||
if (shouldHideStory() != that.shouldHideStory()) {
|
||||
diff.add("HideStory");
|
||||
}
|
||||
|
||||
if (getUnregisteredTimestamp() != that.getUnregisteredTimestamp()) {
|
||||
diff.add("UnregisteredTimestamp");
|
||||
}
|
||||
|
||||
if (isHidden() != that.isHidden()) {
|
||||
diff.add("Hidden");
|
||||
}
|
||||
|
||||
if (isPniSignatureVerified() != that.isPniSignatureVerified()) {
|
||||
diff.add("PniSignatureVerified");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) {
|
||||
diff.add("UnknownFields");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.nicknameGivenName, that.nicknameGivenName)) {
|
||||
diff.add("NicknameGivenName");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.nicknameFamilyName, that.nicknameFamilyName)) {
|
||||
diff.add("NicknameFamilyName");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.note, that.note)) {
|
||||
diff.add("Note");
|
||||
}
|
||||
|
||||
return diff.toString();
|
||||
} else {
|
||||
return "Different class. " + getClass().getSimpleName() + " | " + other.getClass().getSimpleName();
|
||||
}
|
||||
public SignalStorageRecord asStorageRecord() {
|
||||
return SignalStorageRecord.forContact(this);
|
||||
}
|
||||
|
||||
public boolean hasUnknownFields() {
|
||||
|
@ -310,10 +200,6 @@ public final class SignalContactRecord implements SignalRecord {
|
|||
return new SignalContactRecord(id, proto.newBuilder().pni("").build());
|
||||
}
|
||||
|
||||
public ContactRecord toProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -12,7 +12,7 @@ import java.util.Objects;
|
|||
|
||||
import okio.ByteString;
|
||||
|
||||
public final class SignalGroupV1Record implements SignalRecord {
|
||||
public final class SignalGroupV1Record implements SignalRecord<GroupV1Record> {
|
||||
|
||||
private static final String TAG = SignalGroupV1Record.class.getSimpleName();
|
||||
|
||||
|
@ -33,53 +33,13 @@ public final class SignalGroupV1Record implements SignalRecord {
|
|||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignalStorageRecord asStorageRecord() {
|
||||
return SignalStorageRecord.forGroupV1(this);
|
||||
@Override public GroupV1Record getProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describeDiff(SignalRecord other) {
|
||||
if (other instanceof SignalGroupV1Record) {
|
||||
SignalGroupV1Record that = (SignalGroupV1Record) other;
|
||||
List<String> diff = new LinkedList<>();
|
||||
|
||||
if (!Arrays.equals(this.id.getRaw(), that.id.getRaw())) {
|
||||
diff.add("ID");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(this.groupId, that.groupId)) {
|
||||
diff.add("MasterKey");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isBlocked(), that.isBlocked())) {
|
||||
diff.add("Blocked");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isProfileSharingEnabled(), that.isProfileSharingEnabled())) {
|
||||
diff.add("ProfileSharing");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isArchived(), that.isArchived())) {
|
||||
diff.add("Archived");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isForcedUnread(), that.isForcedUnread())) {
|
||||
diff.add("ForcedUnread");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getMuteUntil(), that.getMuteUntil())) {
|
||||
diff.add("MuteUntil");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) {
|
||||
diff.add("UnknownFields");
|
||||
}
|
||||
|
||||
return diff.toString();
|
||||
} else {
|
||||
return "Different class. " + getClass().getSimpleName() + " | " + other.getClass().getSimpleName();
|
||||
}
|
||||
public SignalStorageRecord asStorageRecord() {
|
||||
return SignalStorageRecord.forGroupV1(this);
|
||||
}
|
||||
|
||||
public boolean hasUnknownFields() {
|
||||
|
@ -114,10 +74,6 @@ public final class SignalGroupV1Record implements SignalRecord {
|
|||
return proto.mutedUntilTimestamp;
|
||||
}
|
||||
|
||||
public GroupV1Record toProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.signal.core.util.ProtoUtil;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||
|
@ -14,7 +15,7 @@ import java.util.Objects;
|
|||
|
||||
import okio.ByteString;
|
||||
|
||||
public final class SignalGroupV2Record implements SignalRecord {
|
||||
public final class SignalGroupV2Record implements SignalRecord<GroupV2Record> {
|
||||
|
||||
private static final String TAG = SignalGroupV2Record.class.getSimpleName();
|
||||
|
||||
|
@ -35,65 +36,13 @@ public final class SignalGroupV2Record implements SignalRecord {
|
|||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignalStorageRecord asStorageRecord() {
|
||||
return SignalStorageRecord.forGroupV2(this);
|
||||
@Override public GroupV2Record getProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describeDiff(SignalRecord other) {
|
||||
if (other instanceof SignalGroupV2Record) {
|
||||
SignalGroupV2Record that = (SignalGroupV2Record) other;
|
||||
List<String> diff = new LinkedList<>();
|
||||
|
||||
if (!Arrays.equals(this.id.getRaw(), that.id.getRaw())) {
|
||||
diff.add("ID");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(this.getMasterKeyBytes(), that.getMasterKeyBytes())) {
|
||||
diff.add("MasterKey");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isBlocked(), that.isBlocked())) {
|
||||
diff.add("Blocked");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isProfileSharingEnabled(), that.isProfileSharingEnabled())) {
|
||||
diff.add("ProfileSharing");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isArchived(), that.isArchived())) {
|
||||
diff.add("Archived");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.isForcedUnread(), that.isForcedUnread())) {
|
||||
diff.add("ForcedUnread");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getMuteUntil(), that.getMuteUntil())) {
|
||||
diff.add("MuteUntil");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.notifyForMentionsWhenMuted(), that.notifyForMentionsWhenMuted())) {
|
||||
diff.add("NotifyForMentionsWhenMuted");
|
||||
}
|
||||
|
||||
if (shouldHideStory() != that.shouldHideStory()) {
|
||||
diff.add("HideStory");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getStorySendMode(), that.getStorySendMode())) {
|
||||
diff.add("StorySendMode");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.hasUnknownFields(), that.hasUnknownFields())) {
|
||||
diff.add("UnknownFields");
|
||||
}
|
||||
|
||||
return diff.toString();
|
||||
} else {
|
||||
return "Different class. " + getClass().getSimpleName() + " | " + other.getClass().getSimpleName();
|
||||
}
|
||||
public SignalStorageRecord asStorageRecord() {
|
||||
return SignalStorageRecord.forGroupV2(this);
|
||||
}
|
||||
|
||||
public boolean hasUnknownFields() {
|
||||
|
@ -148,10 +97,6 @@ public final class SignalGroupV2Record implements SignalRecord {
|
|||
return proto.storySendMode;
|
||||
}
|
||||
|
||||
public GroupV2Record toProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
|
@ -1,7 +0,0 @@
|
|||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
public interface SignalRecord {
|
||||
StorageId getId();
|
||||
SignalStorageRecord asStorageRecord();
|
||||
String describeDiff(SignalRecord other);
|
||||
}
|
|
@ -0,0 +1,52 @@
|
|||
package org.whispersystems.signalservice.api.storage
|
||||
|
||||
import kotlin.reflect.KVisibility
|
||||
import kotlin.reflect.full.memberProperties
|
||||
|
||||
interface SignalRecord<E> {
|
||||
val id: StorageId
|
||||
val proto: E
|
||||
fun asStorageRecord(): SignalStorageRecord
|
||||
|
||||
fun describeDiff(other: SignalRecord<*>): String {
|
||||
if (this::class != other::class) {
|
||||
return "Classes are different!"
|
||||
}
|
||||
|
||||
if (this.proto!!::class != other.proto!!::class) {
|
||||
return "Proto classes are different!"
|
||||
}
|
||||
|
||||
val myFields = this.proto!!::class.memberProperties
|
||||
val otherFields = other.proto!!::class.memberProperties
|
||||
|
||||
val myFieldsByName = myFields
|
||||
.filter { it.isFinal && it.visibility == KVisibility.PUBLIC }
|
||||
.associate { it.name to it.getter.call(this.proto!!) }
|
||||
|
||||
val otherFieldsByName = otherFields
|
||||
.filter { it.isFinal && it.visibility == KVisibility.PUBLIC }
|
||||
.associate { it.name to it.getter.call(other.proto!!) }
|
||||
|
||||
val mismatching = mutableListOf<String>()
|
||||
|
||||
if (this.id != other.id) {
|
||||
mismatching += "id"
|
||||
}
|
||||
|
||||
for (key in myFieldsByName.keys) {
|
||||
val myValue = myFieldsByName[key]
|
||||
val otherValue = otherFieldsByName[key]
|
||||
|
||||
if (myValue != otherValue) {
|
||||
mismatching += key
|
||||
}
|
||||
}
|
||||
|
||||
return if (mismatching.isEmpty()) {
|
||||
"All fields match."
|
||||
} else {
|
||||
mismatching.sorted().joinToString(prefix = "Some fields differ: ", separator = ", ")
|
||||
}
|
||||
}
|
||||
}
|
|
@ -64,17 +64,17 @@ public final class SignalStorageModels {
|
|||
StorageRecord.Builder builder = new StorageRecord.Builder();
|
||||
|
||||
if (record.getContact().isPresent()) {
|
||||
builder.contact(record.getContact().get().toProto());
|
||||
builder.contact(record.getContact().get().getProto());
|
||||
} else if (record.getGroupV1().isPresent()) {
|
||||
builder.groupV1(record.getGroupV1().get().toProto());
|
||||
builder.groupV1(record.getGroupV1().get().getProto());
|
||||
} else if (record.getGroupV2().isPresent()) {
|
||||
builder.groupV2(record.getGroupV2().get().toProto());
|
||||
builder.groupV2(record.getGroupV2().get().getProto());
|
||||
} else if (record.getAccount().isPresent()) {
|
||||
builder.account(record.getAccount().get().toProto());
|
||||
builder.account(record.getAccount().get().getProto());
|
||||
} else if (record.getStoryDistributionList().isPresent()) {
|
||||
builder.storyDistributionList(record.getStoryDistributionList().get().toProto());
|
||||
builder.storyDistributionList(record.getStoryDistributionList().get().getProto());
|
||||
} else if (record.getCallLink().isPresent()) {
|
||||
builder.callLink(record.getCallLink().get().toProto());
|
||||
builder.callLink(record.getCallLink().get().getProto());
|
||||
} else {
|
||||
throw new InvalidStorageWriteError();
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import org.whispersystems.signalservice.internal.storage.protos.CallLinkRecord;
|
|||
import java.util.Objects;
|
||||
import java.util.Optional;
|
||||
|
||||
public class SignalStorageRecord implements SignalRecord {
|
||||
public class SignalStorageRecord {
|
||||
|
||||
private final StorageId id;
|
||||
private final Optional<SignalStoryDistributionListRecord> storyDistributionList;
|
||||
|
@ -89,21 +89,10 @@ public class SignalStorageRecord implements SignalRecord {
|
|||
this.callLink = callLink;
|
||||
}
|
||||
|
||||
@Override
|
||||
public StorageId getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public SignalStorageRecord asStorageRecord() {
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describeDiff(SignalRecord other) {
|
||||
return "Diffs not supported.";
|
||||
}
|
||||
|
||||
public int getType() {
|
||||
return id.getType();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package org.whispersystems.signalservice.api.storage;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.signal.core.util.ProtoUtil;
|
||||
import org.signal.libsignal.protocol.logging.Log;
|
||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||
|
@ -15,7 +16,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import okio.ByteString;
|
||||
|
||||
public class SignalStoryDistributionListRecord implements SignalRecord {
|
||||
public class SignalStoryDistributionListRecord implements SignalRecord<StoryDistributionListRecord> {
|
||||
|
||||
private static final String TAG = SignalStoryDistributionListRecord.class.getSimpleName();
|
||||
|
||||
|
@ -42,12 +43,13 @@ public class SignalStoryDistributionListRecord implements SignalRecord {
|
|||
}
|
||||
|
||||
@Override
|
||||
public SignalStorageRecord asStorageRecord() {
|
||||
return SignalStorageRecord.forStoryDistributionList(this);
|
||||
public StoryDistributionListRecord getProto() {
|
||||
return proto;
|
||||
}
|
||||
|
||||
public StoryDistributionListRecord toProto() {
|
||||
return proto;
|
||||
@Override
|
||||
public SignalStorageRecord asStorageRecord() {
|
||||
return SignalStorageRecord.forStoryDistributionList(this);
|
||||
}
|
||||
|
||||
public byte[] serializeUnknownFields() {
|
||||
|
@ -78,46 +80,6 @@ public class SignalStoryDistributionListRecord implements SignalRecord {
|
|||
return proto.isBlockList;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String describeDiff(SignalRecord other) {
|
||||
if (other instanceof SignalStoryDistributionListRecord) {
|
||||
SignalStoryDistributionListRecord that = (SignalStoryDistributionListRecord) other;
|
||||
List<String> diff = new LinkedList<>();
|
||||
|
||||
if (!Arrays.equals(this.id.getRaw(), that.id.getRaw())) {
|
||||
diff.add("ID");
|
||||
}
|
||||
|
||||
if (!Arrays.equals(this.getIdentifier(), that.getIdentifier())) {
|
||||
diff.add("Identifier");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.getName(), that.getName())) {
|
||||
diff.add("Name");
|
||||
}
|
||||
|
||||
if (!Objects.equals(this.recipients, that.recipients)) {
|
||||
diff.add("RecipientUuids");
|
||||
}
|
||||
|
||||
if (this.getDeletedAtTimestamp() != that.getDeletedAtTimestamp()) {
|
||||
diff.add("DeletedAtTimestamp");
|
||||
}
|
||||
|
||||
if (this.allowsReplies() != that.allowsReplies()) {
|
||||
diff.add("AllowsReplies");
|
||||
}
|
||||
|
||||
if (this.isBlockList() != that.isBlockList()) {
|
||||
diff.add("BlockList");
|
||||
}
|
||||
|
||||
return diff.toString();
|
||||
} else {
|
||||
return "Different class. " + getClass().getSimpleName() + " | " + other.getClass().getSimpleName();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) return true;
|
||||
|
|
Loading…
Add table
Reference in a new issue