Add support for syncing pinned status with storage service.

This commit is contained in:
Greyson Parrelli
2020-10-12 21:06:42 -04:00
parent 97420aae1b
commit 7ef57cc0cf
8 changed files with 295 additions and 49 deletions

View File

@@ -3,10 +3,14 @@ package org.whispersystems.signalservice.api.storage;
import com.google.protobuf.ByteString;
import org.whispersystems.libsignal.util.guava.Optional;
import org.whispersystems.signalservice.api.push.SignalServiceAddress;
import org.whispersystems.signalservice.api.util.OptionalUtil;
import org.whispersystems.signalservice.api.util.ProtoUtil;
import org.whispersystems.signalservice.api.util.UuidUtil;
import org.whispersystems.signalservice.internal.storage.protos.AccountRecord;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
public final class SignalAccountRecord implements SignalRecord {
@@ -15,20 +19,26 @@ public final class SignalAccountRecord implements SignalRecord {
private final AccountRecord proto;
private final boolean hasUnknownFields;
private final Optional<String> givenName;
private final Optional<String> familyName;
private final Optional<String> avatarUrlPath;
private final Optional<byte[]> profileKey;
private final Optional<String> givenName;
private final Optional<String> familyName;
private final Optional<String> avatarUrlPath;
private final Optional<byte[]> profileKey;
private final List<PinnedConversation> pinnedConversations;
public SignalAccountRecord(StorageId id, AccountRecord proto) {
this.id = id;
this.proto = proto;
this.hasUnknownFields = ProtoUtil.hasUnknownFields(proto);
this.givenName = OptionalUtil.absentIfEmpty(proto.getGivenName());
this.familyName = OptionalUtil.absentIfEmpty(proto.getFamilyName());
this.profileKey = OptionalUtil.absentIfEmpty(proto.getProfileKey());
this.avatarUrlPath = OptionalUtil.absentIfEmpty(proto.getAvatarUrlPath());
this.givenName = OptionalUtil.absentIfEmpty(proto.getGivenName());
this.familyName = OptionalUtil.absentIfEmpty(proto.getFamilyName());
this.profileKey = OptionalUtil.absentIfEmpty(proto.getProfileKey());
this.avatarUrlPath = OptionalUtil.absentIfEmpty(proto.getAvatarUrlPath());
this.pinnedConversations = new ArrayList<>(proto.getPinnedConversationsCount());
for (AccountRecord.PinnedConversation conversation : proto.getPinnedConversationsList()) {
pinnedConversations.add(PinnedConversation.fromRemote(conversation));
}
}
@Override
@@ -92,6 +102,10 @@ public final class SignalAccountRecord implements SignalRecord {
return proto.getUnlistedPhoneNumber();
}
public List<PinnedConversation> getPinnedConversations() {
return pinnedConversations;
}
AccountRecord toProto() {
return proto;
}
@@ -110,6 +124,96 @@ public final class SignalAccountRecord implements SignalRecord {
return Objects.hash(id, proto);
}
public static class PinnedConversation {
private final Optional<SignalServiceAddress> contact;
private final Optional<byte[]> groupV1Id;
private final Optional<byte[]> groupV2MasterKey;
private PinnedConversation(Optional<SignalServiceAddress> contact, Optional<byte[]> groupV1Id, Optional<byte[]> groupV2MasterKey) {
this.contact = contact;
this.groupV1Id = groupV1Id;
this.groupV2MasterKey = groupV2MasterKey;
}
public static PinnedConversation forContact(SignalServiceAddress address) {
return new PinnedConversation(Optional.of(address), Optional.absent(), Optional.absent());
}
public static PinnedConversation forGroupV1(byte[] groupId) {
return new PinnedConversation(Optional.absent(), Optional.of(groupId), Optional.absent());
}
public static PinnedConversation forGroupV2(byte[] masterKey) {
return new PinnedConversation(Optional.absent(), Optional.absent(), Optional.of(masterKey));
}
private static PinnedConversation forEmpty() {
return new PinnedConversation(Optional.absent(), Optional.absent(), Optional.absent());
}
static PinnedConversation fromRemote(AccountRecord.PinnedConversation remote) {
if (remote.hasContact()) {
return forContact(new SignalServiceAddress(UuidUtil.parseOrNull(remote.getContact().getUuid()), remote.getContact().getE164()));
} else if (!remote.getLegacyGroupId().isEmpty()) {
return forGroupV1(remote.getLegacyGroupId().toByteArray());
} else if (!remote.getGroupMasterKey().isEmpty()) {
return forGroupV2(remote.getGroupMasterKey().toByteArray());
} else {
return PinnedConversation.forEmpty();
}
}
public Optional<SignalServiceAddress> getContact() {
return contact;
}
public Optional<byte[]> getGroupV1Id() {
return groupV1Id;
}
public Optional<byte[]> getGroupV2MasterKey() {
return groupV2MasterKey;
}
public boolean isValid() {
return contact.isPresent() || groupV1Id.isPresent() || groupV2MasterKey.isPresent();
}
private AccountRecord.PinnedConversation toRemote() {
if (contact.isPresent()) {
AccountRecord.PinnedConversation.Contact.Builder contactBuilder = AccountRecord.PinnedConversation.Contact.newBuilder();
if (contact.get().getUuid().isPresent()) {
contactBuilder.setUuid(contact.get().getUuid().get().toString());
}
if (contact.get().getNumber().isPresent()) {
contactBuilder.setE164(contact.get().getNumber().get());
}
return AccountRecord.PinnedConversation.newBuilder().setContact(contactBuilder.build()).build();
} else if (groupV1Id.isPresent()) {
return AccountRecord.PinnedConversation.newBuilder().setLegacyGroupId(ByteString.copyFrom(groupV1Id.get())).build();
} else if (groupV2MasterKey.isPresent()) {
return AccountRecord.PinnedConversation.newBuilder().setGroupMasterKey(ByteString.copyFrom(groupV2MasterKey.get())).build();
} else {
return AccountRecord.PinnedConversation.newBuilder().build();
}
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
PinnedConversation that = (PinnedConversation) o;
return contact.equals(that.contact) &&
groupV1Id.equals(that.groupV1Id) &&
groupV2MasterKey.equals(that.groupV2MasterKey);
}
@Override
public int hashCode() {
return Objects.hash(contact, groupV1Id, groupV2MasterKey);
}
}
public static final class Builder {
private final StorageId id;
private final AccountRecord.Builder builder;
@@ -186,6 +290,16 @@ public final class SignalAccountRecord implements SignalRecord {
return this;
}
public Builder setPinnedConversations(List<PinnedConversation> pinnedConversations) {
builder.clearPinnedConversations();
for (PinnedConversation pinned : pinnedConversations) {
builder.addPinnedConversations(pinned.toRemote());
}
return this;
}
public SignalAccountRecord build() {
AccountRecord proto = builder.build();

View File

@@ -107,17 +107,31 @@ message AccountRecord {
NOBODY = 2;
}
bytes profileKey = 1;
string givenName = 2;
string familyName = 3;
string avatarUrlPath = 4;
bool noteToSelfArchived = 5;
bool readReceipts = 6;
bool sealedSenderIndicators = 7;
bool typingIndicators = 8;
bool proxiedLinkPreviews = 9;
bool noteToSelfMarkedUnread = 10;
bool linkPreviews = 11;
PhoneNumberSharingMode phoneNumberSharingMode = 12;
bool unlistedPhoneNumber = 13;
message PinnedConversation {
message Contact {
string uuid = 1;
string e164 = 2;
}
oneof identifier {
Contact contact = 1;
bytes legacyGroupId = 3;
bytes groupMasterKey = 4;
}
}
bytes profileKey = 1;
string givenName = 2;
string familyName = 3;
string avatarUrlPath = 4;
bool noteToSelfArchived = 5;
bool readReceipts = 6;
bool sealedSenderIndicators = 7;
bool typingIndicators = 8;
bool proxiedLinkPreviews = 9;
bool noteToSelfMarkedUnread = 10;
bool linkPreviews = 11;
PhoneNumberSharingMode phoneNumberSharingMode = 12;
bool unlistedPhoneNumber = 13;
repeated PinnedConversation pinnedConversations = 14;
}