]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java
18dd1869ae6f7d7dba84f5caf7ac362b9d247896
[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.util.OptionalUtil;
16 import org.whispersystems.signalservice.api.util.UuidUtil;
17 import org.whispersystems.signalservice.internal.storage.protos.OptionalBool;
18
19 import java.sql.Connection;
20 import java.sql.SQLException;
21 import java.util.Arrays;
22 import java.util.Optional;
23
24 /**
25 * Processes {@link SignalAccountRecord}s.
26 */
27 public class AccountRecordProcessor extends DefaultStorageRecordProcessor<SignalAccountRecord> {
28
29 private static final Logger logger = LoggerFactory.getLogger(AccountRecordProcessor.class);
30 private final SignalAccountRecord localAccountRecord;
31 private final SignalAccount account;
32 private final Connection connection;
33 private final JobExecutor jobExecutor;
34
35 public AccountRecordProcessor(
36 SignalAccount account,
37 Connection connection,
38 final JobExecutor jobExecutor
39 ) throws SQLException {
40 this.account = account;
41 this.connection = connection;
42 this.jobExecutor = jobExecutor;
43 final var selfRecipientId = account.getSelfRecipientId();
44 final var recipient = account.getRecipientStore().getRecipient(connection, selfRecipientId);
45 final var storageId = account.getRecipientStore().getSelfStorageId(connection);
46 this.localAccountRecord = StorageSyncModels.localToRemoteRecord(account.getConfigurationStore(),
47 recipient,
48 account.getUsernameLink(),
49 storageId.getRaw()).getAccount().get();
50 }
51
52 @Override
53 protected boolean isInvalid(SignalAccountRecord remote) {
54 return false;
55 }
56
57 @Override
58 protected Optional<SignalAccountRecord> getMatching(SignalAccountRecord record) {
59 return Optional.of(localAccountRecord);
60 }
61
62 @Override
63 protected SignalAccountRecord merge(SignalAccountRecord remote, SignalAccountRecord local) {
64 String givenName;
65 String familyName;
66 if (remote.getGivenName().isPresent() || remote.getFamilyName().isPresent()) {
67 givenName = remote.getGivenName().orElse("");
68 familyName = remote.getFamilyName().orElse("");
69 } else {
70 givenName = local.getGivenName().orElse("");
71 familyName = local.getFamilyName().orElse("");
72 }
73
74 final var payments = remote.getPayments().getEntropy().isPresent() ? remote.getPayments() : local.getPayments();
75 final var subscriber = remote.getSubscriber().getId().isPresent()
76 ? remote.getSubscriber()
77 : local.getSubscriber();
78 final var storyViewReceiptsState = remote.getStoryViewReceiptsState() == OptionalBool.UNSET
79 ? local.getStoryViewReceiptsState()
80 : remote.getStoryViewReceiptsState();
81 final var unknownFields = remote.serializeUnknownFields();
82 final var avatarUrlPath = OptionalUtil.or(remote.getAvatarUrlPath(), local.getAvatarUrlPath()).orElse("");
83 final var profileKey = OptionalUtil.or(remote.getProfileKey(), local.getProfileKey()).orElse(null);
84 final var noteToSelfArchived = remote.isNoteToSelfArchived();
85 final var noteToSelfForcedUnread = remote.isNoteToSelfForcedUnread();
86 final var readReceipts = remote.isReadReceiptsEnabled();
87 final var typingIndicators = remote.isTypingIndicatorsEnabled();
88 final var sealedSenderIndicators = remote.isSealedSenderIndicatorsEnabled();
89 final var linkPreviews = remote.isLinkPreviewsEnabled();
90 final var unlisted = remote.isPhoneNumberUnlisted();
91 final var pinnedConversations = remote.getPinnedConversations();
92 final var phoneNumberSharingMode = remote.getPhoneNumberSharingMode();
93 final var preferContactAvatars = remote.isPreferContactAvatars();
94 final var universalExpireTimer = remote.getUniversalExpireTimer();
95 final var e164 = account.isPrimaryDevice() ? local.getE164() : remote.getE164();
96 final var defaultReactions = !remote.getDefaultReactions().isEmpty()
97 ? remote.getDefaultReactions()
98 : local.getDefaultReactions();
99 final var displayBadgesOnProfile = remote.isDisplayBadgesOnProfile();
100 final var subscriptionManuallyCancelled = remote.isSubscriptionManuallyCancelled();
101 final var keepMutedChatsArchived = remote.isKeepMutedChatsArchived();
102 final var hasSetMyStoriesPrivacy = remote.hasSetMyStoriesPrivacy();
103 final var hasViewedOnboardingStory = remote.hasViewedOnboardingStory() || local.hasViewedOnboardingStory();
104 final var storiesDisabled = remote.isStoriesDisabled();
105 final var hasSeenGroupStoryEducation = remote.hasSeenGroupStoryEducationSheet()
106 || local.hasSeenGroupStoryEducationSheet();
107 boolean hasSeenUsernameOnboarding = remote.hasCompletedUsernameOnboarding()
108 || local.hasCompletedUsernameOnboarding();
109 final var username = remote.getUsername();
110 final var usernameLink = remote.getUsernameLink();
111
112 final var mergedBuilder = new SignalAccountRecord.Builder(remote.getId().getRaw(), unknownFields).setGivenName(
113 givenName)
114 .setFamilyName(familyName)
115 .setAvatarUrlPath(avatarUrlPath)
116 .setProfileKey(profileKey)
117 .setNoteToSelfArchived(noteToSelfArchived)
118 .setNoteToSelfForcedUnread(noteToSelfForcedUnread)
119 .setReadReceiptsEnabled(readReceipts)
120 .setTypingIndicatorsEnabled(typingIndicators)
121 .setSealedSenderIndicatorsEnabled(sealedSenderIndicators)
122 .setLinkPreviewsEnabled(linkPreviews)
123 .setUnlistedPhoneNumber(unlisted)
124 .setPhoneNumberSharingMode(phoneNumberSharingMode)
125 .setPinnedConversations(pinnedConversations)
126 .setPreferContactAvatars(preferContactAvatars)
127 .setPayments(payments.isEnabled(), payments.getEntropy().orElse(null))
128 .setUniversalExpireTimer(universalExpireTimer)
129 .setDefaultReactions(defaultReactions)
130 .setSubscriber(subscriber)
131 .setDisplayBadgesOnProfile(displayBadgesOnProfile)
132 .setSubscriptionManuallyCancelled(subscriptionManuallyCancelled)
133 .setKeepMutedChatsArchived(keepMutedChatsArchived)
134 .setHasSetMyStoriesPrivacy(hasSetMyStoriesPrivacy)
135 .setHasViewedOnboardingStory(hasViewedOnboardingStory)
136 .setStoriesDisabled(storiesDisabled)
137 .setHasSeenGroupStoryEducationSheet(hasSeenGroupStoryEducation)
138 .setHasCompletedUsernameOnboarding(hasSeenUsernameOnboarding)
139 .setStoryViewReceiptsState(storyViewReceiptsState)
140 .setUsername(username)
141 .setUsernameLink(usernameLink)
142 .setE164(e164);
143 final var merged = mergedBuilder.build();
144
145 final var matchesRemote = doProtosMatch(merged, remote);
146 if (matchesRemote) {
147 return remote;
148 }
149
150 final var matchesLocal = doProtosMatch(merged, local);
151 if (matchesLocal) {
152 return local;
153 }
154
155 return mergedBuilder.setId(KeyUtils.createRawStorageId()).build();
156 }
157
158 @Override
159 protected void insertLocal(SignalAccountRecord record) {
160 throw new UnsupportedOperationException(
161 "We should always have a local AccountRecord, so we should never been inserting a new one.");
162 }
163
164 @Override
165 protected void updateLocal(StorageRecordUpdate<SignalAccountRecord> update) throws SQLException {
166 final var accountRecord = update.newRecord();
167
168 if (!accountRecord.getE164().equals(account.getNumber())) {
169 jobExecutor.enqueueJob(new CheckWhoAmIJob());
170 }
171
172 account.getConfigurationStore().setReadReceipts(connection, accountRecord.isReadReceiptsEnabled());
173 account.getConfigurationStore().setTypingIndicators(connection, accountRecord.isTypingIndicatorsEnabled());
174 account.getConfigurationStore()
175 .setUnidentifiedDeliveryIndicators(connection, accountRecord.isSealedSenderIndicatorsEnabled());
176 account.getConfigurationStore().setLinkPreviews(connection, accountRecord.isLinkPreviewsEnabled());
177 account.getConfigurationStore()
178 .setPhoneNumberSharingMode(connection,
179 StorageSyncModels.remoteToLocal(accountRecord.getPhoneNumberSharingMode()));
180 account.getConfigurationStore().setPhoneNumberUnlisted(connection, accountRecord.isPhoneNumberUnlisted());
181
182 account.setUsername(accountRecord.getUsername() != null && !accountRecord.getUsername().isEmpty()
183 ? accountRecord.getUsername()
184 : null);
185 if (accountRecord.getUsernameLink() != null) {
186 final var usernameLink = accountRecord.getUsernameLink();
187 account.setUsernameLink(new UsernameLinkComponents(usernameLink.entropy.toByteArray(),
188 UuidUtil.parseOrThrow(usernameLink.serverId.toByteArray())));
189 account.getConfigurationStore().setUsernameLinkColor(connection, usernameLink.color.name());
190 }
191
192 if (accountRecord.getProfileKey().isPresent()) {
193 ProfileKey profileKey;
194 try {
195 profileKey = new ProfileKey(accountRecord.getProfileKey().get());
196 } catch (InvalidInputException e) {
197 logger.debug("Received invalid profile key from storage");
198 profileKey = null;
199 }
200 if (profileKey != null) {
201 account.setProfileKey(profileKey);
202 final var avatarPath = accountRecord.getAvatarUrlPath().orElse(null);
203 jobExecutor.enqueueJob(new DownloadProfileAvatarJob(avatarPath));
204 }
205 }
206
207 final var profile = account.getRecipientStore().getProfile(connection, account.getSelfRecipientId());
208 final var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile);
209 builder.withGivenName(accountRecord.getGivenName().orElse(null));
210 builder.withFamilyName(accountRecord.getFamilyName().orElse(null));
211 account.getRecipientStore().storeProfile(connection, account.getSelfRecipientId(), builder.build());
212 account.getRecipientStore()
213 .storeStorageRecord(connection,
214 account.getSelfRecipientId(),
215 accountRecord.getId(),
216 accountRecord.toProto().encode());
217 }
218
219 @Override
220 public int compare(SignalAccountRecord lhs, SignalAccountRecord rhs) {
221 return 0;
222 }
223
224 private static boolean doProtosMatch(SignalAccountRecord merged, SignalAccountRecord other) {
225 return Arrays.equals(merged.toProto().encode(), other.toProto().encode());
226 }
227 }