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