]> nmode's Git Repositories - signal-cli/commitdiff
Cache profiles for 24h before retrieving them again
authorAsamK <asamk@gmx.de>
Thu, 10 Sep 2020 12:20:16 +0000 (14:20 +0200)
committerAsamK <asamk@gmx.de>
Thu, 10 Sep 2020 12:20:16 +0000 (14:20 +0200)
src/main/java/org/asamk/signal/manager/Manager.java
src/main/java/org/asamk/signal/storage/SignalAccount.java
src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java [new file with mode: 0644]
src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java [moved from src/main/java/org/asamk/signal/manager/SignalProfile.java with 69% similarity]
src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java [new file with mode: 0644]
src/main/java/org/asamk/signal/storage/protocol/RecipientStore.java

index 31f2d9d6fff05659e4bf03d2f0da28af875b1c2c..d16aabe60a81cc3dde5411f113b6b2667275e697 100644 (file)
@@ -22,6 +22,8 @@ import org.asamk.signal.storage.SignalAccount;
 import org.asamk.signal.storage.contacts.ContactInfo;
 import org.asamk.signal.storage.groups.GroupInfo;
 import org.asamk.signal.storage.groups.JsonGroupStore;
+import org.asamk.signal.storage.profiles.SignalProfile;
+import org.asamk.signal.storage.profiles.SignalProfileEntry;
 import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
 import org.asamk.signal.util.IOUtils;
 import org.asamk.signal.util.Util;
