]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/storage/sessions/LegacySessionStore.java
d0646ec14340a968bce478fd00e5baa3217c0f55
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / storage / sessions / LegacySessionStore.java
1 package org.asamk.signal.manager.storage.sessions;
2
3 import org.asamk.signal.manager.api.Pair;
4 import org.asamk.signal.manager.storage.recipients.RecipientResolver;
5 import org.asamk.signal.manager.storage.sessions.SessionStore.Key;
6 import org.asamk.signal.manager.util.IOUtils;
7 import org.signal.libsignal.protocol.state.SessionRecord;
8 import org.slf4j.Logger;
9 import org.slf4j.LoggerFactory;
10
11 import java.io.File;
12 import java.io.FileInputStream;
13 import java.io.IOException;
14 import java.nio.file.Files;
15 import java.util.Arrays;
16 import java.util.Collection;
17 import java.util.List;
18 import java.util.Objects;
19 import java.util.regex.Matcher;
20 import java.util.regex.Pattern;
21
22 public class LegacySessionStore {
23
24 private final static Logger logger = LoggerFactory.getLogger(LegacySessionStore.class);
25
26 public static void migrate(
27 final File sessionsPath, final RecipientResolver resolver, final SessionStore sessionStore
28 ) {
29 final var keys = getKeysLocked(sessionsPath, resolver);
30 final var sessions = keys.stream().map(key -> {
31 final var record = loadSessionLocked(key, sessionsPath);
32 if (record == null) {
33 return null;
34 }
35 return new Pair<>(key, record);
36 }).filter(Objects::nonNull).toList();
37 sessionStore.addLegacySessions(sessions);
38 deleteAllSessions(sessionsPath);
39 }
40
41 private static void deleteAllSessions(File sessionsPath) {
42 final var files = sessionsPath.listFiles();
43 if (files == null) {
44 return;
45 }
46
47 for (var file : files) {
48 try {
49 Files.delete(file.toPath());
50 } catch (IOException e) {
51 logger.error("Failed to delete session file {}: {}", file, e.getMessage());
52 }
53 }
54 try {
55 Files.delete(sessionsPath.toPath());
56 } catch (IOException e) {
57 logger.error("Failed to delete session directory {}: {}", sessionsPath, e.getMessage());
58 }
59 }
60
61 private static Collection<Key> getKeysLocked(File sessionsPath, final RecipientResolver resolver) {
62 final var files = sessionsPath.listFiles();
63 if (files == null) {
64 return List.of();
65 }
66 return parseFileNames(files, resolver);
67 }
68
69 static final Pattern sessionFileNamePattern = Pattern.compile("(\\d+)_(\\d+)");
70
71 private static List<Key> parseFileNames(final File[] files, final RecipientResolver resolver) {
72 return Arrays.stream(files)
73 .map(f -> sessionFileNamePattern.matcher(f.getName()))
74 .filter(Matcher::matches)
75 .map(matcher -> {
76 final var recipientId = resolver.resolveRecipient(Long.parseLong(matcher.group(1)));
77 if (recipientId == null) {
78 return null;
79 }
80 return new Key(recipientId, Integer.parseInt(matcher.group(2)));
81 })
82 .filter(Objects::nonNull)
83 .toList();
84 }
85
86 private static File getSessionFile(Key key, final File sessionsPath) {
87 try {
88 IOUtils.createPrivateDirectories(sessionsPath);
89 } catch (IOException e) {
90 throw new AssertionError("Failed to create sessions path", e);
91 }
92 return new File(sessionsPath, key.recipientId().id() + "_" + key.deviceId());
93 }
94
95 private static SessionRecord loadSessionLocked(final Key key, final File sessionsPath) {
96 final var file = getSessionFile(key, sessionsPath);
97 if (!file.exists()) {
98 return null;
99 }
100 try (var inputStream = new FileInputStream(file)) {
101 return new SessionRecord(inputStream.readAllBytes());
102 } catch (Exception e) {
103 logger.warn("Failed to load session, resetting session: {}", e.getMessage());
104 return null;
105 }
106 }
107 }