Wrap messages, introductions and forum invites in a ConversationManager

This commit is contained in:
str4d
2016-06-02 03:51:08 +00:00
parent 67aa7c3269
commit c333052396
41 changed files with 1041 additions and 814 deletions

View File

@@ -2,6 +2,7 @@ package org.briarproject;
import org.briarproject.blogs.BlogsModule;
import org.briarproject.contact.ContactModule;
import org.briarproject.conversation.ConversationModule;
import org.briarproject.crypto.CryptoModule;
import org.briarproject.db.DatabaseExecutorModule;
import org.briarproject.forum.ForumModule;
@@ -22,6 +23,8 @@ public interface CoreEagerSingletons {
void inject(ContactModule.EagerSingletons init);
void inject(ConversationModule.EagerSingletons init);
void inject(CryptoModule.EagerSingletons init);
void inject(DatabaseExecutorModule.EagerSingletons init);

View File

@@ -3,6 +3,7 @@ package org.briarproject;
import org.briarproject.blogs.BlogsModule;
import org.briarproject.clients.ClientsModule;
import org.briarproject.contact.ContactModule;
import org.briarproject.conversation.ConversationModule;
import org.briarproject.crypto.CryptoModule;
import org.briarproject.data.DataModule;
import org.briarproject.db.DatabaseExecutorModule;
@@ -31,6 +32,7 @@ import dagger.Module;
BlogsModule.class,
ClientsModule.class,
ContactModule.class,
ConversationModule.class,
CryptoModule.class,
DataModule.class,
DatabaseModule.class,
@@ -58,6 +60,7 @@ public class CoreModule {
public static void initEagerSingletons(CoreEagerSingletons c) {
c.inject(new BlogsModule.EagerSingletons());
c.inject(new ContactModule.EagerSingletons());
c.inject(new ConversationModule.EagerSingletons());
c.inject(new CryptoModule.EagerSingletons());
c.inject(new DatabaseExecutorModule.EagerSingletons());
c.inject(new ForumModule.EagerSingletons());

View File

@@ -0,0 +1,46 @@
package org.briarproject.conversation;
import org.briarproject.api.conversation.ConversationForumInvitationItem;
import org.briarproject.api.conversation.ConversationItem;
import org.briarproject.api.forum.ForumInvitationMessage;
class ConversationForumInvitationItemImpl {
static ConversationItem from(ForumInvitationMessage i) {
return i.isLocal() ? new Outgoing(i) : new Incoming(i);
}
static class Outgoing extends OutgoingConversationItem
implements ConversationForumInvitationItem {
private final ForumInvitationMessage fim;
public Outgoing(ForumInvitationMessage fim) {
super(fim.getId(), fim.getTimestamp(), fim.isSent(), fim.isSeen());
this.fim = fim;
}
@Override
public ForumInvitationMessage getForumInvitationMessage() {
return fim;
}
}
static class Incoming extends IncomingConversationItem
implements ConversationForumInvitationItem {
private final ForumInvitationMessage fim;
public Incoming(ForumInvitationMessage fim) {
super(fim.getId(), fim.getTimestamp(), fim.isRead());
this.fim = fim;
}
@Override
public ForumInvitationMessage getForumInvitationMessage() {
return fim;
}
}
}

View File

@@ -0,0 +1,71 @@
package org.briarproject.conversation;
import org.briarproject.api.conversation.ConversationIntroductionRequestItem;
import org.briarproject.api.conversation.ConversationItem;
import org.briarproject.api.introduction.IntroductionRequest;
class ConversationIntroductionRequestItemImpl {
static ConversationItem from(IntroductionRequest i) {
return i.isLocal() ? new Outgoing(i) : new Incoming(i);
}
static class Outgoing extends OutgoingConversationItem
implements ConversationIntroductionRequestItem {
private final IntroductionRequest ir;
private boolean answered;
public Outgoing(IntroductionRequest ir) {
super(ir.getMessageId(), ir.getTimestamp(), ir.isSent(), ir.isSeen());
this.ir = ir;
this.answered = ir.wasAnswered();
}
@Override
public IntroductionRequest getIntroductionRequest() {
return ir;
}
@Override
public boolean wasAnswered() {
return answered;
}
@Override
public void setAnswered(boolean answered) {
this.answered = answered;
}
}
// This class is not thread-safe
static class Incoming extends IncomingConversationItem
implements ConversationIntroductionRequestItem {
private final IntroductionRequest ir;
private boolean answered;
public Incoming(IntroductionRequest ir) {
super(ir.getMessageId(), ir.getTimestamp(), ir.isRead());
this.ir = ir;
this.answered = ir.wasAnswered();
}
@Override
public IntroductionRequest getIntroductionRequest() {
return ir;
}
@Override
public boolean wasAnswered() {
return answered;
}
@Override
public void setAnswered(boolean answered) {
this.answered = answered;
}
}
}

View File

@@ -0,0 +1,46 @@
package org.briarproject.conversation;
import org.briarproject.api.conversation.ConversationIntroductionResponseItem;
import org.briarproject.api.conversation.ConversationItem;
import org.briarproject.api.introduction.IntroductionResponse;
class ConversationIntroductionResponseItemImpl {
static ConversationItem from(IntroductionResponse i) {
return i.isLocal() ? new Outgoing(i) : new Incoming(i);
}
static class Outgoing extends OutgoingConversationItem
implements ConversationIntroductionResponseItem {
private final IntroductionResponse ir;
public Outgoing(IntroductionResponse ir) {
super(ir.getMessageId(), ir.getTimestamp(), ir.isSent(), ir.isSeen());
this.ir = ir;
}
@Override
public IntroductionResponse getIntroductionResponse() {
return ir;
}
}
static class Incoming extends IncomingConversationItem
implements ConversationIntroductionResponseItem {
private final IntroductionResponse ir;
public Incoming(IntroductionResponse ir) {
super(ir.getMessageId(), ir.getTimestamp(), ir.isRead());
this.ir = ir;
}
@Override
public IntroductionResponse getIntroductionResponse() {
return ir;
}
}
}

View File

@@ -0,0 +1,23 @@
package org.briarproject.conversation;
import org.briarproject.api.conversation.ConversationItem;
import org.briarproject.api.sync.MessageId;
public abstract class ConversationItemImpl implements ConversationItem {
private final MessageId id;
private final long time;
public ConversationItemImpl(MessageId id, long time) {
this.id = id;
this.time = time;
}
public MessageId getId() {
return id;
}
public long getTime() {
return time;
}
}

View File

@@ -0,0 +1,200 @@
package org.briarproject.conversation;
import org.briarproject.api.FormatException;
import org.briarproject.api.clients.SessionId;
import org.briarproject.api.contact.ContactId;
import org.briarproject.api.conversation.ConversationForumInvitationItem;
import org.briarproject.api.conversation.ConversationIntroductionRequestItem;
import org.briarproject.api.conversation.ConversationIntroductionResponseItem;
import org.briarproject.api.conversation.ConversationItem;
import org.briarproject.api.conversation.ConversationItem.Partial;
import org.briarproject.api.conversation.ConversationManager;
import org.briarproject.api.conversation.ConversationMessageItem;
import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.forum.ForumInvitationMessage;
import org.briarproject.api.forum.ForumSharingManager;
import org.briarproject.api.introduction.IntroductionManager;
import org.briarproject.api.introduction.IntroductionMessage;
import org.briarproject.api.introduction.IntroductionRequest;
import org.briarproject.api.introduction.IntroductionResponse;
import org.briarproject.api.messaging.MessagingManager;
import org.briarproject.api.messaging.PrivateMessage;
import org.briarproject.api.messaging.PrivateMessageHeader;
import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.GroupId;
import org.briarproject.api.sync.MessageId;
import org.briarproject.util.StringUtils;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
import java.util.logging.Logger;
import javax.inject.Inject;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
public class ConversationManagerImpl implements ConversationManager {
static final ClientId CLIENT_ID = new ClientId(StringUtils.fromHexString(
"05313ec596871e5305181220488fc9c0"
+ "3d44985a48a6d0fb8767da3a6cae1cb4"));
private static final Logger LOG =
Logger.getLogger(ConversationManagerImpl.class.getName());
private final Executor dbExecutor;
private final ForumSharingManager forumSharingManager;
private final IntroductionManager introductionManager;
private final MessagingManager messagingManager;
private Map<MessageId, byte[]> bodyCache =
new ConcurrentHashMap<MessageId, byte[]>();
@Inject
ConversationManagerImpl(@DatabaseExecutor Executor dbExecutor,
ForumSharingManager forumSharingManager,
IntroductionManager introductionManager,
MessagingManager messagingManager) {
this.dbExecutor = dbExecutor;
this.forumSharingManager = forumSharingManager;
this.introductionManager = introductionManager;
this.messagingManager = messagingManager;
}
@Override
public ClientId getClientId() {
return CLIENT_ID;
}
@Override
public ConversationItem addLocalMessage(PrivateMessage m, byte[] body)
throws DbException {
messagingManager.addLocalMessage(m);
bodyCache.put(m.getMessage().getId(), body);
PrivateMessageHeader h = new PrivateMessageHeader(
m.getMessage().getId(),
m.getMessage().getTimestamp(), m.getContentType(),
true, false, false, false);
ConversationItem item = ConversationMessageItemImpl.from(h);
((Partial) item).setContent(body);
return item;
}
@Override
public ContactId getContactId(GroupId g) throws DbException {
return messagingManager.getContactId(g);
}
@Override
public GroupId getConversationId(ContactId c) throws DbException {
return messagingManager.getConversationId(c);
}
@Override
public List<ConversationItem> getMessages(ContactId c)
throws DbException {
return getMessages(c, true);
}
@Override
public List<ConversationItem> getMessages(ContactId c, boolean content)
throws DbException {
Collection<PrivateMessageHeader> headers =
messagingManager.getMessageHeaders(c);
Collection<IntroductionMessage> introductions =
introductionManager.getIntroductionMessages(c);
Collection<ForumInvitationMessage> invitations =
forumSharingManager.getInvitationMessages(c);
List<ConversationItem> items = new ArrayList<ConversationItem>();
for (PrivateMessageHeader h : headers) {
ConversationItem item = ConversationMessageItemImpl.from(h);
if (content) {
byte[] body = bodyCache.get(h.getId());
if (body == null) loadMessageContent((Partial) item);
else ((Partial) item).setContent(body);
}
items.add(item);
}
for (IntroductionMessage m : introductions) {
ConversationItem item;
if (m instanceof IntroductionRequest) {
item = ConversationIntroductionRequestItemImpl.from(
(IntroductionRequest) m);
} else {
item = ConversationIntroductionResponseItemImpl.from(
(IntroductionResponse) m);
}
items.add(item);
}
for (ForumInvitationMessage i : invitations) {
ConversationItem item = ConversationForumInvitationItemImpl.from(i);
items.add(item);
}
return items;
}
@Override
public void loadMessageContent(final Partial m) {
dbExecutor.execute(new Runnable() {
@Override
public void run() {
try {
MessageId id = m.getId();
long now = System.currentTimeMillis();
byte[] body = messagingManager.getMessageBody(id);
long duration = System.currentTimeMillis() - now;
if (LOG.isLoggable(INFO))
LOG.info("Loading message took " + duration + " ms");
bodyCache.put(id, body);
m.setContent(body);
} catch (NoSuchMessageException e) {
// The item will be removed when we get the event
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
}
}
});
}
@Override
public void respondToItem(ContactId c, ConversationItem item,
boolean accept, long timestamp)
throws DbException, FormatException {
if (item instanceof ConversationIntroductionRequestItem) {
SessionId sessionId = ((ConversationIntroductionRequestItem) item)
.getIntroductionRequest().getSessionId();
if (accept) {
introductionManager
.acceptIntroduction(c, sessionId,
timestamp);
} else {
introductionManager
.declineIntroduction(c, sessionId,
timestamp);
}
}
}
@Override
public void setReadFlag(ConversationItem item, boolean read)
throws DbException {
MessageId id = item.getId();
if (item instanceof ConversationMessageItem) {
messagingManager.setReadFlag(id, read);
} else if (item instanceof ConversationIntroductionRequestItem ||
item instanceof ConversationIntroductionResponseItem) {
introductionManager.setReadFlag(id, read);
} else if (item instanceof ConversationForumInvitationItem) {
forumSharingManager.setReadFlag(id, read);
}
}
}

View File

@@ -0,0 +1,131 @@
package org.briarproject.conversation;
import org.briarproject.api.conversation.ConversationItem;
import org.briarproject.api.conversation.ConversationMessageItem;
import org.briarproject.api.messaging.PrivateMessageHeader;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class ConversationMessageItemImpl {
static ConversationItem from(PrivateMessageHeader h) {
return h.isLocal() ? new Outgoing(h) : new Incoming(h);
}
static class Outgoing extends OutgoingConversationItem
implements ConversationMessageItem {
private final PrivateMessageHeader header;
private byte[] body;
private ContentListener listener;
private final ReentrantReadWriteLock bodyLock =
new ReentrantReadWriteLock();
private final ReentrantReadWriteLock listenerLock =
new ReentrantReadWriteLock();
public Outgoing(PrivateMessageHeader header) {
super(header.getId(), header.getTimestamp(), header.isSent(),
header.isSeen());
this.header = header;
body = null;
listener = null;
}
@Override
public PrivateMessageHeader getHeader() {
return header;
}
@Override
public byte[] getBody() {
bodyLock.readLock().lock();
byte[] ret = body;
bodyLock.readLock().unlock();
return ret;
}
@Override
public void setContent(byte[] content) {
bodyLock.writeLock().lock();
this.body = content;
bodyLock.writeLock().unlock();
listenerLock.readLock().lock();
if (listener != null) {
listener.contentReady();
}
listenerLock.readLock().unlock();
}
@Override
public void setContentListener(
ContentListener listener) {
listenerLock.writeLock().lock();
this.listener = listener;
listenerLock.writeLock().unlock();
bodyLock.readLock().lock();
if (body != null) {
listener.contentReady();
}
bodyLock.readLock().unlock();
}
}
static class Incoming extends IncomingConversationItem
implements ConversationMessageItem {
private final PrivateMessageHeader header;
private byte[] body;
private ContentListener listener;
private final ReentrantReadWriteLock bodyLock =
new ReentrantReadWriteLock();
private final ReentrantReadWriteLock listenerLock =
new ReentrantReadWriteLock();
public Incoming(PrivateMessageHeader header) {
super(header.getId(), header.getTimestamp(), header.isRead());
this.header = header;
body = null;
listener = null;
}
@Override
public PrivateMessageHeader getHeader() {
return header;
}
@Override
public byte[] getBody() {
bodyLock.readLock().lock();
byte[] ret = body;
bodyLock.readLock().unlock();
return ret;
}
@Override
public void setContent(byte[] content) {
bodyLock.writeLock().lock();
this.body = content;
bodyLock.writeLock().unlock();
listenerLock.readLock().lock();
if (listener != null) {
listener.contentReady();
}
listenerLock.readLock().unlock();
}
@Override
public void setContentListener(
ContentListener listener) {
listenerLock.writeLock().lock();
this.listener = listener;
listenerLock.writeLock().unlock();
bodyLock.readLock().lock();
if (body != null) {
listener.contentReady();
}
bodyLock.readLock().unlock();
}
}
}

View File

@@ -0,0 +1,25 @@
package org.briarproject.conversation;
import org.briarproject.api.conversation.ConversationManager;
import javax.inject.Inject;
import javax.inject.Singleton;
import dagger.Module;
import dagger.Provides;
@Module
public class ConversationModule {
public static class EagerSingletons {
@Inject
ConversationManager conversationManager;
}
@Provides
@Singleton
ConversationManager getConversationManager(
ConversationManagerImpl conversationManager) {
return conversationManager;
}
}

View File

@@ -0,0 +1,27 @@
package org.briarproject.conversation;
import org.briarproject.api.conversation.ConversationItem.IncomingItem;
import org.briarproject.api.sync.MessageId;
// This class is not thread-safe
class IncomingConversationItem extends ConversationItemImpl
implements IncomingItem {
private boolean read;
public IncomingConversationItem(MessageId id, long time, boolean read) {
super(id, time);
this.read = read;
}
@Override
public boolean isRead() {
return read;
}
@Override
public void setRead(boolean read) {
this.read = read;
}
}

View File

@@ -0,0 +1,39 @@
package org.briarproject.conversation;
import org.briarproject.api.conversation.ConversationItem.OutgoingItem;
import org.briarproject.api.sync.MessageId;
// This class is not thread-safe
class OutgoingConversationItem extends ConversationItemImpl
implements OutgoingItem {
private boolean sent, seen;
public OutgoingConversationItem(MessageId id, long time, boolean sent,
boolean seen) {
super(id, time);
this.sent = sent;
this.seen = seen;
}
@Override
public boolean isSent() {
return sent;
}
@Override
public void setSent(boolean sent) {
this.sent = sent;
}
@Override
public boolean isSeen() {
return seen;
}
@Override
public void setSeen(boolean seen) {
this.seen = seen;
}
}

View File

@@ -457,6 +457,17 @@ class IntroductionManagerImpl extends BdfIncomingMessageHook
return list;
}
@Override
public void setReadFlag(MessageId m, boolean read) throws DbException {
try {
BdfDictionary meta = new BdfDictionary();
meta.put(READ, read);
clientHelper.mergeMessageMetadata(m, meta);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
private String getNameForIntroducer(ContactId contactId,
BdfDictionary state) throws FormatException {

View File

@@ -492,6 +492,17 @@ abstract class SharingManagerImpl<S extends Shareable, I extends Invitation, IM
}
}
@Override
public void setReadFlag(MessageId m, boolean read) throws DbException {
try {
BdfDictionary meta = new BdfDictionary();
meta.put(READ, read);
clientHelper.mergeMessageMetadata(m, meta);
} catch (FormatException e) {
throw new RuntimeException(e);
}
}
void removingShareable(Transaction txn, S f) throws DbException {
try {
for (Contact c : db.getContacts(txn)) {