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