@@ -442,6 +444,18 @@ public class Manager implements Closeable {
     }
 
     private SignalProfile getRecipientProfile(SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess, ProfileKey profileKey) throws IOException {
+        SignalProfileEntry profileEntry = account.getProfileStore().getProfile(address);
+        long now = new Date().getTime();
+        // Profiles are cache for 24h before retrieving them again
+        if (profileEntry == null || profileEntry.getProfile() == null || now - profileEntry.getLastUpdateTimestamp() > 24 * 60 * 60 * 1000) {
+            SignalProfile profile = retrieveRecipientProfile(address, unidentifiedAccess, profileKey);
+            profileEntry = new SignalProfileEntry(profileKey, now, profile);
+            account.getProfileStore().updateProfile(address, profileEntry);
+        }
+        return profileEntry.getProfile();
+    }
+
+    private SignalProfile retrieveRecipientProfile(SignalServiceAddress address, Optional<UnidentifiedAccess> unidentifiedAccess, ProfileKey profileKey) throws IOException {
         final SignalServiceProfile encryptedProfile = getEncryptedRecipientProfile(address, unidentifiedAccess);
 
         File avatarFile = null;
index d0638e410562169167d511c82f840a18b2fd8ac8..d9b37825b108fd114baa90139ddd44ca83bffd6a 100644 (file)
@@ -14,6 +14,7 @@ import org.asamk.signal.storage.contacts.ContactInfo;
 import org.asamk.signal.storage.contacts.JsonContactsStore;
 import org.asamk.signal.storage.groups.GroupInfo;
 import org.asamk.signal.storage.groups.JsonGroupStore;
+import org.asamk.signal.storage.profiles.ProfileStore;
 import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
 import org.asamk.signal.storage.protocol.JsonSignalProtocolStore;
 import org.asamk.signal.storage.protocol.RecipientStore;
@@ -67,6 +68,7 @@ public class SignalAccount implements Closeable {
     private JsonGroupStore groupStore;
     private JsonContactsStore contactStore;
     private RecipientStore recipientStore;
+    private ProfileStore profileStore;
 
     private SignalAccount(final FileChannel fileChannel, final FileLock lock) {
         this.fileChannel = fileChannel;
@@ -109,6 +111,7 @@ public class SignalAccount implements Closeable {
         account.groupStore = new JsonGroupStore();
         account.contactStore = new JsonContactsStore();
         account.recipientStore = new RecipientStore();
+        account.profileStore = new ProfileStore();
         account.registered = false;
 
         return account;
@@ -134,6 +137,7 @@ public class SignalAccount implements Closeable {
         account.groupStore = new JsonGroupStore();
         account.contactStore = new JsonContactsStore();
         account.recipientStore = new RecipientStore();
+        account.profileStore = new ProfileStore();
         account.registered = true;
         account.isMultiDevice = true;
 
@@ -245,6 +249,14 @@ public class SignalAccount implements Closeable {
             }
         }
 
+        JsonNode profileStoreNode = rootNode.get("profileStore");
+        if (profileStoreNode != null) {
+            profileStore = jsonProcessor.convertValue(profileStoreNode, ProfileStore.class);
+        }
+        if (profileStore == null) {
+            profileStore = new ProfileStore();
+        }
+
         JsonNode threadStoreNode = rootNode.get("threadStore");
         if (threadStoreNode != null) {
             LegacyJsonThreadStore threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
@@ -291,6 +303,7 @@ public class SignalAccount implements Closeable {
                 .putPOJO("groupStore", groupStore)
                 .putPOJO("contactStore", contactStore)
                 .putPOJO("recipientStore", recipientStore)
+                .putPOJO("profileStore", profileStore)
         ;
         try {
             synchronized (fileChannel) {
@@ -347,6 +360,10 @@ public class SignalAccount implements Closeable {
         return recipientStore;
     }
 
+    public ProfileStore getProfileStore() {
+        return profileStore;
+    }
+
     public String getUsername() {
         return username;
     }
diff --git a/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java b/src/main/java/org/asamk/signal/storage/profiles/ProfileStore.java
new file mode 100644 (file)
index 0000000..691ac0e
--- /dev/null
@@ -0,0 +1,90 @@
+package org.asamk.signal.storage.profiles;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
+import com.fasterxml.jackson.core.JsonGenerator;
+import com.fasterxml.jackson.core.JsonParser;
+import com.fasterxml.jackson.databind.DeserializationContext;
+import com.fasterxml.jackson.databind.JsonDeserializer;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.JsonSerializer;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializerProvider;
+import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
+import com.fasterxml.jackson.databind.annotation.JsonSerialize;
+
+import org.signal.zkgroup.InvalidInputException;
+import org.signal.zkgroup.profiles.ProfileKey;
+import org.whispersystems.signalservice.api.push.SignalServiceAddress;
+import org.whispersystems.signalservice.api.util.UuidUtil;
+import org.whispersystems.util.Base64;
+
+import java.io.IOException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.UUID;
+
+public class ProfileStore {
+
+    private static final ObjectMapper jsonProcessor = new ObjectMapper();
+
+    @JsonProperty("profiles")
+    @JsonDeserialize(using = ProfileStoreDeserializer.class)
+    @JsonSerialize(using = ProfileStoreSerializer.class)
+    private final Map<SignalServiceAddress, SignalProfileEntry> profiles = new HashMap<>();
+
+    public SignalProfileEntry getProfile(SignalServiceAddress serviceAddress) {
+        return profiles.get(serviceAddress);
+    }
+
+    public SignalProfileEntry updateProfile(SignalServiceAddress serviceAddress, SignalProfileEntry profile) {
+        return profiles.put(serviceAddress, profile);
+    }
+
+    public static class ProfileStoreDeserializer extends JsonDeserializer<Map<SignalServiceAddress, SignalProfileEntry>> {
+
+        @Override
+        public Map<SignalServiceAddress, SignalProfileEntry> deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) throws IOException {
+            JsonNode node = jsonParser.getCodec().readTree(jsonParser);
+
+            Map<SignalServiceAddress, SignalProfileEntry> addresses = new HashMap<>();
+
+            if (node.isArray()) {
+                for (JsonNode recipient : node) {
+                    String recipientName = recipient.get("name").asText();
+                    UUID uuid = UuidUtil.parseOrThrow(recipient.get("uuid").asText());
+                    final SignalServiceAddress serviceAddress = new SignalServiceAddress(uuid, recipientName);
+                    ProfileKey profileKey = null;
+                    try {
+                        profileKey = new ProfileKey(Base64.decode(recipient.get("profileKey").asText()));
+                    } catch (InvalidInputException ignored) {
+                    }
+                    long lastUpdateTimestamp = recipient.get("lastUpdateTimestamp").asLong();
+                    SignalProfile profile = jsonProcessor.treeToValue(recipient.get("profile"), SignalProfile.class);
+                    addresses.put(serviceAddress, new SignalProfileEntry(profileKey, lastUpdateTimestamp, profile));
+                }
+            }
+
+            return addresses;
+        }
+    }
+
+    public static class ProfileStoreSerializer extends JsonSerializer<Map<SignalServiceAddress, SignalProfileEntry>> {
+
+        @Override
+        public void serialize(Map<SignalServiceAddress, SignalProfileEntry> profiles, JsonGenerator json, SerializerProvider serializerProvider) throws IOException {
+            json.writeStartArray();
+            for (Map.Entry<SignalServiceAddress, SignalProfileEntry> entry : profiles.entrySet()) {
+                final SignalServiceAddress address = entry.getKey();
+                final SignalProfileEntry profileEntry = entry.getValue();
+                json.writeStartObject();
+                json.writeStringField("name", address.getNumber().get());
+                json.writeStringField("uuid", address.getUuid().get().toString());
+                json.writeStringField("profileKey", Base64.encodeBytes(profileEntry.getProfileKey().serialize()));
+                json.writeNumberField("lastUpdateTimestamp", profileEntry.getLastUpdateTimestamp());
+                json.writeObjectField("profile", profileEntry.getProfile());
+                json.writeEndObject();
+            }
+            json.writeEndArray();
+        }
+    }
+}
similarity index 69%
rename from src/main/java/org/asamk/signal/manager/SignalProfile.java
rename to src/main/java/org/asamk/signal/storage/profiles/SignalProfile.java
index 1217793cd551b3de14eba2dc356afaf91afd2bd1..71ab60e65cef3d5a28b83b24b8ae0bb4cdb0f490 100644 (file)
@@ -1,4 +1,6 @@
-package org.asamk.signal.manager;
+package org.asamk.signal.storage.profiles;
+
+import com.fasterxml.jackson.annotation.JsonProperty;
 
 import org.whispersystems.signalservice.api.profiles.SignalServiceProfile;
 
@@ -6,16 +8,21 @@ import java.io.File;
 
 public class SignalProfile {
 
+    @JsonProperty
     private final String identityKey;
 
+    @JsonProperty
     private final String name;
 
     private final File avatarFile;
 
+    @JsonProperty
     private final String unidentifiedAccess;
 
+    @JsonProperty
     private final boolean unrestrictedUnidentifiedAccess;
 
+    @JsonProperty
     private final SignalServiceProfile.Capabilities capabilities;
 
     public SignalProfile(final String identityKey, final String name, final File avatarFile, final String unidentifiedAccess, final boolean unrestrictedUnidentifiedAccess, final SignalServiceProfile.Capabilities capabilities) {
@@ -27,6 +34,15 @@ public class SignalProfile {
         this.capabilities = capabilities;
     }
 
+    public SignalProfile(@JsonProperty("identityKey") final String identityKey, @JsonProperty("name") final String name, @JsonProperty("unidentifiedAccess") final String unidentifiedAccess, @JsonProperty("unrestrictedUnidentifiedAccess") final boolean unrestrictedUnidentifiedAccess, @JsonProperty("capabilities") final SignalServiceProfile.Capabilities capabilities) {
+        this.identityKey = identityKey;
+        this.name = name;
+        this.avatarFile = null;
+        this.unidentifiedAccess = unidentifiedAccess;
+        this.unrestrictedUnidentifiedAccess = unrestrictedUnidentifiedAccess;
+        this.capabilities = capabilities;
+    }
+
     public String getIdentityKey() {
         return identityKey;
     }
diff --git a/src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java b/src/main/java/org/asamk/signal/storage/profiles/SignalProfileEntry.java
new file mode 100644 (file)
index 0000000..0423e12
--- /dev/null
@@ -0,0 +1,30 @@
+package org.asamk.signal.storage.profiles;
+
+import org.signal.zkgroup.profiles.ProfileKey;
+
+public class SignalProfileEntry {
+
+    private ProfileKey profileKey;
+
+    private long lastUpdateTimestamp;
+
+    private SignalProfile profile;
+
+    public SignalProfileEntry(final ProfileKey profileKey, final long lastUpdateTimestamp, final SignalProfile profile) {
+        this.profileKey = profileKey;
+        this.lastUpdateTimestamp = lastUpdateTimestamp;
+        this.profile = profile;
+    }
+
+    public ProfileKey getProfileKey() {
+        return profileKey;
+    }
+
+    public long getLastUpdateTimestamp() {
+        return lastUpdateTimestamp;
+    }
+
+    public SignalProfile getProfile() {
+        return profile;
+    }
+}
index 4943bd369dce6951738a74847241f426a03ebfc0..47f06c4620d9354e3727377219918d37e784031b 100644 (file)
@@ -26,9 +26,6 @@ public class RecipientStore {
     @JsonSerialize(using = RecipientStoreSerializer.class)
     private final Set<SignalServiceAddress> addresses = new HashSet<>();
 
-    public RecipientStore() {
-    }
-
     public SignalServiceAddress resolveServiceAddress(SignalServiceAddress serviceAddress) {
         if (addresses.contains(serviceAddress)) {
             // If the Set already contains the exact address with UUID and Number,