]> nmode's Git Repositories - signal-cli/blob - lib/src/main/java/org/asamk/signal/manager/syncStorage/DefaultStorageRecordProcessor.java
e9e4ef7f97be732c863ad639a25c4b694d0f443a
[signal-cli] / lib / src / main / java / org / asamk / signal / manager / syncStorage / DefaultStorageRecordProcessor.java
1 package org.asamk.signal.manager.syncStorage;
2
3 import org.slf4j.Logger;
4 import org.slf4j.LoggerFactory;
5 import org.whispersystems.signalservice.api.storage.SignalRecord;
6 import org.whispersystems.signalservice.api.storage.StorageId;
7
8 import java.sql.SQLException;
9 import java.util.Comparator;
10 import java.util.Optional;
11 import java.util.Set;
12 import java.util.TreeSet;
13
14 /**
15 * An implementation of {@link StorageRecordProcessor} that solidifies a pattern and reduces
16 * duplicate code in individual implementations.
17 * <p>
18 * Concerning the implementation of {@link #compare(Object, Object)}, it's purpose is to detect if
19 * two items would map to the same logical entity (i.e. they would correspond to the same record in
20 * our local store). We use it for a {@link TreeSet}, so mainly it's just important that the '0'
21 * case is correct. Other cases are whatever, just make it something stable.
22 */
23 abstract class DefaultStorageRecordProcessor<E extends SignalRecord<?>> implements StorageRecordProcessor<E>, Comparator<E> {
24
25 private static final Logger logger = LoggerFactory.getLogger(DefaultStorageRecordProcessor.class);
26 private final Set<E> matchedRecords = new TreeSet<>(this);
27
28 /**
29 * One type of invalid remote data this handles is two records mapping to the same local data. We
30 * have to trim this bad data out, because if we don't, we'll upload an ID set that only has one
31 * of the IDs in it, but won't properly delete the dupes, which will then fail our validation
32 * checks.
33 * <p>
34 * This is a bit tricky -- as we process records, IDs are written back to the local store, so we
35 * can't easily be like "oh multiple records are mapping to the same local storage ID". And in
36 * general we rely on SignalRecords to implement an equals() that includes the StorageId, so using
37 * a regular set is out. Instead, we use a {@link TreeSet}, which allows us to define a custom
38 * comparator for checking equality. Then we delegate to the subclass to tell us if two items are
39 * the same based on their actual data (i.e. two contacts having the same UUID, or two groups
40 * having the same MasterKey).
41 */
42 @Override
43 public void process(E remote) throws SQLException {
44 if (isInvalid(remote)) {
45 debug(remote.getId(), remote, "Found invalid key! Ignoring it.");
46 return;
47 }
48
49 final var local = getMatching(remote);
50
51 if (local.isEmpty()) {
52 debug(remote.getId(), remote, "No matching local record. Inserting.");
53 insertLocal(remote);
54 return;
55 }
56
57 if (matchedRecords.contains(local.get())) {
58 debug(remote.getId(),
59 remote,
60 "Multiple remote records map to the same local record " + local.get() + "! Ignoring this one.");
61 return;
62 }
63
64 matchedRecords.add(local.get());
65
66 final var merged = merge(remote, local.get());
67 if (!merged.equals(remote)) {
68 debug(remote.getId(), remote, "[Remote Update] " + merged.describeDiff(remote));
69 }
70
71 if (!merged.equals(local.get())) {
72 final var update = new StorageRecordUpdate<>(local.get(), merged);
73 debug(remote.getId(), remote, "[Local Update] " + update);
74 updateLocal(update);
75 }
76 }
77
78 private void debug(StorageId i, E record, String message) {
79 logger.debug("[{}][{}] {}", i, record.getClass().getSimpleName(), message);
80 }
81
82 /**
83 * @return True if the record is invalid and should be removed from storage service, otherwise false.
84 */
85 protected abstract boolean isInvalid(E remote) throws SQLException;
86
87 /**
88 * Only records that pass the validity check (i.e. return false from {@link #isInvalid(SignalRecord)})
89 * make it to here, so you can assume all records are valid.
90 */
91 protected abstract Optional<E> getMatching(E remote) throws SQLException;
92
93 protected abstract E merge(E remote, E local);
94
95 protected abstract void insertLocal(E record) throws SQLException;
96
97 protected abstract void updateLocal(StorageRecordUpdate<E> update) throws SQLException;
98 }