Remove ThreadStore and store message expiration time in group/contact store

To match the implemenation of Signal-Android
This commit is contained in:
AsamK 2020-03-23 20:28:15 +01:00
parent f982d2752e
commit b62694dbc7
5 changed files with 75 additions and 70 deletions

View file

@ -31,7 +31,6 @@ import org.asamk.signal.storage.contacts.ContactInfo;
import org.asamk.signal.storage.groups.GroupInfo; import org.asamk.signal.storage.groups.GroupInfo;
import org.asamk.signal.storage.groups.JsonGroupStore; import org.asamk.signal.storage.groups.JsonGroupStore;
import org.asamk.signal.storage.protocol.JsonIdentityKeyStore; import org.asamk.signal.storage.protocol.JsonIdentityKeyStore;
import org.asamk.signal.storage.threads.ThreadInfo;
import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.Util; import org.asamk.signal.util.Util;
import org.signal.libsignal.metadata.InvalidMetadataMessageException; import org.signal.libsignal.metadata.InvalidMetadataMessageException;
@ -527,13 +526,11 @@ public class Manager implements Signal {
.build(); .build();
messageBuilder.asGroupMessage(group); messageBuilder.asGroupMessage(group);
} }
ThreadInfo thread = account.getThreadStore().getThread(Base64.encodeBytes(groupId));
if (thread != null) {
messageBuilder.withExpiration(thread.messageExpirationTime);
}
final GroupInfo g = getGroupForSending(groupId); final GroupInfo g = getGroupForSending(groupId);
messageBuilder.withExpiration(g.messageExpirationTime);
sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress())); sendMessageLegacy(messageBuilder, g.getMembersWithout(account.getSelfAddress()));
} }
@ -651,15 +648,9 @@ public class Manager implements Signal {
} }
} }
SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() return SignalServiceDataMessage.newBuilder()
.asGroupMessage(group.build()); .asGroupMessage(group.build())
.withExpiration(g.messageExpirationTime);
ThreadInfo thread = account.getThreadStore().getThread(Base64.encodeBytes(g.groupId));
if (thread != null) {
messageBuilder.withExpiration(thread.messageExpirationTime);
}
return messageBuilder;
} }
private void sendGroupInfoRequest(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions { private void sendGroupInfoRequest(byte[] groupId, SignalServiceAddress recipient) throws IOException, EncapsulatedExceptions {
@ -673,11 +664,6 @@ public class Manager implements Signal {
SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder() SignalServiceDataMessage.Builder messageBuilder = SignalServiceDataMessage.newBuilder()
.asGroupMessage(group.build()); .asGroupMessage(group.build());
ThreadInfo thread = account.getThreadStore().getThread(Base64.encodeBytes(groupId));
if (thread != null) {
messageBuilder.withExpiration(thread.messageExpirationTime);
}
// Send group info request message to the recipient who sent us a message with this groupId // Send group info request message to the recipient who sent us a message with this groupId
sendMessageLegacy(messageBuilder, Collections.singleton(recipient)); sendMessageLegacy(messageBuilder, Collections.singleton(recipient));
} }
@ -837,12 +823,21 @@ public class Manager implements Signal {
} }
/** /**
* Change the expiration timer for a thread (number of groupId) * Change the expiration timer for a contact
*/ */
public void setExpirationTimer(String numberOrGroupId, int messageExpirationTimer) { public void setExpirationTimer(SignalServiceAddress address, int messageExpirationTimer) {
ThreadInfo thread = account.getThreadStore().getThread(numberOrGroupId); ContactInfo c = account.getContactStore().getContact(address);
thread.messageExpirationTime = messageExpirationTimer; c.messageExpirationTime = messageExpirationTimer;
account.getThreadStore().updateThread(thread); account.getContactStore().updateContact(c);
}
/**
* Change the expiration timer for a group
*/
public void setExpirationTimer(byte[] groupId, int messageExpirationTimer) {
GroupInfo g = account.getGroupStore().getGroup(groupId);
g.messageExpirationTime = messageExpirationTimer;
account.getGroupStore().updateGroup(g);
} }
/** /**
@ -1186,9 +1181,9 @@ public class Manager implements Signal {
// Send to all individually, so sync messages are sent correctly // Send to all individually, so sync messages are sent correctly
List<SendMessageResult> results = new ArrayList<>(recipients.size()); List<SendMessageResult> results = new ArrayList<>(recipients.size());
for (SignalServiceAddress address : recipients) { for (SignalServiceAddress address : recipients) {
ThreadInfo thread = account.getThreadStore().getThread(address.getNumber().get()); ContactInfo contact = account.getContactStore().getContact(address);
if (thread != null) { if (contact != null) {
messageBuilder.withExpiration(thread.messageExpirationTime); messageBuilder.withExpiration(contact.messageExpirationTime);
} else { } else {
messageBuilder.withExpiration(0); messageBuilder.withExpiration(0);
} }
@ -1229,10 +1224,8 @@ public class Manager implements Signal {
} }
private void handleSignalServiceDataMessage(SignalServiceDataMessage message, boolean isSync, SignalServiceAddress source, SignalServiceAddress destination, boolean ignoreAttachments) { private void handleSignalServiceDataMessage(SignalServiceDataMessage message, boolean isSync, SignalServiceAddress source, SignalServiceAddress destination, boolean ignoreAttachments) {
String threadId;
if (message.getGroupInfo().isPresent()) { if (message.getGroupInfo().isPresent()) {
SignalServiceGroup groupInfo = message.getGroupInfo().get(); SignalServiceGroup groupInfo = message.getGroupInfo().get();
threadId = Base64.encodeBytes(groupInfo.getGroupId());
GroupInfo group = account.getGroupStore().getGroup(groupInfo.getGroupId()); GroupInfo group = account.getGroupStore().getGroup(groupInfo.getGroupId());
switch (groupInfo.getType()) { switch (groupInfo.getType()) {
case UPDATE: case UPDATE:
@ -1294,25 +1287,30 @@ public class Manager implements Signal {
} }
break; break;
} }
} else {
if (isSync) {
threadId = destination.getNumber().get();
} else {
threadId = source.getNumber().get();
}
} }
if (message.isEndSession()) { if (message.isEndSession()) {
handleEndSession(isSync ? destination.getNumber().get() : source.getNumber().get()); handleEndSession(isSync ? destination.getNumber().get() : source.getNumber().get());
} }
if (message.isExpirationUpdate() || message.getBody().isPresent()) { if (message.isExpirationUpdate() || message.getBody().isPresent()) {
ThreadInfo thread = account.getThreadStore().getThread(threadId); if (message.getGroupInfo().isPresent()) {
if (thread == null) { SignalServiceGroup groupInfo = message.getGroupInfo().get();
thread = new ThreadInfo(); GroupInfo group = account.getGroupStore().getGroup(groupInfo.getGroupId());
thread.id = threadId; if (group == null) {
group = new GroupInfo(groupInfo.getGroupId());
}
if (group.messageExpirationTime != message.getExpiresInSeconds()) {
group.messageExpirationTime = message.getExpiresInSeconds();
account.getGroupStore().updateGroup(group);
}
} else {
ContactInfo contact = account.getContactStore().getContact(isSync ? destination : source);
if (contact == null) {
contact = new ContactInfo(isSync ? destination : source);
}
if (contact.messageExpirationTime != message.getExpiresInSeconds()) {
contact.messageExpirationTime = message.getExpiresInSeconds();
account.getContactStore().updateContact(contact);
} }
if (thread.messageExpirationTime != message.getExpiresInSeconds()) {
thread.messageExpirationTime = message.getExpiresInSeconds();
account.getThreadStore().updateThread(thread);
} }
} }
if (message.getAttachments().isPresent() && !ignoreAttachments) { if (message.getAttachments().isPresent() && !ignoreAttachments) {
@ -1635,13 +1633,7 @@ public class Manager implements Signal {
account.getSignalProtocolStore().saveIdentity(verifiedMessage.getDestination().getNumber().get(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified())); account.getSignalProtocolStore().saveIdentity(verifiedMessage.getDestination().getNumber().get(), verifiedMessage.getIdentityKey(), TrustLevel.fromVerifiedState(verifiedMessage.getVerified()));
} }
if (c.getExpirationTimer().isPresent()) { if (c.getExpirationTimer().isPresent()) {
ThreadInfo thread = account.getThreadStore().getThread(c.getAddress().getNumber().get()); contact.messageExpirationTime = c.getExpirationTimer().get();
if (thread == null) {
thread = new ThreadInfo();
thread.id = c.getAddress().getNumber().get();
}
thread.messageExpirationTime = c.getExpirationTimer().get();
account.getThreadStore().updateThread(thread);
} }
contact.blocked = c.isBlocked(); contact.blocked = c.isBlocked();
contact.inboxPosition = c.getInboxPosition().orNull(); contact.inboxPosition = c.getInboxPosition().orNull();
@ -1769,10 +1761,9 @@ public class Manager implements Signal {
try (OutputStream fos = new FileOutputStream(groupsFile)) { try (OutputStream fos = new FileOutputStream(groupsFile)) {
DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(fos); DeviceGroupsOutputStream out = new DeviceGroupsOutputStream(fos);
for (GroupInfo record : account.getGroupStore().getGroups()) { for (GroupInfo record : account.getGroupStore().getGroups()) {
ThreadInfo info = account.getThreadStore().getThread(Base64.encodeBytes(record.groupId));
out.write(new DeviceGroup(record.groupId, Optional.fromNullable(record.name), out.write(new DeviceGroup(record.groupId, Optional.fromNullable(record.name),
new ArrayList<>(record.getMembers()), createGroupAvatarAttachment(record.groupId), new ArrayList<>(record.getMembers()), createGroupAvatarAttachment(record.groupId),
record.isMember(account.getSelfAddress()), Optional.fromNullable(info != null ? info.messageExpirationTime : null), record.isMember(account.getSelfAddress()), Optional.of(record.messageExpirationTime),
Optional.fromNullable(record.color), record.blocked, Optional.fromNullable(record.inboxPosition), record.archived)); Optional.fromNullable(record.color), record.blocked, Optional.fromNullable(record.inboxPosition), record.archived));
} }
} }
@ -1805,7 +1796,6 @@ public class Manager implements Signal {
DeviceContactsOutputStream out = new DeviceContactsOutputStream(fos); DeviceContactsOutputStream out = new DeviceContactsOutputStream(fos);
for (ContactInfo record : account.getContactStore().getContacts()) { for (ContactInfo record : account.getContactStore().getContacts()) {
VerifiedMessage verifiedMessage = null; VerifiedMessage verifiedMessage = null;
ThreadInfo info = account.getThreadStore().getThread(record.number);
if (getIdentities().containsKey(record.number)) { if (getIdentities().containsKey(record.number)) {
JsonIdentityKeyStore.Identity currentIdentity = null; JsonIdentityKeyStore.Identity currentIdentity = null;
for (JsonIdentityKeyStore.Identity id : getIdentities().get(record.number)) { for (JsonIdentityKeyStore.Identity id : getIdentities().get(record.number)) {
@ -1826,7 +1816,7 @@ public class Manager implements Signal {
out.write(new DeviceContact(record.getAddress(), Optional.fromNullable(record.name), out.write(new DeviceContact(record.getAddress(), Optional.fromNullable(record.name),
createContactAvatarAttachment(record.number), Optional.fromNullable(record.color), createContactAvatarAttachment(record.number), Optional.fromNullable(record.color),
Optional.fromNullable(verifiedMessage), Optional.fromNullable(profileKey), record.blocked, Optional.fromNullable(verifiedMessage), Optional.fromNullable(profileKey), record.blocked,
Optional.fromNullable(info != null ? info.messageExpirationTime : null), Optional.of(record.messageExpirationTime),
Optional.fromNullable(record.inboxPosition), record.archived)); Optional.fromNullable(record.inboxPosition), record.archived));
} }

View file

@ -10,10 +10,13 @@ import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature; import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode; import com.fasterxml.jackson.databind.node.ObjectNode;
import org.asamk.signal.storage.contacts.ContactInfo;
import org.asamk.signal.storage.contacts.JsonContactsStore; 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.groups.JsonGroupStore;
import org.asamk.signal.storage.protocol.JsonSignalProtocolStore; import org.asamk.signal.storage.protocol.JsonSignalProtocolStore;
import org.asamk.signal.storage.threads.JsonThreadStore; import org.asamk.signal.storage.threads.LegacyJsonThreadStore;
import org.asamk.signal.storage.threads.ThreadInfo;
import org.asamk.signal.util.IOUtils; import org.asamk.signal.util.IOUtils;
import org.asamk.signal.util.Util; import org.asamk.signal.util.Util;
import org.signal.zkgroup.InvalidInputException; import org.signal.zkgroup.InvalidInputException;
@ -55,7 +58,6 @@ public class SignalAccount {
private JsonSignalProtocolStore signalProtocolStore; private JsonSignalProtocolStore signalProtocolStore;
private JsonGroupStore groupStore; private JsonGroupStore groupStore;
private JsonContactsStore contactStore; private JsonContactsStore contactStore;
private JsonThreadStore threadStore;
private SignalAccount() { private SignalAccount() {
jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect jsonProcessor.setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE); // disable autodetect
@ -84,7 +86,6 @@ public class SignalAccount {
account.profileKey = profileKey; account.profileKey = profileKey;
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId); account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
account.groupStore = new JsonGroupStore(); account.groupStore = new JsonGroupStore();
account.threadStore = new JsonThreadStore();
account.contactStore = new JsonContactsStore(); account.contactStore = new JsonContactsStore();
account.registered = false; account.registered = false;
@ -104,7 +105,6 @@ public class SignalAccount {
account.signalingKey = signalingKey; account.signalingKey = signalingKey;
account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId); account.signalProtocolStore = new JsonSignalProtocolStore(identityKey, registrationId);
account.groupStore = new JsonGroupStore(); account.groupStore = new JsonGroupStore();
account.threadStore = new JsonThreadStore();
account.contactStore = new JsonContactsStore(); account.contactStore = new JsonContactsStore();
account.registered = true; account.registered = true;
account.isMultiDevice = true; account.isMultiDevice = true;
@ -191,10 +191,24 @@ public class SignalAccount {
} }
JsonNode threadStoreNode = rootNode.get("threadStore"); JsonNode threadStoreNode = rootNode.get("threadStore");
if (threadStoreNode != null) { if (threadStoreNode != null) {
threadStore = jsonProcessor.convertValue(threadStoreNode, JsonThreadStore.class); LegacyJsonThreadStore threadStore = jsonProcessor.convertValue(threadStoreNode, LegacyJsonThreadStore.class);
// Migrate thread info to group and contact store
for (ThreadInfo thread : threadStore.getThreads()) {
try {
ContactInfo contactInfo = contactStore.getContact(new SignalServiceAddress(null, thread.id));
if (contactInfo != null) {
contactInfo.messageExpirationTime = thread.messageExpirationTime;
contactStore.updateContact(contactInfo);
} else {
GroupInfo groupInfo = groupStore.getGroup(Base64.decode(thread.id));
if (groupInfo != null) {
groupInfo.messageExpirationTime = thread.messageExpirationTime;
groupStore.updateGroup(groupInfo);
}
}
} catch (Exception ignored) {
}
} }
if (threadStore == null) {
threadStore = new JsonThreadStore();
} }
} }
@ -216,7 +230,6 @@ public class SignalAccount {
.putPOJO("axolotlStore", signalProtocolStore) .putPOJO("axolotlStore", signalProtocolStore)
.putPOJO("groupStore", groupStore) .putPOJO("groupStore", groupStore)
.putPOJO("contactStore", contactStore) .putPOJO("contactStore", contactStore)
.putPOJO("threadStore", threadStore)
; ;
try { try {
synchronized (fileChannel) { synchronized (fileChannel) {
@ -271,10 +284,6 @@ public class SignalAccount {
return contactStore; return contactStore;
} }
public JsonThreadStore getThreadStore() {
return threadStore;
}
public String getUsername() { public String getUsername() {
return username; return username;
} }

View file

@ -21,6 +21,9 @@ public class ContactInfo {
@JsonProperty @JsonProperty
public String color; public String color;
@JsonProperty(defaultValue = "0")
public int messageExpirationTime;
@JsonProperty @JsonProperty
public String profileKey; public String profileKey;

View file

@ -37,6 +37,8 @@ public class GroupInfo {
public Set<SignalServiceAddress> members = new HashSet<>(); public Set<SignalServiceAddress> members = new HashSet<>();
@JsonProperty @JsonProperty
public String color; public String color;
@JsonProperty(defaultValue = "0")
public int messageExpirationTime;
@JsonProperty(defaultValue = "false") @JsonProperty(defaultValue = "false")
public boolean blocked; public boolean blocked;
@JsonProperty @JsonProperty
@ -54,7 +56,7 @@ public class GroupInfo {
this.groupId = groupId; this.groupId = groupId;
} }
public GroupInfo(@JsonProperty("groupId") byte[] groupId, @JsonProperty("name") String name, @JsonProperty("members") Collection<SignalServiceAddress> members, @JsonProperty("avatarId") long avatarId, @JsonProperty("color") String color, @JsonProperty("blocked") boolean blocked, @JsonProperty("inboxPosition") Integer inboxPosition, @JsonProperty("archived") boolean archived) { public GroupInfo(@JsonProperty("groupId") byte[] groupId, @JsonProperty("name") String name, @JsonProperty("members") Collection<SignalServiceAddress> members, @JsonProperty("avatarId") long avatarId, @JsonProperty("color") String color, @JsonProperty("blocked") boolean blocked, @JsonProperty("inboxPosition") Integer inboxPosition, @JsonProperty("archived") boolean archived, @JsonProperty("messageExpirationTime") int messageExpirationTime) {
this.groupId = groupId; this.groupId = groupId;
this.name = name; this.name = name;
this.members.addAll(members); this.members.addAll(members);
@ -63,6 +65,7 @@ public class GroupInfo {
this.blocked = blocked; this.blocked = blocked;
this.inboxPosition = inboxPosition; this.inboxPosition = inboxPosition;
this.archived = archived; this.archived = archived;
this.messageExpirationTime = messageExpirationTime;
} }
@JsonIgnore @JsonIgnore

View file

@ -18,12 +18,12 @@ import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
public class JsonThreadStore { public class LegacyJsonThreadStore {
private static final ObjectMapper jsonProcessor = new ObjectMapper(); private static final ObjectMapper jsonProcessor = new ObjectMapper();
@JsonProperty("threads") @JsonProperty("threads")
@JsonSerialize(using = JsonThreadStore.MapToListSerializer.class) @JsonSerialize(using = MapToListSerializer.class)
@JsonDeserialize(using = ThreadsDeserializer.class) @JsonDeserialize(using = ThreadsDeserializer.class)
private Map<String, ThreadInfo> threads = new HashMap<>(); private Map<String, ThreadInfo> threads = new HashMap<>();