]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/recipients/LegacyRecipientStore2.java
0c05e0c055de2216448bfd57fcac737d2d51d6ee
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / recipients / LegacyRecipientStore2.java
1 package org.asamk.signal.manager.storage.recipients;
2
3 import org.asamk.signal.manager.storage.Utils;
4 import org.signal.libsignal.zkgroup.InvalidInputException;
5 import org.signal.libsignal.zkgroup.profiles.ExpiringProfileKeyCredential;
6 import org.signal.libsignal.zkgroup.profiles.ProfileKey;
7 import org.slf4j.Logger;
8 import org.slf4j.LoggerFactory;
9 import org.whispersystems.signalservice.api.util.UuidUtil;
10
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.FileNotFoundException;
14 import java.io.IOException;
15 import java.nio.file.Files;
16 import java.util.Base64;
17 import java.util.List;
18 import java.util.Objects;
19 import java.util.Optional;
20 import java.util.Set;
21 import java.util.stream.Collectors;
22
23 public class LegacyRecipientStore2 {
24
25 private final static Logger logger = LoggerFactory.getLogger(LegacyRecipientStore2.class);
26
27 public static void migrate(File file, RecipientStore recipientStore) {
28 final var objectMapper = Utils.createStorageObjectMapper();
29 try (var inputStream = new FileInputStream(file)) {
30 final var storage = objectMapper.readValue(inputStream, Storage.class);
31
32 final var recipients = storage.recipients.stream().map(r -> {
33 final var recipientId = new RecipientId(r.id, recipientStore);
34 final var address = new RecipientAddress(Optional.ofNullable(r.uuid).map(UuidUtil::parseOrThrow),
35 Optional.ofNullable(r.number));
36
37 Contact contact = null;
38 if (r.contact != null) {
39 contact = new Contact(r.contact.name,
40 null,
41 r.contact.color,
42 r.contact.messageExpirationTime,
43 r.contact.blocked,
44 r.contact.archived,
45 r.contact.profileSharingEnabled);
46 }
47
48 ProfileKey profileKey = null;
49 if (r.profileKey != null) {
50 try {
51 profileKey = new ProfileKey(Base64.getDecoder().decode(r.profileKey));
52 } catch (InvalidInputException ignored) {
53 }
54 }
55
56 ExpiringProfileKeyCredential expiringProfileKeyCredential = null;
57 if (r.expiringProfileKeyCredential != null) {
58 try {
59 expiringProfileKeyCredential = new ExpiringProfileKeyCredential(Base64.getDecoder()
60 .decode(r.expiringProfileKeyCredential));
61 } catch (Throwable ignored) {
62 }
63 }
64
65 Profile profile = null;
66 if (r.profile != null) {
67 profile = new Profile(r.profile.lastUpdateTimestamp,
68 r.profile.givenName,
69 r.profile.familyName,
70 r.profile.about,
71 r.profile.aboutEmoji,
72 r.profile.avatarUrlPath,
73 r.profile.mobileCoinAddress == null
74 ? null
75 : Base64.getDecoder().decode(r.profile.mobileCoinAddress),
76 Profile.UnidentifiedAccessMode.valueOfOrUnknown(r.profile.unidentifiedAccessMode),
77 r.profile.capabilities.stream()
78 .map(Profile.Capability::valueOfOrNull)
79 .filter(Objects::nonNull)
80 .collect(Collectors.toSet()));
81 }
82
83 return new Recipient(recipientId, address, contact, profileKey, expiringProfileKeyCredential, profile);
84 }).collect(Collectors.toMap(Recipient::getRecipientId, r -> r));
85
86 recipientStore.addLegacyRecipients(recipients);
87 } catch (FileNotFoundException e) {
88 // nothing to migrate
89 } catch (IOException e) {
90 logger.warn("Failed to load recipient store", e);
91 throw new RuntimeException(e);
92 }
93 try {
94 Files.delete(file.toPath());
95 } catch (IOException e) {
96 logger.warn("Failed to load recipient store", e);
97 throw new RuntimeException(e);
98 }
99 }
100
101 private record Storage(List<Recipient> recipients, long lastId) {
102
103 private record Recipient(
104 long id,
105 String number,
106 String uuid,
107 String profileKey,
108 String expiringProfileKeyCredential,
109 Contact contact,
110 Profile profile
111 ) {
112
113 private record Contact(
114 String name,
115 String color,
116 int messageExpirationTime,
117 boolean blocked,
118 boolean archived,
119 boolean profileSharingEnabled
120 ) {}
121
122 private record Profile(
123 long lastUpdateTimestamp,
124 String givenName,
125 String familyName,
126 String about,
127 String aboutEmoji,
128 String avatarUrlPath,
129 String mobileCoinAddress,
130 String unidentifiedAccessMode,
131 Set<String> capabilities
132 ) {}
133 }
134 }
135 }