]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java
b2f7dd2931801d0c306e2f5ccd35e33c6e1b1f75
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / syncStorage / AccountRecordProcessor.java
1 package org.asamk.signal.manager.syncStorage;
2
3 import org.asamk.signal.manager.api.Profile;
4 import org.asamk.signal.manager.internal.JobExecutor;
5 import org.asamk.signal.manager.jobs.CheckWhoAmIJob;
6 import org.asamk.signal.manager.jobs.DownloadProfileAvatarJob;
7 import org.asamk.signal.manager.storage.SignalAccount;
8 import org.asamk.signal.manager.util.KeyUtils;
9 import org.signal.libsignal.zkgroup.InvalidInputException;
10 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
11 import org.slf4j.Logger;
12 import org.slf4j.LoggerFactory;
13 import org.whispersystems.signalservice.api.push.UsernameLinkComponents;
14 import org.whispersystems.signalservice.api.storage.SignalAccountRecord;
15 import org.whispersystems.signalservice.api.storage.StorageId;
16 import org.whispersystems.signalservice.api.util.UuidUtil;
17 import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
18 import org.whispersystems.signalservice.internal.storage.protos.OptionalBool;
19
20 import java.sql.Connection;
21 import java.sql.SQLException;
22 import java.util.Arrays;
23 import java.util.Optional;
24
25 import static org.asamk.signal.manager.util.Utils.firstNonEmpty;
26 import static org.asamk.signal.manager.util.Utils.firstNonNull;
27
28 /**
29 * Processes {@link SignalAccountRecord}s.
30 */
31 public class AccountRecordProcessor extends DefaultStorageRecordProcessor<SignalAccountRecord> {
32
33 private static final Logger logger = LoggerFactory.getLogger(AccountRecordProcessor.class);
34 private final SignalAccountRecord localAccountRecord;
35 private final SignalAccount account;
36 private final Connection connection;
37 private final JobExecutor jobExecutor;
38
39 public AccountRecordProcessor(
40 SignalAccount account,
41 Connection connection,
42 final JobExecutor jobExecutor
43 ) throws SQLException {
44 this.account = account;
45 this.connection = connection;
46 this.jobExecutor = jobExecutor;
47 final var selfRecipientId = account.getSelfRecipientId();
48 final var recipient = account.getRecipientStore().getRecipient(connection, selfRecipientId);
49 final var storageId = account.getRecipientStore().getSelfStorageId(connection);
50 this.localAccountRecord = new SignalAccountRecord(storageId,
51 StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(),
52 recipient,
53 account.getUsernameLink()));
54 }
55
56 @Override
57 protected boolean isInvalid(SignalAccountRecord remote) {
58 return false;
59 }
60
61 @Override
62 protected Optional<SignalAccountRecord> getMatching(SignalAccountRecord record) {
63 return Optional.of(localAccountRecord);
64 }
65
66 @Override
67 protected SignalAccountRecord merge(SignalAccountRecord remoteRecord, SignalAccountRecord localRecord) {
68 final var remote = remoteRecord.getProto();
69 final var local = localRecord.getProto();
70 String givenName;
71 String familyName;
72 if (!remote.givenName.isEmpty() || !remote.familyName.isEmpty()) {
73 givenName = remote.givenName;
74 familyName = remote.familyName;
75 } else {
76 givenName = local.givenName;
77 familyName = local.familyName;
78 }
79
80 final var mergedBuilder = SignalAccountRecord.Companion.newBuilder(remote.unknownFields().toByteArray())
81 .givenName(givenName)
82 .familyName(familyName)
83 .avatarUrlPath(firstNonEmpty(remote.avatarUrlPath, local.avatarUrlPath))
84 .profileKey(firstNonEmpty(remote.profileKey, local.profileKey))
85 .noteToSelfArchived(remote.noteToSelfArchived)
86 .noteToSelfMarkedUnread(remote.noteToSelfMarkedUnread)
87 .readReceipts(remote.readReceipts)
88 .typingIndicators(remote.typingIndicators)
89 .sealedSenderIndicators(remote.sealedSenderIndicators)
90 .linkPreviews(remote.linkPreviews)
91 .unlistedPhoneNumber(remote.unlistedPhoneNumber)
92 .phoneNumberSharingMode(remote.phoneNumberSharingMode)
93 .pinnedConversations(remote.pinnedConversations)
94 .preferContactAvatars(remote.preferContactAvatars)
95 .universalExpireTimer(remote.universalExpireTimer)
96 .preferredReactionEmoji(firstNonEmpty(remote.preferredReactionEmoji, local.preferredReactionEmoji))
97 .subscriberId(firstNonEmpty(remote.subscriberId, local.subscriberId))
98 .subscriberCurrencyCode(firstNonEmpty(remote.subscriberCurrencyCode, local.subscriberCurrencyCode))
99 .backupsSubscriberId(firstNonEmpty(remote.backupsSubscriberId, local.backupsSubscriberId))
100 .backupsSubscriberCurrencyCode(firstNonEmpty(remote.backupsSubscriberCurrencyCode,
101 local.backupsSubscriberCurrencyCode))
102 .displayBadgesOnProfile(remote.displayBadgesOnProfile)
103 .subscriptionManuallyCancelled(remote.subscriptionManuallyCancelled)
104 .keepMutedChatsArchived(remote.keepMutedChatsArchived)
105 .hasSetMyStoriesPrivacy(remote.hasSetMyStoriesPrivacy)
106 .hasViewedOnboardingStory(remote.hasViewedOnboardingStory || local.hasViewedOnboardingStory)
107 .storiesDisabled(remote.storiesDisabled)
108 .hasSeenGroupStoryEducationSheet(remote.hasSeenGroupStoryEducationSheet
109 || local.hasSeenGroupStoryEducationSheet)
110 .hasCompletedUsernameOnboarding(remote.hasCompletedUsernameOnboarding
111 || local.hasCompletedUsernameOnboarding)
112 .storyViewReceiptsEnabled(remote.storyViewReceiptsEnabled == OptionalBool.UNSET
113 ? local.storyViewReceiptsEnabled
114 : remote.storyViewReceiptsEnabled)
115 .username(remote.username)
116 .usernameLink(remote.usernameLink)
117 .e164(account.isPrimaryDevice() ? local.e164 : remote.e164);
118 if (firstNonNull(remote.payments, local.payments) != null) {
119 mergedBuilder.payments(firstNonNull(remote.payments, local.payments));
120 }
121 final var merged = mergedBuilder.build();
122
123 final var matchesRemote = doProtosMatch(merged, remote);
124 if (matchesRemote) {
125 return remoteRecord;
126 }
127
128 final var matchesLocal = doProtosMatch(merged, local);
129 if (matchesLocal) {
130 return localRecord;
131 }
132
133 return new SignalAccountRecord(StorageId.forAccount(KeyUtils.createRawStorageId()), mergedBuilder.build());
134 }
135
136 @Override
137 protected void insertLocal(SignalAccountRecord record) {
138 throw new UnsupportedOperationException(
139 "We should always have a local AccountRecord, so we should never been inserting a new one.");
140 }
141
142 @Override
143 protected void updateLocal(StorageRecordUpdate<SignalAccountRecord> update) throws SQLException {
144 final var accountRecord = update.newRecord();
145 final var accountProto = accountRecord.getProto();
146
147 if (!accountProto.e164.equals(account.getNumber())) {
148 jobExecutor.enqueueJob(new CheckWhoAmIJob());
149 }
150
151 account.getConfigurationStore().setReadReceipts(connection, accountProto.readReceipts);
152 account.getConfigurationStore().setTypingIndicators(connection, accountProto.typingIndicators);
153 account.getConfigurationStore()
154 .setUnidentifiedDeliveryIndicators(connection, accountProto.sealedSenderIndicators);
155 account.getConfigurationStore().setLinkPreviews(connection, accountProto.linkPreviews);
156 account.getConfigurationStore()
157 .setPhoneNumberSharingMode(connection,
158 StorageSyncModels.remoteToLocal(accountProto.phoneNumberSharingMode));
159 account.getConfigurationStore().setPhoneNumberUnlisted(connection, accountProto.unlistedPhoneNumber);
160
161 account.setUsername(!accountProto.username.isEmpty() ? accountProto.username : null);
162 if (accountProto.usernameLink != null) {
163 final var usernameLink = accountProto.usernameLink;
164 account.setUsernameLink(new UsernameLinkComponents(usernameLink.entropy.toByteArray(),
165 UuidUtil.parseOrThrow(usernameLink.serverId.toByteArray())));
166 account.getConfigurationStore().setUsernameLinkColor(connection, usernameLink.color.name());
167 }
168
169 if (accountProto.profileKey.size() > 0) {
170 ProfileKey profileKey;
171 try {
172 profileKey = new ProfileKey(accountProto.profileKey.toByteArray());
173 } catch (InvalidInputException e) {
174 logger.debug("Received invalid profile key from storage");
175 profileKey = null;
176 }
177 if (profileKey != null) {
178 account.setProfileKey(profileKey);
179 final var avatarPath = accountProto.avatarUrlPath.isEmpty() ? null : accountProto.avatarUrlPath;
180 jobExecutor.enqueueJob(new DownloadProfileAvatarJob(avatarPath));
181 }
182 }
183
184 final var profile = account.getRecipientStore().getProfile(connection, account.getSelfRecipientId());
185 final var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile);
186 builder.withGivenName(accountProto.givenName);
187 builder.withFamilyName(accountProto.familyName);
188 account.getRecipientStore().storeProfile(connection, account.getSelfRecipientId(), builder.build());
189 account.getRecipientStore()
190 .storeStorageRecord(connection,
191 account.getSelfRecipientId(),
192 accountRecord.getId(),
193 accountProto.encode());
194 }
195
196 @Override
197 public int compare(SignalAccountRecord lhs, SignalAccountRecord rhs) {
198 return 0;
199 }
200
201 private static boolean doProtosMatch(AccountRecord merged, AccountRecord other) {
202 return Arrays.equals(merged.encode(), other.encode());
203 }
204 }