1 package org
.asamk
.signal
.manager
.syncStorage
;
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
;
8 import java
.sql
.SQLException
;
9 import java
.util
.Comparator
;
10 import java
.util
.Optional
;
12 import java
.util
.TreeSet
;
15 * An implementation of {@link StorageRecordProcessor} that solidifies a pattern and reduces
16 * duplicate code in individual implementations.
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.
23 abstract class DefaultStorageRecordProcessor
<E
extends SignalRecord
> implements StorageRecordProcessor
<E
>, Comparator
<E
> {
25 private static final Logger logger
= LoggerFactory
.getLogger(DefaultStorageRecordProcessor
.class);
26 private final Set
<E
> matchedRecords
= new TreeSet
<>(this);
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
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).
43 public void process(E remote
) throws SQLException
{
44 if (isInvalid(remote
)) {
45 debug(remote
.getId(), remote
, "Found invalid key! Ignoring it.");
49 final var local
= getMatching(remote
);
51 if (local
.isEmpty()) {
52 debug(remote
.getId(), remote
, "No matching local record. Inserting.");
57 if (matchedRecords
.contains(local
.get())) {
58 debug(remote
.getId(), remote
, "Multiple remote records map to the same local record! Ignoring this one.");
62 matchedRecords
.add(local
.get());
64 final var merged
= merge(remote
, local
.get());
65 if (!merged
.equals(remote
)) {
66 debug(remote
.getId(), remote
, "[Remote Update] " + merged
.describeDiff(remote
));
69 if (!merged
.equals(local
.get())) {
70 final var update
= new StorageRecordUpdate
<>(local
.get(), merged
);
71 debug(remote
.getId(), remote
, "[Local Update] " + update
);
76 private void debug(StorageId i
, E
record, String message
) {
77 logger
.debug("[" + i
+ "][" + record.getClass().getSimpleName() + "] " + message
);
81 * @return True if the record is invalid and should be removed from storage service, otherwise false.
83 protected abstract boolean isInvalid(E remote
) throws SQLException
;
86 * Only records that pass the validity check (i.e. return false from {@link #isInvalid(SignalRecord)})
87 * make it to here, so you can assume all records are valid.
89 protected abstract Optional
<E
> getMatching(E remote
) throws SQLException
;
91 protected abstract E
merge(E remote
, E local
);
93 protected abstract void insertLocal(E
record) throws SQLException
;
95 protected abstract void updateLocal(StorageRecordUpdate
<E
> update
) throws SQLException
;