diff --git a/briar-api/src/org/briarproject/api/privategroup/GroupMember.java b/briar-api/src/org/briarproject/api/privategroup/GroupMember.java new file mode 100644 index 0000000000000000000000000000000000000000..e433aa792b30ad07efc8d1944122d856fa4f0bcd --- /dev/null +++ b/briar-api/src/org/briarproject/api/privategroup/GroupMember.java @@ -0,0 +1,35 @@ +package org.briarproject.api.privategroup; + +import org.briarproject.api.identity.Author; +import org.briarproject.api.identity.Author.Status; +import org.briarproject.api.nullsafety.NotNullByDefault; + +import javax.annotation.concurrent.Immutable; + +@Immutable +@NotNullByDefault +public class GroupMember { + + private final Author author; + private final Status status; + private final boolean shared; + + public GroupMember(Author author, Status status, boolean shared) { + this.author = author; + this.status = status; + this.shared = shared; + } + + public Author getAuthor() { + return author; + } + + public Status getStatus() { + return status; + } + + public boolean isShared() { + return shared; + } + +} diff --git a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java index 49bee81f8788f1edacda917d4d0244544fe001e8..ded7a1702848a01cd4db0290fb6f8999766f16a5 100644 --- a/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java +++ b/briar-api/src/org/briarproject/api/privategroup/PrivateGroupManager.java @@ -3,12 +3,15 @@ package org.briarproject.api.privategroup; import org.briarproject.api.clients.MessageTracker; import org.briarproject.api.db.DbException; import org.briarproject.api.db.Transaction; +import org.briarproject.api.identity.Author; +import org.briarproject.api.nullsafety.NotNullByDefault; import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.GroupId; import org.briarproject.api.sync.MessageId; import java.util.Collection; +@NotNullByDefault public interface PrivateGroupManager extends MessageTracker { /** Returns the unique ID of the private group client. */ @@ -35,6 +38,12 @@ public interface PrivateGroupManager extends MessageTracker { // TODO change to getPreviousMessageHeader() long getMessageTimestamp(MessageId id) throws DbException; + /** Marks the group with GroupId g as resolved */ + void markGroupDissolved(Transaction txn, GroupId g) throws DbException; + + /** Returns true if the private group has been dissolved. */ + boolean isDissolved(GroupId g) throws DbException; + /** Stores (and sends) a local group message. */ GroupMessageHeader addLocalMessage(GroupMessage p) throws DbException; @@ -49,13 +58,32 @@ public interface PrivateGroupManager extends MessageTracker { /** Returns all private groups the user is a member of. */ Collection<PrivateGroup> getPrivateGroups() throws DbException; - /** Returns true if the private group has been dissolved. */ - boolean isDissolved(GroupId g) throws DbException; - /** Returns the body of the group message with the given ID. */ String getMessageBody(MessageId m) throws DbException; /** Returns the headers of all group messages in the given group. */ Collection<GroupMessageHeader> getHeaders(GroupId g) throws DbException; + /** Returns all members of the group with ID g */ + Collection<GroupMember> getMembers(GroupId g) throws DbException; + + /** Returns true if the given Author a is member of the group with ID g */ + boolean isMember(Transaction txn, GroupId g, Author a) throws DbException; + + /** + * Registers a hook to be called when members are added + * or groups are removed. + * */ + void registerPrivateGroupHook(PrivateGroupHook hook); + + @NotNullByDefault + interface PrivateGroupHook { + + void addingMember(Transaction txn, GroupId g, Author a) + throws DbException; + + void removingGroup(Transaction txn, GroupId g) throws DbException; + + } + } diff --git a/briar-core/src/org/briarproject/privategroup/Constants.java b/briar-core/src/org/briarproject/privategroup/Constants.java index 12219f78d789193398edea716259c41cba7f7847..8bc82be42b4d6146282ec1d90496a7df1c35d349 100644 --- a/briar-core/src/org/briarproject/privategroup/Constants.java +++ b/briar-core/src/org/briarproject/privategroup/Constants.java @@ -15,4 +15,7 @@ interface Constants { String KEY_MEMBER_NAME = "memberName"; String KEY_MEMBER_PUBLIC_KEY = "memberPublicKey"; + String KEY_MEMBERS = "members"; + String KEY_DISSOLVED = "dissolved"; + } diff --git a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java index 769e065b87a200cd8e80b2cbcc64bb4c42fe5437..7a623419ed2a50581442d177642be763009bf849 100644 --- a/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java +++ b/briar-core/src/org/briarproject/privategroup/PrivateGroupManagerImpl.java @@ -2,6 +2,7 @@ package org.briarproject.privategroup; import org.briarproject.api.FormatException; import org.briarproject.api.clients.ClientHelper; +import org.briarproject.api.contact.ContactId; import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfEntry; import org.briarproject.api.data.BdfList; @@ -13,6 +14,8 @@ import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author.Status; import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.IdentityManager; +import org.briarproject.api.nullsafety.NotNullByDefault; +import org.briarproject.api.privategroup.GroupMember; import org.briarproject.api.privategroup.GroupMessage; import org.briarproject.api.privategroup.GroupMessageHeader; import org.briarproject.api.privategroup.JoinMessageHeader; @@ -33,17 +36,23 @@ import java.util.Arrays; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; +import java.util.List; import java.util.Map; import java.util.Map.Entry; import java.util.Set; +import java.util.concurrent.CopyOnWriteArrayList; import java.util.logging.Logger; import javax.inject.Inject; import static org.briarproject.api.identity.Author.Status.OURSELVES; +import static org.briarproject.api.identity.Author.Status.UNVERIFIED; +import static org.briarproject.api.identity.Author.Status.VERIFIED; import static org.briarproject.api.privategroup.MessageType.JOIN; import static org.briarproject.api.privategroup.MessageType.NEW_MEMBER; import static org.briarproject.api.privategroup.MessageType.POST; +import static org.briarproject.privategroup.Constants.KEY_DISSOLVED; +import static org.briarproject.privategroup.Constants.KEY_MEMBERS; import static org.briarproject.privategroup.Constants.KEY_MEMBER_ID; import static org.briarproject.privategroup.Constants.KEY_MEMBER_NAME; import static org.briarproject.privategroup.Constants.KEY_MEMBER_PUBLIC_KEY; @@ -54,6 +63,7 @@ import static org.briarproject.privategroup.Constants.KEY_READ; import static org.briarproject.privategroup.Constants.KEY_TIMESTAMP; import static org.briarproject.privategroup.Constants.KEY_TYPE; +@NotNullByDefault public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements PrivateGroupManager { @@ -65,6 +75,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements private final PrivateGroupFactory privateGroupFactory; private final IdentityManager identityManager; + private final List<PrivateGroupHook> hooks; @Inject PrivateGroupManagerImpl(ClientHelper clientHelper, @@ -75,6 +86,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements this.privateGroupFactory = privateGroupFactory; this.identityManager = identityManager; + hooks = new CopyOnWriteArrayList<PrivateGroupHook>(); } @Override @@ -89,6 +101,11 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements Transaction txn = db.startTransaction(false); try { db.addGroup(txn, group.getGroup()); + BdfDictionary meta = BdfDictionary.of( + new BdfEntry(KEY_MEMBERS, new BdfList()), + new BdfEntry(KEY_DISSOLVED, false) + ); + clientHelper.mergeGroupMetadata(txn, group.getId(), meta); announceNewMember(txn, newMemberMsg); joinPrivateGroup(txn, joinMsg); txn.setComplete(); @@ -114,6 +131,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements addMessageMetadata(meta, m, true); clientHelper.addLocalMessage(txn, m.getMessage(), meta, true); trackOutgoingMessage(txn, m.getMessage()); + addMember(txn, m.getMessage().getGroupId(), m.getMember()); setPreviousMsgId(txn, m.getMessage().getGroupId(), m.getMessage().getId()); } @@ -121,6 +139,15 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements @Override public void removePrivateGroup(GroupId g) throws DbException { // TODO + Transaction txn = db.startTransaction(false); + try { + for (PrivateGroupHook hook : hooks) { + hook.removingGroup(txn, g); + } + txn.setComplete(); + } finally { + db.endTransaction(txn); + } } @Override @@ -152,6 +179,19 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements clientHelper.mergeGroupMetadata(txn, g, d); } + @Override + public void markGroupDissolved(Transaction txn, GroupId g) + throws DbException { + BdfDictionary meta = BdfDictionary.of( + new BdfEntry(KEY_DISSOLVED, true) + ); + try { + clientHelper.mergeGroupMetadata(txn, g, meta); + } catch (FormatException e) { + throw new DbException(e); + } + } + @Override public long getMessageTimestamp(MessageId id) throws DbException { try { @@ -243,7 +283,12 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements @Override public boolean isDissolved(GroupId g) throws DbException { - return false; + try { + BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(g); + return meta.getBoolean(KEY_DISSOLVED); + } catch (FormatException e) { + throw new DbException(e); + } } @Override @@ -306,14 +351,10 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } long timestamp = meta.getLong(KEY_TIMESTAMP); - AuthorId authorId = new AuthorId(meta.getRaw(KEY_MEMBER_ID)); - String name = meta.getString(KEY_MEMBER_NAME); - byte[] publicKey = meta.getRaw(KEY_MEMBER_PUBLIC_KEY); - Author author = new Author(authorId, name, publicKey); - + Author author = getAuthor(meta); Status status; - if (statuses.containsKey(authorId)) { - status = statuses.get(authorId); + if (statuses.containsKey(author.getId())) { + status = statuses.get(author.getId()); } else { status = identityManager.getAuthorStatus(txn, author.getId()); } @@ -327,6 +368,63 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements status, read); } + @Override + public Collection<GroupMember> getMembers(GroupId g) throws DbException { + Transaction txn = db.startTransaction(true); + try { + Collection<GroupMember> members = new ArrayList<GroupMember>(); + Collection<Author> authors = getMembers(txn, g); + for (Author a : authors) { + Status status = identityManager.getAuthorStatus(txn, a.getId()); + boolean shared = false; + if (status == VERIFIED || status == UNVERIFIED) { + Collection<ContactId> contacts = + db.getContacts(txn, a.getId()); + if (contacts.size() != 1) throw new DbException(); + ContactId c = contacts.iterator().next(); + shared = db.isVisibleToContact(txn, c, g); + } + members.add(new GroupMember(a, status, shared)); + } + txn.setComplete(); + return members; + } finally { + db.endTransaction(txn); + } + } + + private Collection<Author> getMembers(Transaction txn, GroupId g) + throws DbException { + try { + Collection<Author> members = new ArrayList<Author>(); + BdfDictionary meta = + clientHelper.getGroupMetadataAsDictionary(txn, g); + BdfList list = meta.getList(KEY_MEMBERS); + for (Object o : list) { + BdfDictionary d = (BdfDictionary) o; + Author member = getAuthor(d); + members.add(member); + } + return members; + } catch (FormatException e) { + throw new DbException(e); + } + } + + @Override + public boolean isMember(Transaction txn, GroupId g, Author a) + throws DbException { + for (Author member : getMembers(txn, g)) { + if (member.equals(a)) return true; + } + return false; + } + + @Override + public void registerPrivateGroupHook(PrivateGroupHook hook) { + hooks.add(hook); + } + @Override protected boolean incomingMessage(Transaction txn, Message m, BdfList body, BdfDictionary meta) throws DbException, FormatException { @@ -365,7 +463,7 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements db.deleteMessage(txn, m.getId()); return false; } - // TODO add to member list + addMember(txn, m.getGroupId(), getAuthor(meta)); trackIncomingMessage(txn, m); return true; case POST: @@ -421,4 +519,24 @@ public class PrivateGroupManagerImpl extends BdfIncomingMessageHook implements } } + private void addMember(Transaction txn, GroupId g, Author a) + throws DbException, FormatException { + + BdfDictionary meta = clientHelper.getGroupMetadataAsDictionary(txn, g); + BdfList members = meta.getList(KEY_MEMBERS); + members.add(BdfDictionary.of( + new BdfEntry(KEY_MEMBER_ID, a.getId()), + new BdfEntry(KEY_MEMBER_NAME, a.getName()), + new BdfEntry(KEY_MEMBER_PUBLIC_KEY, a.getPublicKey()) + )); + clientHelper.mergeGroupMetadata(txn, g, meta); + } + + private Author getAuthor(BdfDictionary meta) throws FormatException { + AuthorId authorId = new AuthorId(meta.getRaw(KEY_MEMBER_ID)); + String name = meta.getString(KEY_MEMBER_NAME); + byte[] publicKey = meta.getRaw(KEY_MEMBER_PUBLIC_KEY); + return new Author(authorId, name, publicKey); + } + }