Cache profiles for 24h before retrieving them again

This commit is contained in:
AsamK 2020-09-10 14:20:16 +02:00
parent 0f3aa22519
commit b94c1e50e6
6 changed files with 168 additions and 4 deletions

View 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;

View 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;
}

View file

@ -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();
}
}
}

View 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;
}

View file

@ -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;
}
}

View 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,