mirror of
https://github.com/signalapp/Signal-Android.git
synced 2025-12-22 20:18:36 +00:00
Remove cruft around SignalAccountRecord.
This commit is contained in:
@@ -37,7 +37,7 @@ class RecipientTableTest_applyStorageSyncContactUpdate {
|
|||||||
val oldRecord: SignalContactRecord = StorageSyncModels.localToRemoteRecord(SignalDatabase.recipients.getRecordForSync(harness.others[0])!!).contact.get()
|
val oldRecord: SignalContactRecord = StorageSyncModels.localToRemoteRecord(SignalDatabase.recipients.getRecordForSync(harness.others[0])!!).contact.get()
|
||||||
|
|
||||||
val newProto = oldRecord
|
val newProto = oldRecord
|
||||||
.toProto()
|
.proto
|
||||||
.newBuilder()
|
.newBuilder()
|
||||||
.identityState(ContactRecord.IdentityState.DEFAULT)
|
.identityState(ContactRecord.IdentityState.DEFAULT)
|
||||||
.build()
|
.build()
|
||||||
|
|||||||
@@ -17,8 +17,11 @@ import org.signal.core.util.SqlUtil
|
|||||||
import org.signal.core.util.delete
|
import org.signal.core.util.delete
|
||||||
import org.signal.core.util.exists
|
import org.signal.core.util.exists
|
||||||
import org.signal.core.util.forEach
|
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.logging.Log
|
||||||
import org.signal.core.util.nullIfBlank
|
import org.signal.core.util.nullIfBlank
|
||||||
|
import org.signal.core.util.nullIfEmpty
|
||||||
import org.signal.core.util.optionalString
|
import org.signal.core.util.optionalString
|
||||||
import org.signal.core.util.or
|
import org.signal.core.util.or
|
||||||
import org.signal.core.util.orNull
|
import org.signal.core.util.orNull
|
||||||
@@ -1024,12 +1027,13 @@ open class RecipientTable(context: Context, databaseHelper: SignalDatabase) : Da
|
|||||||
}
|
}
|
||||||
|
|
||||||
fun applyStorageSyncAccountUpdate(update: StorageRecordUpdate<SignalAccountRecord>) {
|
fun applyStorageSyncAccountUpdate(update: StorageRecordUpdate<SignalAccountRecord>) {
|
||||||
val profileName = ProfileName.fromParts(update.new.givenName.orElse(null), update.new.familyName.orElse(null))
|
val profileName = ProfileName.fromParts(update.new.proto.givenName, update.new.proto.familyName)
|
||||||
val localKey = ProfileKeyUtil.profileKeyOptional(update.old.profileKey.orElse(null))
|
val localKey = update.old.proto.profileKey.nullIfEmpty()?.toByteArray()?.let { ProfileKeyUtil.profileKeyOrNull(it) }
|
||||||
val remoteKey = ProfileKeyUtil.profileKeyOptional(update.new.profileKey.orElse(null))
|
val remoteKey = update.new.proto.profileKey.nullIfEmpty()?.toByteArray()?.let { ProfileKeyUtil.profileKeyOrNull(it) }
|
||||||
val profileKey: String? = remoteKey.or(localKey).map { obj: ProfileKey -> obj.serialize() }.map { source: ByteArray? -> Base64.encodeWithPadding(source!!) }.orElse(null)
|
val profileKey: String? = (remoteKey ?: localKey)?.let { Base64.encodeWithPadding(it.serialize()) }
|
||||||
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"}.")
|
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 {
|
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!")
|
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))
|
put(STORAGE_SERVICE_ID, Base64.encodeWithPadding(update.new.id.raw))
|
||||||
|
|
||||||
if (update.new.hasUnknownFields()) {
|
if (update.new.proto.hasUnknownFields()) {
|
||||||
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(Objects.requireNonNull(update.new.serializeUnknownFields())))
|
put(STORAGE_SERVICE_PROTO, Base64.encodeWithPadding(update.new.serializeUnknownFields()!!))
|
||||||
} else {
|
} else {
|
||||||
putNull(STORAGE_SERVICE_PROTO)
|
putNull(STORAGE_SERVICE_PROTO)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update.new.username != null) {
|
if (update.new.proto.username.nullIfBlank() != null) {
|
||||||
writableDatabase
|
writableDatabase
|
||||||
.update(TABLE_NAME)
|
.update(TABLE_NAME)
|
||||||
.values(USERNAME to null)
|
.values(USERNAME to null)
|
||||||
.where("$USERNAME = ?", update.new.username!!)
|
.where("$USERNAME = ?", update.new.proto.username)
|
||||||
.run()
|
.run()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -71,10 +71,11 @@ import org.thoughtcrime.securesms.util.LRUCache
|
|||||||
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
import org.thoughtcrime.securesms.util.TextSecurePreferences
|
||||||
import org.thoughtcrime.securesms.util.isScheduled
|
import org.thoughtcrime.securesms.util.isScheduled
|
||||||
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
|
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.SignalContactRecord
|
||||||
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record
|
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record
|
||||||
import org.whispersystems.signalservice.api.storage.SignalGroupV2Record
|
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.Closeable
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.Collections
|
import java.util.Collections
|
||||||
@@ -1522,7 +1523,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
|||||||
|
|
||||||
fun applyStorageSyncUpdate(recipientId: RecipientId, record: SignalAccountRecord) {
|
fun applyStorageSyncUpdate(recipientId: RecipientId, record: SignalAccountRecord) {
|
||||||
writableDatabase.withinTransaction { db ->
|
writableDatabase.withinTransaction { db ->
|
||||||
applyStorageSyncUpdate(recipientId, record.isNoteToSelfArchived, record.isNoteToSelfForcedUnread)
|
applyStorageSyncUpdate(recipientId, record.proto.noteToSelfArchived, record.proto.noteToSelfMarkedUnread)
|
||||||
|
|
||||||
db.updateAll(TABLE_NAME)
|
db.updateAll(TABLE_NAME)
|
||||||
.values(PINNED to 0)
|
.values(PINNED to 0)
|
||||||
@@ -1530,19 +1531,19 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
|
|||||||
|
|
||||||
var pinnedPosition = 1
|
var pinnedPosition = 1
|
||||||
|
|
||||||
for (pinned: PinnedConversation in record.pinnedConversations) {
|
for (pinned: AccountRecord.PinnedConversation in record.proto.pinnedConversations) {
|
||||||
val pinnedRecipient: Recipient? = if (pinned.contact.isPresent) {
|
val pinnedRecipient: Recipient? = if (pinned.contact != null) {
|
||||||
Recipient.externalPush(pinned.contact.get())
|
Recipient.externalPush(pinned.contact!!.toSignalServiceAddress())
|
||||||
} else if (pinned.groupV1Id.isPresent) {
|
} else if (pinned.legacyGroupId != null) {
|
||||||
try {
|
try {
|
||||||
Recipient.externalGroupExact(GroupId.v1(pinned.groupV1Id.get()))
|
Recipient.externalGroupExact(GroupId.v1(pinned.legacyGroupId!!.toByteArray()))
|
||||||
} catch (e: BadGroupIdException) {
|
} catch (e: BadGroupIdException) {
|
||||||
Log.w(TAG, "Failed to parse pinned groupV1 ID!", e)
|
Log.w(TAG, "Failed to parse pinned groupV1 ID!", e)
|
||||||
null
|
null
|
||||||
}
|
}
|
||||||
} else if (pinned.groupV2MasterKey.isPresent) {
|
} else if (pinned.groupMasterKey != null) {
|
||||||
try {
|
try {
|
||||||
Recipient.externalGroupExact(GroupId.v2(GroupMasterKey(pinned.groupV2MasterKey.get())))
|
Recipient.externalGroupExact(GroupId.v2(GroupMasterKey(pinned.groupMasterKey!!.toByteArray())))
|
||||||
} catch (e: InvalidInputException) {
|
} catch (e: InvalidInputException) {
|
||||||
Log.w(TAG, "Failed to parse pinned groupV2 master key!", e)
|
Log.w(TAG, "Failed to parse pinned groupV2 master key!", e)
|
||||||
null
|
null
|
||||||
|
|||||||
@@ -124,9 +124,9 @@ public class StorageAccountRestoreJob extends BaseJob {
|
|||||||
|
|
||||||
JobManager jobManager = AppDependencies.getJobManager();
|
JobManager jobManager = AppDependencies.getJobManager();
|
||||||
|
|
||||||
if (accountRecord.getAvatarUrlPath().isPresent()) {
|
if (!accountRecord.getProto().avatarUrlPath.isEmpty()) {
|
||||||
Log.i(TAG, "Fetching avatar...");
|
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()) {
|
if (state.isPresent()) {
|
||||||
Log.i(TAG, "Avatar retrieved successfully. " + state.get());
|
Log.i(TAG, "Avatar retrieved successfully. " + state.get());
|
||||||
|
|||||||
@@ -100,13 +100,11 @@ import java.util.stream.Collectors
|
|||||||
* - Update the respective model (i.e. [SignalContactRecord])
|
* - Update the respective model (i.e. [SignalContactRecord])
|
||||||
* - Add getters
|
* - Add getters
|
||||||
* - Update the builder
|
* - Update the builder
|
||||||
* - Update [SignalRecord.describeDiff].
|
* - Update the respective record processor (i.e [ContactRecordProcessor]). You need to make sure that you're:
|
||||||
* - Update the respective record processor (i.e [ContactRecordProcessor]). You need to make
|
* - Merging the attributes, likely preferring remote
|
||||||
* sure that you're:
|
* - Adding to doParamsMatch()
|
||||||
* - Merging the attributes, likely preferring remote
|
* - Adding the parameter to the builder chain when creating a merged model
|
||||||
* - Adding to doParamsMatch()
|
* - Update builder usage in StorageSyncModels
|
||||||
* - 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
|
* - Handle the new data when writing to the local storage
|
||||||
* (i.e. [RecipientTable.applyStorageSyncContactUpdate]).
|
* (i.e. [RecipientTable.applyStorageSyncContactUpdate]).
|
||||||
* - Make sure that whenever you change the field in the UI, we rotate the storageId for that row
|
* - 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 android.preference.PreferenceManager
|
||||||
import org.signal.core.util.Base64
|
import org.signal.core.util.Base64
|
||||||
import org.signal.core.util.logging.Log
|
import org.signal.core.util.logging.Log
|
||||||
|
import org.signal.core.util.nullIfBlank
|
||||||
import org.signal.libsignal.protocol.IdentityKey
|
import org.signal.libsignal.protocol.IdentityKey
|
||||||
import org.signal.libsignal.protocol.IdentityKeyPair
|
import org.signal.libsignal.protocol.IdentityKeyPair
|
||||||
import org.signal.libsignal.protocol.ecc.Curve
|
import org.signal.libsignal.protocol.ecc.Curve
|
||||||
@@ -401,10 +402,10 @@ class AccountValues internal constructor(store: KeyValueStore, context: Context)
|
|||||||
var username: String?
|
var username: String?
|
||||||
get() {
|
get() {
|
||||||
val value = getString(KEY_USERNAME, null)
|
val value = getString(KEY_USERNAME, null)
|
||||||
return if (value.isNullOrBlank()) null else value
|
return value.nullIfBlank()
|
||||||
}
|
}
|
||||||
set(value) {
|
set(value) {
|
||||||
putString(KEY_USERNAME, value)
|
putString(KEY_USERNAME, value.nullIfBlank())
|
||||||
}
|
}
|
||||||
|
|
||||||
/** The local user's username link components, if set. */
|
/** The local user's username link components, if set. */
|
||||||
|
|||||||
@@ -1,14 +1,20 @@
|
|||||||
package org.thoughtcrime.securesms.storage
|
package org.thoughtcrime.securesms.storage
|
||||||
|
|
||||||
import android.content.Context
|
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.logging.Log
|
||||||
|
import org.signal.core.util.nullIfEmpty
|
||||||
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
import org.thoughtcrime.securesms.keyvalue.SignalStore
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper.applyAccountStorageSyncUpdates
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper.applyAccountStorageSyncUpdates
|
||||||
import org.thoughtcrime.securesms.storage.StorageSyncHelper.buildAccountRecord
|
import org.thoughtcrime.securesms.storage.StorageSyncHelper.buildAccountRecord
|
||||||
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
|
import org.whispersystems.signalservice.api.storage.SignalAccountRecord
|
||||||
import org.whispersystems.signalservice.api.util.OptionalUtil
|
import org.whispersystems.signalservice.api.storage.StorageId
|
||||||
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord
|
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 org.whispersystems.signalservice.internal.storage.protos.OptionalBool
|
||||||
import java.util.Optional
|
import java.util.Optional
|
||||||
|
|
||||||
@@ -45,184 +51,100 @@ class AccountRecordProcessor(
|
|||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getMatching(record: SignalAccountRecord, keyGenerator: StorageKeyGenerator): Optional<SignalAccountRecord> {
|
override fun getMatching(remote: SignalAccountRecord, keyGenerator: StorageKeyGenerator): Optional<SignalAccountRecord> {
|
||||||
return Optional.of(localAccountRecord)
|
return Optional.of(localAccountRecord)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun merge(remote: SignalAccountRecord, local: SignalAccountRecord, keyGenerator: StorageKeyGenerator): SignalAccountRecord {
|
override fun merge(remote: SignalAccountRecord, local: SignalAccountRecord, keyGenerator: StorageKeyGenerator): SignalAccountRecord {
|
||||||
val givenName: String
|
val mergedGivenName: String
|
||||||
val familyName: String
|
val mergedFamilyName: String
|
||||||
|
|
||||||
if (remote.givenName.isPresent || remote.familyName.isPresent) {
|
if (remote.proto.givenName.isNotBlank() || remote.proto.familyName.isNotBlank()) {
|
||||||
givenName = remote.givenName.orElse("")
|
mergedGivenName = remote.proto.givenName
|
||||||
familyName = remote.familyName.orElse("")
|
mergedFamilyName = remote.proto.familyName
|
||||||
} else {
|
} else {
|
||||||
givenName = local.givenName.orElse("")
|
mergedGivenName = local.proto.givenName
|
||||||
familyName = local.familyName.orElse("")
|
mergedFamilyName = local.proto.familyName
|
||||||
}
|
}
|
||||||
|
|
||||||
val payments = if (remote.payments.entropy.isPresent) {
|
val payments = if (remote.proto.payments?.entropy != null) {
|
||||||
remote.payments
|
remote.proto.payments
|
||||||
} else {
|
} else {
|
||||||
local.payments
|
local.proto.payments
|
||||||
}
|
}
|
||||||
|
|
||||||
val subscriber = if (remote.subscriber.id.isPresent) {
|
val donationSubscriberId: ByteString
|
||||||
remote.subscriber
|
val donationSubscriberCurrencyCode: String
|
||||||
|
|
||||||
|
if (remote.proto.subscriberId.isNotEmpty()) {
|
||||||
|
donationSubscriberId = remote.proto.subscriberId
|
||||||
|
donationSubscriberCurrencyCode = remote.proto.subscriberCurrencyCode
|
||||||
} else {
|
} else {
|
||||||
local.subscriber
|
donationSubscriberId = local.proto.subscriberId
|
||||||
|
donationSubscriberCurrencyCode = remote.proto.subscriberCurrencyCode
|
||||||
}
|
}
|
||||||
|
|
||||||
val backupsSubscriber = if (remote.subscriber.id.isPresent) {
|
val backupsSubscriberId: ByteString
|
||||||
remote.subscriber
|
val backupsSubscriberCurrencyCode: String
|
||||||
|
|
||||||
|
if (remote.proto.backupsSubscriberId.isNotEmpty()) {
|
||||||
|
backupsSubscriberId = remote.proto.backupsSubscriberId
|
||||||
|
backupsSubscriberCurrencyCode = remote.proto.backupsSubscriberCurrencyCode
|
||||||
} else {
|
} 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 {
|
} else {
|
||||||
remote.storyViewReceiptsState
|
remote.proto.storyViewReceiptsEnabled
|
||||||
}
|
}
|
||||||
|
|
||||||
val unknownFields = remote.serializeUnknownFields()
|
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(
|
val merged = SignalAccountRecord.newBuilder(unknownFields).apply {
|
||||||
contact = remote,
|
givenName = mergedGivenName
|
||||||
unknownFields = unknownFields,
|
familyName = mergedFamilyName
|
||||||
givenName = givenName,
|
avatarUrlPath = remote.proto.avatarUrlPath.nullIfEmpty() ?: local.proto.avatarUrlPath
|
||||||
familyName = familyName,
|
profileKey = remote.proto.profileKey.nullIfEmpty() ?: local.proto.profileKey
|
||||||
avatarUrlPath = avatarUrlPath,
|
noteToSelfArchived = remote.proto.noteToSelfArchived
|
||||||
profileKey = profileKey,
|
noteToSelfMarkedUnread = remote.proto.noteToSelfMarkedUnread
|
||||||
noteToSelfArchived = noteToSelfArchived,
|
readReceipts = remote.proto.readReceipts
|
||||||
noteToSelfForcedUnread = noteToSelfForcedUnread,
|
typingIndicators = remote.proto.typingIndicators
|
||||||
readReceipts = readReceipts,
|
sealedSenderIndicators = remote.proto.sealedSenderIndicators
|
||||||
typingIndicators = typingIndicators,
|
linkPreviews = remote.proto.linkPreviews
|
||||||
sealedSenderIndicators = sealedSenderIndicators,
|
unlistedPhoneNumber = remote.proto.unlistedPhoneNumber
|
||||||
linkPreviewsEnabled = linkPreviews,
|
pinnedConversations = remote.proto.pinnedConversations
|
||||||
phoneNumberSharingMode = phoneNumberSharingMode,
|
phoneNumberSharingMode = remote.proto.phoneNumberSharingMode
|
||||||
unlistedPhoneNumber = unlisted,
|
preferContactAvatars = remote.proto.preferContactAvatars
|
||||||
pinnedConversations = pinnedConversations,
|
universalExpireTimer = remote.proto.universalExpireTimer
|
||||||
preferContactAvatars = preferContactAvatars,
|
primarySendsSms = false
|
||||||
payments = payments,
|
e164 = if (SignalStore.account.isPrimaryDevice) local.proto.e164 else remote.proto.e164
|
||||||
universalExpireTimer = universalExpireTimer,
|
preferredReactionEmoji = remote.proto.preferredReactionEmoji.takeIf { it.isNotEmpty() } ?: local.proto.preferredReactionEmoji
|
||||||
primarySendsSms = primarySendsSms,
|
displayBadgesOnProfile = remote.proto.displayBadgesOnProfile
|
||||||
e164 = e164,
|
subscriptionManuallyCancelled = remote.proto.subscriptionManuallyCancelled
|
||||||
defaultReactions = defaultReactions,
|
keepMutedChatsArchived = remote.proto.keepMutedChatsArchived
|
||||||
subscriber = subscriber,
|
hasSetMyStoriesPrivacy = remote.proto.hasSetMyStoriesPrivacy
|
||||||
displayBadgesOnProfile = displayBadgesOnProfile,
|
hasViewedOnboardingStory = remote.proto.hasViewedOnboardingStory || local.proto.hasViewedOnboardingStory
|
||||||
subscriptionManuallyCancelled = subscriptionManuallyCancelled,
|
storiesDisabled = remote.proto.storiesDisabled
|
||||||
keepMutedChatsArchived = keepMutedChatsArchived,
|
storyViewReceiptsEnabled = storyViewReceiptsState
|
||||||
hasSetMyStoriesPrivacy = hasSetMyStoriesPrivacy,
|
hasSeenGroupStoryEducationSheet = remote.proto.hasSeenGroupStoryEducationSheet || local.proto.hasSeenGroupStoryEducationSheet
|
||||||
hasViewedOnboardingStory = hasViewedOnboardingStory,
|
hasCompletedUsernameOnboarding = remote.proto.hasCompletedUsernameOnboarding || local.proto.hasCompletedUsernameOnboarding
|
||||||
hasCompletedUsernameOnboarding = hasSeenUsernameOnboarding,
|
username = remote.proto.username
|
||||||
storiesDisabled = storiesDisabled,
|
usernameLink = remote.proto.usernameLink
|
||||||
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
|
|
||||||
)
|
|
||||||
|
|
||||||
if (matchesRemote) {
|
safeSetPayments(payments?.enabled == true, payments?.entropy?.toByteArray())
|
||||||
return remote
|
safeSetSubscriber(donationSubscriberId, donationSubscriberCurrencyCode)
|
||||||
} else if (matchesLocal) {
|
safeSetBackupsSubscriber(backupsSubscriberId, backupsSubscriberCurrencyCode)
|
||||||
return local
|
}.toSignalAccountRecord(StorageId.forAccount(keyGenerator.generate()))
|
||||||
|
|
||||||
|
return if (doParamsMatch(remote, merged)) {
|
||||||
|
remote
|
||||||
|
} else if (doParamsMatch(local, merged)) {
|
||||||
|
local
|
||||||
} else {
|
} else {
|
||||||
val builder = SignalAccountRecord.Builder(keyGenerator.generate(), unknownFields)
|
merged
|
||||||
.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()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,72 +160,7 @@ class AccountRecordProcessor(
|
|||||||
return 0
|
return 0
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun doParamsMatch(
|
private fun doParamsMatch(base: SignalAccountRecord, test: SignalAccountRecord): Boolean {
|
||||||
contact: SignalAccountRecord,
|
return base.serializeUnknownFields().contentEquals(test.serializeUnknownFields()) && base.proto == test.proto
|
||||||
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
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'
|
* 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.
|
* 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 {
|
companion object {
|
||||||
private val TAG = Log.tag(DefaultStorageRecordProcessor::class.java)
|
private val TAG = Log.tag(DefaultStorageRecordProcessor::class.java)
|
||||||
}
|
}
|
||||||
@@ -37,16 +37,15 @@ abstract class DefaultStorageRecordProcessor<E : SignalRecord> : StorageRecordPr
|
|||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
override fun process(remoteRecords: Collection<E>, keyGenerator: StorageKeyGenerator) {
|
override fun process(remoteRecords: Collection<E>, keyGenerator: StorageKeyGenerator) {
|
||||||
val matchedRecords: MutableSet<E> = TreeSet(this)
|
val matchedRecords: MutableSet<E> = TreeSet(this)
|
||||||
var i = 0
|
|
||||||
|
|
||||||
for (remote in remoteRecords) {
|
for ((i, remote) in remoteRecords.withIndex()) {
|
||||||
if (isInvalid(remote)) {
|
if (isInvalid(remote)) {
|
||||||
warn(i, remote, "Found invalid key! Ignoring it.")
|
warn(i, remote, "Found invalid key! Ignoring it.")
|
||||||
} else {
|
} else {
|
||||||
val local = getMatching(remote, keyGenerator)
|
val local = getMatching(remote, keyGenerator)
|
||||||
|
|
||||||
if (local.isPresent) {
|
if (local.isPresent) {
|
||||||
val merged = merge(remote, local.get(), keyGenerator)
|
val merged: E = merge(remote, local.get(), keyGenerator)
|
||||||
|
|
||||||
if (matchedRecords.contains(local.get())) {
|
if (matchedRecords.contains(local.get())) {
|
||||||
warn(i, remote, "Multiple remote records map to the same local record! Ignoring this one.")
|
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())
|
matchedRecords.add(local.get())
|
||||||
|
|
||||||
if (merged != remote) {
|
if (merged != remote) {
|
||||||
info(i, remote, "[Remote Update] " + StorageRecordUpdate(remote, merged).toString())
|
info(i, remote, "[Remote Update] " + remote.describeDiff(merged))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (merged != local.get()) {
|
if (merged != local.get()) {
|
||||||
@@ -68,8 +67,6 @@ abstract class DefaultStorageRecordProcessor<E : SignalRecord> : StorageRecordPr
|
|||||||
insertLocal(remote)
|
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
|
* Handles processing a remote record, which involves applying any local changes that need to be
|
||||||
* made based on the remote records.
|
* made based on the remote records.
|
||||||
*/
|
*/
|
||||||
interface StorageRecordProcessor<E : SignalRecord?> {
|
interface StorageRecordProcessor<E : SignalRecord<*>> {
|
||||||
@Throws(IOException::class)
|
@Throws(IOException::class)
|
||||||
fun process(remoteRecords: Collection<E>, keyGenerator: StorageKeyGenerator)
|
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 android.content.Context
|
||||||
import androidx.annotation.VisibleForTesting
|
import androidx.annotation.VisibleForTesting
|
||||||
|
import okio.ByteString
|
||||||
import okio.ByteString.Companion.toByteString
|
import okio.ByteString.Companion.toByteString
|
||||||
import org.signal.core.util.Base64.encodeWithPadding
|
import org.signal.core.util.Base64.encodeWithPadding
|
||||||
import org.signal.core.util.logging.Log
|
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.SignalStorageManifest
|
||||||
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
|
import org.whispersystems.signalservice.api.storage.SignalStorageRecord
|
||||||
import org.whispersystems.signalservice.api.storage.StorageId
|
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.OptionalUtil.byteArrayEquals
|
||||||
import org.whispersystems.signalservice.api.util.UuidUtil
|
import org.whispersystems.signalservice.api.util.UuidUtil
|
||||||
import org.whispersystems.signalservice.api.util.toByteArray
|
import org.whispersystems.signalservice.api.util.toByteArray
|
||||||
@@ -130,52 +135,54 @@ object StorageSyncHelper {
|
|||||||
|
|
||||||
val storageId = selfRecord?.storageId ?: self.storageId
|
val storageId = selfRecord?.storageId ?: self.storageId
|
||||||
|
|
||||||
val account = SignalAccountRecord.Builder(storageId, selfRecord?.syncExtras?.storageProto)
|
val accountRecord = SignalAccountRecord.newBuilder(selfRecord?.syncExtras?.storageProto).apply {
|
||||||
.setProfileKey(self.profileKey)
|
profileKey = self.profileKey?.toByteString() ?: ByteString.EMPTY
|
||||||
.setGivenName(self.profileName.givenName)
|
givenName = self.profileName.givenName
|
||||||
.setFamilyName(self.profileName.familyName)
|
familyName = self.profileName.familyName
|
||||||
.setAvatarUrlPath(self.profileAvatar)
|
avatarUrlPath = self.profileAvatar ?: ""
|
||||||
.setNoteToSelfArchived(selfRecord != null && selfRecord.syncExtras.isArchived)
|
noteToSelfArchived = selfRecord != null && selfRecord.syncExtras.isArchived
|
||||||
.setNoteToSelfForcedUnread(selfRecord != null && selfRecord.syncExtras.isForcedUnread)
|
noteToSelfMarkedUnread = selfRecord != null && selfRecord.syncExtras.isForcedUnread
|
||||||
.setTypingIndicatorsEnabled(TextSecurePreferences.isTypingIndicatorsEnabled(context))
|
typingIndicators = TextSecurePreferences.isTypingIndicatorsEnabled(context)
|
||||||
.setReadReceiptsEnabled(TextSecurePreferences.isReadReceiptsEnabled(context))
|
readReceipts = TextSecurePreferences.isReadReceiptsEnabled(context)
|
||||||
.setSealedSenderIndicatorsEnabled(TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context))
|
sealedSenderIndicators = TextSecurePreferences.isShowUnidentifiedDeliveryIndicatorsEnabled(context)
|
||||||
.setLinkPreviewsEnabled(SignalStore.settings.isLinkPreviewsEnabled)
|
linkPreviews = SignalStore.settings.isLinkPreviewsEnabled
|
||||||
.setUnlistedPhoneNumber(SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode == PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE)
|
unlistedPhoneNumber = SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode == PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE
|
||||||
.setPhoneNumberSharingMode(StorageSyncModels.localToRemotePhoneNumberSharingMode(SignalStore.phoneNumberPrivacy.phoneNumberSharingMode))
|
phoneNumberSharingMode = StorageSyncModels.localToRemotePhoneNumberSharingMode(SignalStore.phoneNumberPrivacy.phoneNumberSharingMode)
|
||||||
.setPinnedConversations(StorageSyncModels.localToRemotePinnedConversations(pinned))
|
pinnedConversations = StorageSyncModels.localToRemotePinnedConversations(pinned)
|
||||||
.setPreferContactAvatars(SignalStore.settings.isPreferSystemContactPhotos)
|
preferContactAvatars = SignalStore.settings.isPreferSystemContactPhotos
|
||||||
.setPayments(SignalStore.payments.mobileCoinPaymentsEnabled(), Optional.ofNullable(SignalStore.payments.paymentsEntropy).map { obj: Entropy -> obj.bytes }.orElse(null))
|
primarySendsSms = false
|
||||||
.setPrimarySendsSms(false)
|
universalExpireTimer = SignalStore.settings.universalExpireTimer
|
||||||
.setUniversalExpireTimer(SignalStore.settings.universalExpireTimer)
|
preferredReactionEmoji = SignalStore.emoji.reactions
|
||||||
.setDefaultReactions(SignalStore.emoji.reactions)
|
displayBadgesOnProfile = SignalStore.inAppPayments.getDisplayBadgesOnProfile()
|
||||||
.setSubscriber(StorageSyncModels.localToRemoteSubscriber(getSubscriber(InAppPaymentSubscriberRecord.Type.DONATION)))
|
subscriptionManuallyCancelled = isUserManuallyCancelled(InAppPaymentSubscriberRecord.Type.DONATION)
|
||||||
.setBackupsSubscriber(StorageSyncModels.localToRemoteSubscriber(getSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP)))
|
keepMutedChatsArchived = SignalStore.settings.shouldKeepMutedChatsArchived()
|
||||||
.setDisplayBadgesOnProfile(SignalStore.inAppPayments.getDisplayBadgesOnProfile())
|
hasSetMyStoriesPrivacy = SignalStore.story.userHasBeenNotifiedAboutStories
|
||||||
.setSubscriptionManuallyCancelled(isUserManuallyCancelled(InAppPaymentSubscriberRecord.Type.DONATION))
|
hasViewedOnboardingStory = SignalStore.story.userHasViewedOnboardingStory
|
||||||
.setKeepMutedChatsArchived(SignalStore.settings.shouldKeepMutedChatsArchived())
|
storiesDisabled = SignalStore.story.isFeatureDisabled
|
||||||
.setHasSetMyStoriesPrivacy(SignalStore.story.userHasBeenNotifiedAboutStories)
|
storyViewReceiptsEnabled = storyViewReceiptsState
|
||||||
.setHasViewedOnboardingStory(SignalStore.story.userHasViewedOnboardingStory)
|
hasSeenGroupStoryEducationSheet = SignalStore.story.userHasSeenGroupStoryEducationSheet
|
||||||
.setStoriesDisabled(SignalStore.story.isFeatureDisabled)
|
hasCompletedUsernameOnboarding = SignalStore.uiHints.hasCompletedUsernameOnboarding()
|
||||||
.setStoryViewReceiptsState(storyViewReceiptsState)
|
username = SignalStore.account.username ?: ""
|
||||||
.setHasSeenGroupStoryEducationSheet(SignalStore.story.userHasSeenGroupStoryEducationSheet)
|
usernameLink = SignalStore.account.usernameLink?.let { linkComponents ->
|
||||||
.setUsername(SignalStore.account.username)
|
AccountRecord.UsernameLink(
|
||||||
.setHasCompletedUsernameOnboarding(SignalStore.uiHints.hasCompletedUsernameOnboarding())
|
entropy = linkComponents.entropy.toByteString(),
|
||||||
|
serverId = linkComponents.serverId.toByteArray().toByteString(),
|
||||||
|
color = StorageSyncModels.localToRemoteUsernameColor(SignalStore.misc.usernameQrCodeColorScheme)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
val linkComponents = SignalStore.account.usernameLink
|
getSubscriber(InAppPaymentSubscriberRecord.Type.DONATION)?.let {
|
||||||
if (linkComponents != null) {
|
safeSetSubscriber(it.subscriberId.bytes.toByteString(), it.currency.currencyCode)
|
||||||
account.setUsernameLink(
|
}
|
||||||
AccountRecord.UsernameLink.Builder()
|
|
||||||
.entropy(linkComponents.entropy.toByteString())
|
getSubscriber(InAppPaymentSubscriberRecord.Type.BACKUP)?.let {
|
||||||
.serverId(linkComponents.serverId.toByteArray().toByteString())
|
safeSetBackupsSubscriber(it.subscriberId.bytes.toByteString(), it.currency.currencyCode)
|
||||||
.color(StorageSyncModels.localToRemoteUsernameColor(SignalStore.misc.usernameQrCodeColorScheme))
|
}
|
||||||
.build()
|
|
||||||
)
|
safeSetPayments(SignalStore.payments.mobileCoinPaymentsEnabled(), Optional.ofNullable(SignalStore.payments.paymentsEntropy).map { obj: Entropy -> obj.bytes }.orElse(null))
|
||||||
} else {
|
|
||||||
account.setUsernameLink(null)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return SignalStorageRecord.forAccount(account.build())
|
return SignalStorageRecord.forAccount(accountRecord.toSignalAccountRecord(StorageId.forAccount(storageId)))
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
@@ -188,62 +195,56 @@ object StorageSyncHelper {
|
|||||||
fun applyAccountStorageSyncUpdates(context: Context, self: Recipient, update: StorageRecordUpdate<SignalAccountRecord>, fetchProfile: Boolean) {
|
fun applyAccountStorageSyncUpdates(context: Context, self: Recipient, update: StorageRecordUpdate<SignalAccountRecord>, fetchProfile: Boolean) {
|
||||||
SignalDatabase.recipients.applyStorageSyncAccountUpdate(update)
|
SignalDatabase.recipients.applyStorageSyncAccountUpdate(update)
|
||||||
|
|
||||||
TextSecurePreferences.setReadReceiptsEnabled(context, update.new.isReadReceiptsEnabled)
|
TextSecurePreferences.setReadReceiptsEnabled(context, update.new.proto.readReceipts)
|
||||||
TextSecurePreferences.setTypingIndicatorsEnabled(context, update.new.isTypingIndicatorsEnabled)
|
TextSecurePreferences.setTypingIndicatorsEnabled(context, update.new.proto.typingIndicators)
|
||||||
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, update.new.isSealedSenderIndicatorsEnabled)
|
TextSecurePreferences.setShowUnidentifiedDeliveryIndicatorsEnabled(context, update.new.proto.sealedSenderIndicators)
|
||||||
SignalStore.settings.isLinkPreviewsEnabled = update.new.isLinkPreviewsEnabled
|
SignalStore.settings.isLinkPreviewsEnabled = update.new.proto.linkPreviews
|
||||||
SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode = if (update.new.isPhoneNumberUnlisted) PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE else PhoneNumberDiscoverabilityMode.DISCOVERABLE
|
SignalStore.phoneNumberPrivacy.phoneNumberDiscoverabilityMode = if (update.new.proto.unlistedPhoneNumber) PhoneNumberDiscoverabilityMode.NOT_DISCOVERABLE else PhoneNumberDiscoverabilityMode.DISCOVERABLE
|
||||||
SignalStore.phoneNumberPrivacy.phoneNumberSharingMode = StorageSyncModels.remoteToLocalPhoneNumberSharingMode(update.new.phoneNumberSharingMode)
|
SignalStore.phoneNumberPrivacy.phoneNumberSharingMode = StorageSyncModels.remoteToLocalPhoneNumberSharingMode(update.new.proto.phoneNumberSharingMode)
|
||||||
SignalStore.settings.isPreferSystemContactPhotos = update.new.isPreferContactAvatars
|
SignalStore.settings.isPreferSystemContactPhotos = update.new.proto.preferContactAvatars
|
||||||
SignalStore.payments.setEnabledAndEntropy(update.new.payments.isEnabled, Entropy.fromBytes(update.new.payments.entropy.orElse(null)))
|
SignalStore.payments.setEnabledAndEntropy(update.new.proto.payments?.enabled == true, Entropy.fromBytes(update.new.proto.payments?.entropy?.toByteArray()))
|
||||||
SignalStore.settings.universalExpireTimer = update.new.universalExpireTimer
|
SignalStore.settings.universalExpireTimer = update.new.proto.universalExpireTimer
|
||||||
SignalStore.emoji.reactions = update.new.defaultReactions
|
SignalStore.emoji.reactions = update.new.proto.preferredReactionEmoji
|
||||||
SignalStore.inAppPayments.setDisplayBadgesOnProfile(update.new.isDisplayBadgesOnProfile)
|
SignalStore.inAppPayments.setDisplayBadgesOnProfile(update.new.proto.displayBadgesOnProfile)
|
||||||
SignalStore.settings.setKeepMutedChatsArchived(update.new.isKeepMutedChatsArchived)
|
SignalStore.settings.setKeepMutedChatsArchived(update.new.proto.keepMutedChatsArchived)
|
||||||
SignalStore.story.userHasBeenNotifiedAboutStories = update.new.hasSetMyStoriesPrivacy()
|
SignalStore.story.userHasBeenNotifiedAboutStories = update.new.proto.hasSetMyStoriesPrivacy
|
||||||
SignalStore.story.userHasViewedOnboardingStory = update.new.hasViewedOnboardingStory()
|
SignalStore.story.userHasViewedOnboardingStory = update.new.proto.hasViewedOnboardingStory
|
||||||
SignalStore.story.isFeatureDisabled = update.new.isStoriesDisabled
|
SignalStore.story.isFeatureDisabled = update.new.proto.storiesDisabled
|
||||||
SignalStore.story.userHasSeenGroupStoryEducationSheet = update.new.hasSeenGroupStoryEducationSheet()
|
SignalStore.story.userHasSeenGroupStoryEducationSheet = update.new.proto.hasSeenGroupStoryEducationSheet
|
||||||
SignalStore.uiHints.setHasCompletedUsernameOnboarding(update.new.hasCompletedUsernameOnboarding())
|
SignalStore.uiHints.setHasCompletedUsernameOnboarding(update.new.proto.hasCompletedUsernameOnboarding)
|
||||||
|
|
||||||
if (update.new.storyViewReceiptsState == OptionalBool.UNSET) {
|
if (update.new.proto.storyViewReceiptsEnabled == OptionalBool.UNSET) {
|
||||||
SignalStore.story.viewedReceiptsEnabled = update.new.isReadReceiptsEnabled
|
SignalStore.story.viewedReceiptsEnabled = update.new.proto.readReceipts
|
||||||
} else {
|
} else {
|
||||||
SignalStore.story.viewedReceiptsEnabled = update.new.storyViewReceiptsState == OptionalBool.ENABLED
|
SignalStore.story.viewedReceiptsEnabled = update.new.proto.storyViewReceiptsEnabled == OptionalBool.ENABLED
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update.new.storyViewReceiptsState == OptionalBool.UNSET) {
|
val remoteSubscriber = StorageSyncModels.remoteToLocalSubscriber(update.new.proto.subscriberId, update.new.proto.subscriberCurrencyCode, InAppPaymentSubscriberRecord.Type.DONATION)
|
||||||
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)
|
|
||||||
if (remoteSubscriber != null) {
|
if (remoteSubscriber != null) {
|
||||||
setSubscriber(remoteSubscriber)
|
setSubscriber(remoteSubscriber)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update.new.isSubscriptionManuallyCancelled && !update.old.isSubscriptionManuallyCancelled) {
|
if (update.new.proto.subscriptionManuallyCancelled && !update.old.proto.subscriptionManuallyCancelled) {
|
||||||
SignalStore.inAppPayments.updateLocalStateForManualCancellation(InAppPaymentSubscriberRecord.Type.DONATION)
|
SignalStore.inAppPayments.updateLocalStateForManualCancellation(InAppPaymentSubscriberRecord.Type.DONATION)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fetchProfile && update.new.avatarUrlPath.isPresent) {
|
if (fetchProfile && update.new.proto.avatarUrlPath.isNotBlank()) {
|
||||||
AppDependencies.jobManager.add(RetrieveProfileAvatarJob(self, update.new.avatarUrlPath.get()))
|
AppDependencies.jobManager.add(RetrieveProfileAvatarJob(self, update.new.proto.avatarUrlPath))
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update.new.username != update.old.username) {
|
if (update.new.proto.username != update.old.proto.username) {
|
||||||
SignalStore.account.username = update.new.username
|
SignalStore.account.username = update.new.proto.username
|
||||||
SignalStore.account.usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
|
SignalStore.account.usernameSyncState = AccountValues.UsernameSyncState.IN_SYNC
|
||||||
SignalStore.account.usernameSyncErrorCount = 0
|
SignalStore.account.usernameSyncErrorCount = 0
|
||||||
}
|
}
|
||||||
|
|
||||||
if (update.new.usernameLink != null) {
|
if (update.new.proto.usernameLink != null) {
|
||||||
SignalStore.account.usernameLink = UsernameLinkComponents(
|
SignalStore.account.usernameLink = UsernameLinkComponents(
|
||||||
update.new.usernameLink!!.entropy.toByteArray(),
|
update.new.proto.usernameLink!!.entropy.toByteArray(),
|
||||||
UuidUtil.parseOrThrow(update.new.usernameLink!!.serverId.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
|
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.signal.libsignal.zkgroup.groups.GroupMasterKey
|
||||||
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.UsernameQrCodeColorScheme
|
import org.thoughtcrime.securesms.components.settings.app.usernamelinks.UsernameQrCodeColorScheme
|
||||||
import org.thoughtcrime.securesms.database.GroupTable.ShowAsStoryState
|
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.keyvalue.PhoneNumberPrivacyValues
|
||||||
import org.thoughtcrime.securesms.recipients.Recipient
|
import org.thoughtcrime.securesms.recipients.Recipient
|
||||||
import org.whispersystems.signalservice.api.push.SignalServiceAddress
|
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.SignalCallLinkRecord
|
||||||
import org.whispersystems.signalservice.api.storage.SignalContactRecord
|
import org.whispersystems.signalservice.api.storage.SignalContactRecord
|
||||||
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record
|
import org.whispersystems.signalservice.api.storage.SignalGroupV1Record
|
||||||
@@ -82,18 +84,33 @@ object StorageSyncModels {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
fun localToRemotePinnedConversations(records: List<RecipientRecord>): List<SignalAccountRecord.PinnedConversation> {
|
fun localToRemotePinnedConversations(records: List<RecipientRecord>): List<AccountRecord.PinnedConversation> {
|
||||||
return records
|
return records
|
||||||
.filter { it.recipientType == RecipientType.GV1 || it.recipientType == RecipientType.GV2 || it.registered == RecipientTable.RegisteredState.REGISTERED }
|
.filter { it.recipientType == RecipientType.GV1 || it.recipientType == RecipientType.GV2 || it.registered == RecipientTable.RegisteredState.REGISTERED }
|
||||||
.map { localToRemotePinnedConversation(it) }
|
.map { localToRemotePinnedConversation(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
@JvmStatic
|
@JvmStatic
|
||||||
private fun localToRemotePinnedConversation(settings: RecipientRecord): SignalAccountRecord.PinnedConversation {
|
private fun localToRemotePinnedConversation(settings: RecipientRecord): AccountRecord.PinnedConversation {
|
||||||
return when (settings.recipientType) {
|
return when (settings.recipientType) {
|
||||||
RecipientType.INDIVIDUAL -> SignalAccountRecord.PinnedConversation.forContact(SignalServiceAddress(settings.serviceId, settings.e164))
|
RecipientType.INDIVIDUAL -> {
|
||||||
RecipientType.GV1 -> SignalAccountRecord.PinnedConversation.forGroupV1(settings.groupId!!.requireV1().decodedId)
|
AccountRecord.PinnedConversation(
|
||||||
RecipientType.GV2 -> SignalAccountRecord.PinnedConversation.forGroupV2(settings.syncExtras.groupMasterKey!!.serialize())
|
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!")
|
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(
|
fun remoteToLocalSubscriber(
|
||||||
subscriber: SignalAccountRecord.Subscriber,
|
subscriberId: ByteString,
|
||||||
|
subscriberCurrencyCode: String,
|
||||||
type: InAppPaymentSubscriberRecord.Type
|
type: InAppPaymentSubscriberRecord.Type
|
||||||
): InAppPaymentSubscriberRecord? {
|
): InAppPaymentSubscriberRecord? {
|
||||||
if (subscriber.id.isPresent) {
|
if (subscriberId.isNotEmpty()) {
|
||||||
val subscriberId = SubscriberId.fromBytes(subscriber.id.get())
|
val subscriberId = SubscriberId.fromBytes(subscriberId.toByteArray())
|
||||||
val localSubscriberRecord = inAppPaymentSubscribers.getBySubscriberId(subscriberId)
|
val localSubscriberRecord = inAppPaymentSubscribers.getBySubscriberId(subscriberId)
|
||||||
val requiresCancel = localSubscriberRecord != null && localSubscriberRecord.requiresCancel
|
val requiresCancel = localSubscriberRecord != null && localSubscriberRecord.requiresCancel
|
||||||
val paymentMethodType = localSubscriberRecord?.paymentMethodType ?: InAppPaymentData.PaymentMethodType.UNKNOWN
|
val paymentMethodType = localSubscriberRecord?.paymentMethodType ?: InAppPaymentData.PaymentMethodType.UNKNOWN
|
||||||
|
|
||||||
val currency: Currency
|
val currency: Currency
|
||||||
if (subscriber.currencyCode.isEmpty) {
|
if (subscriberCurrencyCode.isBlank()) {
|
||||||
return null
|
return null
|
||||||
} else {
|
} else {
|
||||||
try {
|
try {
|
||||||
currency = Currency.getInstance(subscriber.currencyCode.get())
|
currency = Currency.getInstance(subscriberCurrencyCode)
|
||||||
} catch (e: IllegalArgumentException) {
|
} catch (e: IllegalArgumentException) {
|
||||||
return null
|
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!");
|
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);
|
.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);
|
return new StorageRecordUpdate<>(oldRecord, newRecord);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,14 @@ fun ByteString?.isNullOrEmpty(): Boolean {
|
|||||||
return this == null || this.size == 0
|
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.
|
* 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 {
|
tasks.withType<KotlinCompile>().configureEach {
|
||||||
kotlinOptions {
|
kotlinOptions {
|
||||||
jvmTarget = signalKotlinJvmTarget
|
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 okio.ByteString.Companion.toByteString
|
||||||
import org.whispersystems.signalservice.internal.storage.protos.CallLinkRecord
|
import org.whispersystems.signalservice.internal.storage.protos.CallLinkRecord
|
||||||
import java.io.IOException
|
import java.io.IOException
|
||||||
import java.util.LinkedList
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A record in storage service that represents a call link that was already created.
|
* 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 rootKey: ByteArray = proto.rootKey.toByteArray()
|
||||||
val adminPassKey: ByteArray = proto.adminPasskey.toByteArray()
|
val adminPassKey: ByteArray = proto.adminPasskey.toByteArray()
|
||||||
val deletionTimestamp: Long = proto.deletedAtTimestampMs
|
val deletionTimestamp: Long = proto.deletedAtTimestampMs
|
||||||
|
|
||||||
fun toProto(): CallLinkRecord {
|
|
||||||
return proto
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getId(): StorageId {
|
|
||||||
return id
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun asStorageRecord(): SignalStorageRecord {
|
override fun asStorageRecord(): SignalStorageRecord {
|
||||||
return SignalStorageRecord.forCallLink(this)
|
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 {
|
fun isDeleted(): Boolean {
|
||||||
return deletionTimestamp > 0
|
return deletionTimestamp > 0
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.whispersystems.signalservice.api.storage;
|
package org.whispersystems.signalservice.api.storage;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.signal.core.util.ProtoUtil;
|
import org.signal.core.util.ProtoUtil;
|
||||||
import org.signal.libsignal.protocol.logging.Log;
|
import org.signal.libsignal.protocol.logging.Log;
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||||
@@ -20,7 +21,7 @@ import javax.annotation.Nullable;
|
|||||||
|
|
||||||
import okio.ByteString;
|
import okio.ByteString;
|
||||||
|
|
||||||
public final class SignalContactRecord implements SignalRecord {
|
public final class SignalContactRecord implements SignalRecord<ContactRecord> {
|
||||||
|
|
||||||
private static final String TAG = SignalContactRecord.class.getSimpleName();
|
private static final String TAG = SignalContactRecord.class.getSimpleName();
|
||||||
|
|
||||||
@@ -69,124 +70,13 @@ public final class SignalContactRecord implements SignalRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SignalStorageRecord asStorageRecord() {
|
public ContactRecord getProto() {
|
||||||
return SignalStorageRecord.forContact(this);
|
return proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String describeDiff(SignalRecord other) {
|
public SignalStorageRecord asStorageRecord() {
|
||||||
if (other instanceof SignalContactRecord) {
|
return SignalStorageRecord.forContact(this);
|
||||||
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 boolean hasUnknownFields() {
|
public boolean hasUnknownFields() {
|
||||||
@@ -310,10 +200,6 @@ public final class SignalContactRecord implements SignalRecord {
|
|||||||
return new SignalContactRecord(id, proto.newBuilder().pni("").build());
|
return new SignalContactRecord(id, proto.newBuilder().pni("").build());
|
||||||
}
|
}
|
||||||
|
|
||||||
public ContactRecord toProto() {
|
|
||||||
return proto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import okio.ByteString;
|
import okio.ByteString;
|
||||||
|
|
||||||
public final class SignalGroupV1Record implements SignalRecord {
|
public final class SignalGroupV1Record implements SignalRecord<GroupV1Record> {
|
||||||
|
|
||||||
private static final String TAG = SignalGroupV1Record.class.getSimpleName();
|
private static final String TAG = SignalGroupV1Record.class.getSimpleName();
|
||||||
|
|
||||||
@@ -33,53 +33,13 @@ public final class SignalGroupV1Record implements SignalRecord {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public GroupV1Record getProto() {
|
||||||
public SignalStorageRecord asStorageRecord() {
|
return proto;
|
||||||
return SignalStorageRecord.forGroupV1(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String describeDiff(SignalRecord other) {
|
public SignalStorageRecord asStorageRecord() {
|
||||||
if (other instanceof SignalGroupV1Record) {
|
return SignalStorageRecord.forGroupV1(this);
|
||||||
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 boolean hasUnknownFields() {
|
public boolean hasUnknownFields() {
|
||||||
@@ -114,10 +74,6 @@ public final class SignalGroupV1Record implements SignalRecord {
|
|||||||
return proto.mutedUntilTimestamp;
|
return proto.mutedUntilTimestamp;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GroupV1Record toProto() {
|
|
||||||
return proto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.whispersystems.signalservice.api.storage;
|
package org.whispersystems.signalservice.api.storage;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.signal.core.util.ProtoUtil;
|
import org.signal.core.util.ProtoUtil;
|
||||||
import org.signal.libsignal.protocol.logging.Log;
|
import org.signal.libsignal.protocol.logging.Log;
|
||||||
import org.signal.libsignal.zkgroup.InvalidInputException;
|
import org.signal.libsignal.zkgroup.InvalidInputException;
|
||||||
@@ -14,7 +15,7 @@ import java.util.Objects;
|
|||||||
|
|
||||||
import okio.ByteString;
|
import okio.ByteString;
|
||||||
|
|
||||||
public final class SignalGroupV2Record implements SignalRecord {
|
public final class SignalGroupV2Record implements SignalRecord<GroupV2Record> {
|
||||||
|
|
||||||
private static final String TAG = SignalGroupV2Record.class.getSimpleName();
|
private static final String TAG = SignalGroupV2Record.class.getSimpleName();
|
||||||
|
|
||||||
@@ -35,65 +36,13 @@ public final class SignalGroupV2Record implements SignalRecord {
|
|||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override public GroupV2Record getProto() {
|
||||||
public SignalStorageRecord asStorageRecord() {
|
return proto;
|
||||||
return SignalStorageRecord.forGroupV2(this);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String describeDiff(SignalRecord other) {
|
public SignalStorageRecord asStorageRecord() {
|
||||||
if (other instanceof SignalGroupV2Record) {
|
return SignalStorageRecord.forGroupV2(this);
|
||||||
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 boolean hasUnknownFields() {
|
public boolean hasUnknownFields() {
|
||||||
@@ -148,10 +97,6 @@ public final class SignalGroupV2Record implements SignalRecord {
|
|||||||
return proto.storySendMode;
|
return proto.storySendMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
public GroupV2Record toProto() {
|
|
||||||
return proto;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
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();
|
StorageRecord.Builder builder = new StorageRecord.Builder();
|
||||||
|
|
||||||
if (record.getContact().isPresent()) {
|
if (record.getContact().isPresent()) {
|
||||||
builder.contact(record.getContact().get().toProto());
|
builder.contact(record.getContact().get().getProto());
|
||||||
} else if (record.getGroupV1().isPresent()) {
|
} else if (record.getGroupV1().isPresent()) {
|
||||||
builder.groupV1(record.getGroupV1().get().toProto());
|
builder.groupV1(record.getGroupV1().get().getProto());
|
||||||
} else if (record.getGroupV2().isPresent()) {
|
} else if (record.getGroupV2().isPresent()) {
|
||||||
builder.groupV2(record.getGroupV2().get().toProto());
|
builder.groupV2(record.getGroupV2().get().getProto());
|
||||||
} else if (record.getAccount().isPresent()) {
|
} else if (record.getAccount().isPresent()) {
|
||||||
builder.account(record.getAccount().get().toProto());
|
builder.account(record.getAccount().get().getProto());
|
||||||
} else if (record.getStoryDistributionList().isPresent()) {
|
} else if (record.getStoryDistributionList().isPresent()) {
|
||||||
builder.storyDistributionList(record.getStoryDistributionList().get().toProto());
|
builder.storyDistributionList(record.getStoryDistributionList().get().getProto());
|
||||||
} else if (record.getCallLink().isPresent()) {
|
} else if (record.getCallLink().isPresent()) {
|
||||||
builder.callLink(record.getCallLink().get().toProto());
|
builder.callLink(record.getCallLink().get().getProto());
|
||||||
} else {
|
} else {
|
||||||
throw new InvalidStorageWriteError();
|
throw new InvalidStorageWriteError();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import org.whispersystems.signalservice.internal.storage.protos.CallLinkRecord;
|
|||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class SignalStorageRecord implements SignalRecord {
|
public class SignalStorageRecord {
|
||||||
|
|
||||||
private final StorageId id;
|
private final StorageId id;
|
||||||
private final Optional<SignalStoryDistributionListRecord> storyDistributionList;
|
private final Optional<SignalStoryDistributionListRecord> storyDistributionList;
|
||||||
@@ -89,21 +89,10 @@ public class SignalStorageRecord implements SignalRecord {
|
|||||||
this.callLink = callLink;
|
this.callLink = callLink;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public StorageId getId() {
|
public StorageId getId() {
|
||||||
return id;
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public SignalStorageRecord asStorageRecord() {
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public String describeDiff(SignalRecord other) {
|
|
||||||
return "Diffs not supported.";
|
|
||||||
}
|
|
||||||
|
|
||||||
public int getType() {
|
public int getType() {
|
||||||
return id.getType();
|
return id.getType();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
package org.whispersystems.signalservice.api.storage;
|
package org.whispersystems.signalservice.api.storage;
|
||||||
|
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.signal.core.util.ProtoUtil;
|
import org.signal.core.util.ProtoUtil;
|
||||||
import org.signal.libsignal.protocol.logging.Log;
|
import org.signal.libsignal.protocol.logging.Log;
|
||||||
import org.whispersystems.signalservice.api.push.ServiceId;
|
import org.whispersystems.signalservice.api.push.ServiceId;
|
||||||
@@ -15,7 +16,7 @@ import java.util.stream.Collectors;
|
|||||||
|
|
||||||
import okio.ByteString;
|
import okio.ByteString;
|
||||||
|
|
||||||
public class SignalStoryDistributionListRecord implements SignalRecord {
|
public class SignalStoryDistributionListRecord implements SignalRecord<StoryDistributionListRecord> {
|
||||||
|
|
||||||
private static final String TAG = SignalStoryDistributionListRecord.class.getSimpleName();
|
private static final String TAG = SignalStoryDistributionListRecord.class.getSimpleName();
|
||||||
|
|
||||||
@@ -42,12 +43,13 @@ public class SignalStoryDistributionListRecord implements SignalRecord {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public SignalStorageRecord asStorageRecord() {
|
public StoryDistributionListRecord getProto() {
|
||||||
return SignalStorageRecord.forStoryDistributionList(this);
|
return proto;
|
||||||
}
|
}
|
||||||
|
|
||||||
public StoryDistributionListRecord toProto() {
|
@Override
|
||||||
return proto;
|
public SignalStorageRecord asStorageRecord() {
|
||||||
|
return SignalStorageRecord.forStoryDistributionList(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public byte[] serializeUnknownFields() {
|
public byte[] serializeUnknownFields() {
|
||||||
@@ -78,46 +80,6 @@ public class SignalStoryDistributionListRecord implements SignalRecord {
|
|||||||
return proto.isBlockList;
|
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
|
@Override
|
||||||
public boolean equals(Object o) {
|
public boolean equals(Object o) {
|
||||||
if (this == o) return true;
|
if (this == o) return true;
|
||||||
|
|||||||
Reference in New Issue
Block a user