]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/syncStorage/AccountRecordProcessor.java
acce82e6ce27be614e59cf2105edc70faa360590
[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 final var username = remote.getUsername();
106 final var usernameLink = remote.getUsernameLink();
107
108 final var mergedBuilder = new SignalAccountRecord.Builder(remote.getId().getRaw(), unknownFields).setGivenName(
109 givenName)
110 .setFamilyName(familyName)
111 .setAvatarUrlPath(avatarUrlPath)
112 .setProfileKey(profileKey)
113 .setNoteToSelfArchived(noteToSelfArchived)
114 .setNoteToSelfForcedUnread(noteToSelfForcedUnread)
115 .setReadReceiptsEnabled(readReceipts)
116 .setTypingIndicatorsEnabled(typingIndicators)
117 .setSealedSenderIndicatorsEnabled(sealedSenderIndicators)
118 .setLinkPreviewsEnabled(linkPreviews)
119 .setUnlistedPhoneNumber(unlisted)
120 .setPhoneNumberSharingMode(phoneNumberSharingMode)
121 .setPinnedConversations(pinnedConversations)
122 .setPreferContactAvatars(preferContactAvatars)
123 .setPayments(payments.isEnabled(), payments.getEntropy().orElse(null))
124 .setUniversalExpireTimer(universalExpireTimer)
125 .setDefaultReactions(defaultReactions)
126 .setSubscriber(subscriber)
127 .setDisplayBadgesOnProfile(displayBadgesOnProfile)
128 .setSubscriptionManuallyCancelled(subscriptionManuallyCancelled)
129 .setKeepMutedChatsArchived(keepMutedChatsArchived)
130 .setHasSetMyStoriesPrivacy(hasSetMyStoriesPrivacy)
131 .setHasViewedOnboardingStory(hasViewedOnboardingStory)
132 .setStoriesDisabled(storiesDisabled)
133 .setHasSeenGroupStoryEducationSheet(hasSeenGroupStoryEducation)
134 .setStoryViewReceiptsState(storyViewReceiptsState)
135 .setUsername(username)
136 .setUsernameLink(usernameLink)
137 .setE164(e164);
138 final var merged = mergedBuilder.build();
139
140 final var matchesRemote = doProtosMatch(merged, remote);
141 if (matchesRemote) {
142 return remote;
143 }
144
145 final var matchesLocal = doProtosMatch(merged, local);
146 if (matchesLocal) {
147 return local;
148 }
149
150 return mergedBuilder.setId(KeyUtils.createRawStorageId()).build();
151 }
152
153 @Override
154 protected void insertLocal(SignalAccountRecord record) {
155 throw new UnsupportedOperationException(
156 "We should always have a local AccountRecord, so we should never been inserting a new one.");
157 }
158
159 @Override
160 protected void updateLocal(StorageRecordUpdate<SignalAccountRecord> update) throws SQLException {
161 final var accountRecord = update.newRecord();
162
163 if (!accountRecord.getE164().equals(account.getNumber())) {
164 jobExecutor.enqueueJob(new CheckWhoAmIJob());
165 }
166
167 account.getConfigurationStore().setReadReceipts(connection, accountRecord.isReadReceiptsEnabled());
168 account.getConfigurationStore().setTypingIndicators(connection, accountRecord.isTypingIndicatorsEnabled());
169 account.getConfigurationStore()
170 .setUnidentifiedDeliveryIndicators(connection, accountRecord.isSealedSenderIndicatorsEnabled());
171 account.getConfigurationStore().setLinkPreviews(connection, accountRecord.isLinkPreviewsEnabled());
172 account.getConfigurationStore()
173 .setPhoneNumberSharingMode(connection,
174 StorageSyncModels.remoteToLocal(accountRecord.getPhoneNumberSharingMode()));
175 account.getConfigurationStore().setPhoneNumberUnlisted(connection, accountRecord.isPhoneNumberUnlisted());
176
177 account.setUsername(accountRecord.getUsername() != null && !accountRecord.getUsername().isEmpty()
178 ? accountRecord.getUsername()
179 : null);
180 if (accountRecord.getUsernameLink() != null) {
181 final var usernameLink = accountRecord.getUsernameLink();
182 account.setUsernameLink(new UsernameLinkComponents(usernameLink.entropy.toByteArray(),
183 UuidUtil.parseOrThrow(usernameLink.serverId.toByteArray())));
184 account.getConfigurationStore().setUsernameLinkColor(connection, usernameLink.color.name());
185 }
186
187 if (accountRecord.getProfileKey().isPresent()) {
188 ProfileKey profileKey;
189 try {
190 profileKey = new ProfileKey(accountRecord.getProfileKey().get());
191 } catch (InvalidInputException e) {
192 logger.debug("Received invalid profile key from storage");
193 profileKey = null;
194 }
195 if (profileKey != null) {
196 account.setProfileKey(profileKey);
197 final var avatarPath = accountRecord.getAvatarUrlPath().orElse(null);
198 jobExecutor.enqueueJob(new DownloadProfileAvatarJob(avatarPath));
199 }
200 }
201
202 final var profile = account.getRecipientStore().getProfile(connection, account.getSelfRecipientId());
203 final var builder = profile == null ? Profile.newBuilder() : Profile.newBuilder(profile);
204 builder.withGivenName(accountRecord.getGivenName().orElse(null));
205 builder.withFamilyName(accountRecord.getFamilyName().orElse(null));
206 account.getRecipientStore().storeProfile(connection, account.getSelfRecipientId(), builder.build());
207 account.getRecipientStore()
208 .storeStorageRecord(connection,
209 account.getSelfRecipientId(),
210 accountRecord.getId(),
211 accountRecord.toProto().encode());
212 }
213
214 @Override
215 public int compare(SignalAccountRecord lhs, SignalAccountRecord rhs) {
216 return 0;
217 }
218
219 private static boolean doProtosMatch(SignalAccountRecord merged, SignalAccountRecord other) {
220 return Arrays.equals(merged.toProto().encode(), other.toProto().encode());
221 }
222 }