Handle invitations to the same forum by multiple contacts

Closes #391
This commit is contained in:
Torsten Grote
2016-05-12 17:00:14 -03:00
parent 1a175beac9
commit 38a4f73cdc
4 changed files with 163 additions and 48 deletions

View File

@@ -49,6 +49,7 @@ import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection;
import java.util.List; import java.util.List;
import java.util.concurrent.TimeoutException; import java.util.concurrent.TimeoutException;
import java.util.logging.Logger; import java.util.logging.Logger;
@@ -65,15 +66,15 @@ import static org.junit.Assert.assertTrue;
public class ForumSharingIntegrationTest extends BriarTestCase { public class ForumSharingIntegrationTest extends BriarTestCase {
LifecycleManager lifecycleManager0, lifecycleManager1; LifecycleManager lifecycleManager0, lifecycleManager1, lifecycleManager2;
SyncSessionFactory sync0, sync1; SyncSessionFactory sync0, sync1, sync2;
ForumManager forumManager0, forumManager1; ForumManager forumManager0, forumManager1, forumManager2;
ContactManager contactManager0, contactManager1; ContactManager contactManager0, contactManager1, contactManager2;
ContactId contactId0, contactId1; ContactId contactId0, contactId2, contactId1, contactId21;
IdentityManager identityManager0, identityManager1; IdentityManager identityManager0, identityManager1, identityManager2;
LocalAuthor author0, author1; LocalAuthor author0, author1, author2;
Forum forum0; Forum forum0;
SharerListener listener0; SharerListener listener0, listener2;
InviteeListener listener1; InviteeListener listener1;
@Inject @Inject
@@ -84,6 +85,7 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
// objects accessed from background threads need to be volatile // objects accessed from background threads need to be volatile
private volatile ForumSharingManager forumSharingManager0; private volatile ForumSharingManager forumSharingManager0;
private volatile ForumSharingManager forumSharingManager1; private volatile ForumSharingManager forumSharingManager1;
private volatile ForumSharingManager forumSharingManager2;
private volatile Waiter eventWaiter; private volatile Waiter eventWaiter;
private volatile Waiter msgWaiter; private volatile Waiter msgWaiter;
@@ -92,11 +94,12 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
private final int TIMEOUT = 15000; private final int TIMEOUT = 15000;
private final String SHARER = "Sharer"; private final String SHARER = "Sharer";
private final String INVITEE = "Invitee"; private final String INVITEE = "Invitee";
private final String SHARER2 = "Sharer2";
private static final Logger LOG = private static final Logger LOG =
Logger.getLogger(ForumSharingIntegrationTest.class.getName()); Logger.getLogger(ForumSharingIntegrationTest.class.getName());
private ForumSharingIntegrationTestComponent t0, t1; private ForumSharingIntegrationTestComponent t0, t1, t2;
@Rule @Rule
public ExpectedException thrown=ExpectedException.none(); public ExpectedException thrown=ExpectedException.none();
@@ -117,17 +120,26 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
t1 = DaggerForumSharingIntegrationTestComponent.builder() t1 = DaggerForumSharingIntegrationTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t1Dir)).build(); .testDatabaseModule(new TestDatabaseModule(t1Dir)).build();
injectEagerSingletons(t1); injectEagerSingletons(t1);
File t2Dir = new File(testDir, SHARER2);
t2 = DaggerForumSharingIntegrationTestComponent.builder()
.testDatabaseModule(new TestDatabaseModule(t2Dir)).build();
injectEagerSingletons(t2);
identityManager0 = t0.getIdentityManager(); identityManager0 = t0.getIdentityManager();
identityManager1 = t1.getIdentityManager(); identityManager1 = t1.getIdentityManager();
identityManager2 = t2.getIdentityManager();
contactManager0 = t0.getContactManager(); contactManager0 = t0.getContactManager();
contactManager1 = t1.getContactManager(); contactManager1 = t1.getContactManager();
contactManager2 = t2.getContactManager();
forumManager0 = t0.getForumManager(); forumManager0 = t0.getForumManager();
forumManager1 = t1.getForumManager(); forumManager1 = t1.getForumManager();
forumManager2 = t2.getForumManager();
forumSharingManager0 = t0.getForumSharingManager(); forumSharingManager0 = t0.getForumSharingManager();
forumSharingManager1 = t1.getForumSharingManager(); forumSharingManager1 = t1.getForumSharingManager();
forumSharingManager2 = t2.getForumSharingManager();
sync0 = t0.getSyncSessionFactory(); sync0 = t0.getSyncSessionFactory();
sync1 = t1.getSyncSessionFactory(); sync1 = t1.getSyncSessionFactory();
sync2 = t2.getSyncSessionFactory();
// initialize waiters fresh for each test // initialize waiters fresh for each test
eventWaiter = new Waiter(); eventWaiter = new Waiter();
@@ -474,7 +486,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
assertEquals(1, forumManager1.getForums().size()); assertEquals(1, forumManager1.getForums().size());
// invitee now shares same forum back // invitee now shares same forum back
forumSharingManager1.sendForumInvitation(forum0.getId(), contactId0, forumSharingManager1.sendForumInvitation(forum0.getId(),
contactId0,
"I am re-sharing this forum with you."); "I am re-sharing this forum with you.");
// sync re-share invitation // sync re-share invitation
@@ -526,7 +539,9 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
// contacts now remove each other // contacts now remove each other
contactManager0.removeContact(contactId1); contactManager0.removeContact(contactId1);
contactManager2.removeContact(contactId21);
contactManager1.removeContact(contactId0); contactManager1.removeContact(contactId0);
contactManager1.removeContact(contactId2);
// make sure sharer does share the forum with nobody now // make sure sharer does share the forum with nobody now
assertEquals(0, assertEquals(0,
@@ -572,6 +587,75 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
} }
} }
@Test
public void testTwoContactsShareSameForum() throws Exception {
startLifecycles();
try {
// initialize
addDefaultIdentities();
addDefaultContacts();
addForumForSharer();
// second sharer adds the same forum
DatabaseComponent db2 = t2.getDatabaseComponent();
Transaction txn = db2.startTransaction(false);
db2.addGroup(txn, forum0.getGroup());
txn.setComplete();
db2.endTransaction(txn);
// add listeners
listener0 = new SharerListener();
t0.getEventBus().addListener(listener0);
listener1 = new InviteeListener(true, false);
t1.getEventBus().addListener(listener1);
listener2 = new SharerListener();
t2.getEventBus().addListener(listener2);
// send invitation
forumSharingManager0
.sendForumInvitation(forum0.getId(), contactId1, "Hi!");
// sync first request message
syncToInvitee();
// second sharer sends invitation for same forum
forumSharingManager2
.sendForumInvitation(forum0.getId(), contactId1, null);
// sync second request message
deliverMessage(sync2, contactId2, sync1, contactId1,
"Sharer2 to Invitee");
// make sure we have only one forum available
Collection<Forum> forums =
forumSharingManager1.getAvailableForums();
assertEquals(1, forums.size());
// make sure both sharers actually share the forum
Collection<Contact> contacts =
forumSharingManager1.getSharedBy(forum0.getId());
assertEquals(2, contacts.size());
// answer second request
Contact c2 = contactManager1.getContact(contactId2);
forumSharingManager1.respondToInvitation(forum0, c2, true);
// sync response
deliverMessage(sync1, contactId21, sync2, contactId2,
"Invitee to Sharer2");
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener2.responseReceived);
// answer first request
Contact c0 =
contactManager1.getContact(contactId0);
forumSharingManager1.respondToInvitation(forum0, c0, true);
// sync response
syncToSharer();
eventWaiter.await(TIMEOUT, 1);
assertTrue(listener0.responseReceived);
} finally {
stopLifecycles();
}
}
@After @After
public void tearDown() throws InterruptedException { public void tearDown() throws InterruptedException {
@@ -609,7 +693,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
requestReceived = true; requestReceived = true;
Forum f = event.getForum(); Forum f = event.getForum();
try { try {
forumSharingManager0.respondToInvitation(f, true); Contact c = contactManager0.getContact(contactId1);
forumSharingManager0.respondToInvitation(f, c, true);
} catch (DbException ex) { } catch (DbException ex) {
eventWaiter.rethrow(ex); eventWaiter.rethrow(ex);
} finally { } finally {
@@ -624,10 +709,15 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
public volatile boolean requestReceived = false; public volatile boolean requestReceived = false;
public volatile boolean responseReceived = false; public volatile boolean responseReceived = false;
private final boolean accept; private final boolean accept, answer;
InviteeListener(boolean accept, boolean answer) {
this.accept = accept;
this.answer = answer;
}
InviteeListener(boolean accept) { InviteeListener(boolean accept) {
this.accept = accept; this(accept, true);
} }
public void eventOccurred(Event e) { public void eventOccurred(Event e) {
@@ -644,11 +734,13 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
} else if (e instanceof ForumInvitationReceivedEvent) { } else if (e instanceof ForumInvitationReceivedEvent) {
ForumInvitationReceivedEvent event = ForumInvitationReceivedEvent event =
(ForumInvitationReceivedEvent) e; (ForumInvitationReceivedEvent) e;
eventWaiter.assertEquals(contactId0, event.getContactId());
requestReceived = true; requestReceived = true;
if (!answer) return;
Forum f = event.getForum(); Forum f = event.getForum();
try { try {
forumSharingManager1.respondToInvitation(f, accept); Contact c =
contactManager1.getContact(event.getContactId());
forumSharingManager1.respondToInvitation(f, c, accept);
} catch (DbException ex) { } catch (DbException ex) {
eventWaiter.rethrow(ex); eventWaiter.rethrow(ex);
} finally { } finally {
@@ -670,18 +762,23 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
// Start the lifecycle manager and wait for it to finish // Start the lifecycle manager and wait for it to finish
lifecycleManager0 = t0.getLifecycleManager(); lifecycleManager0 = t0.getLifecycleManager();
lifecycleManager1 = t1.getLifecycleManager(); lifecycleManager1 = t1.getLifecycleManager();
lifecycleManager2 = t2.getLifecycleManager();
lifecycleManager0.startServices(); lifecycleManager0.startServices();
lifecycleManager1.startServices(); lifecycleManager1.startServices();
lifecycleManager2.startServices();
lifecycleManager0.waitForStartup(); lifecycleManager0.waitForStartup();
lifecycleManager1.waitForStartup(); lifecycleManager1.waitForStartup();
lifecycleManager2.waitForStartup();
} }
private void stopLifecycles() throws InterruptedException { private void stopLifecycles() throws InterruptedException {
// Clean up // Clean up
lifecycleManager0.stopServices(); lifecycleManager0.stopServices();
lifecycleManager1.stopServices(); lifecycleManager1.stopServices();
lifecycleManager2.stopServices();
lifecycleManager0.waitForShutdown(); lifecycleManager0.waitForShutdown();
lifecycleManager1.waitForShutdown(); lifecycleManager1.waitForShutdown();
lifecycleManager2.waitForShutdown();
} }
private void defaultInit(boolean accept) throws DbException { private void defaultInit(boolean accept) throws DbException {
@@ -700,6 +797,10 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH), TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
TestUtils.getRandomBytes(123)); TestUtils.getRandomBytes(123));
identityManager1.addLocalAuthor(author1); identityManager1.addLocalAuthor(author1);
author2 = authorFactory.createLocalAuthor(SHARER2,
TestUtils.getRandomBytes(MAX_PUBLIC_KEY_LENGTH),
TestUtils.getRandomBytes(123));
identityManager2.addLocalAuthor(author2);
} }
private void addDefaultContacts() throws DbException { private void addDefaultContacts() throws DbException {
@@ -708,11 +809,20 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
author0.getId(), master, clock.currentTimeMillis(), true, author0.getId(), master, clock.currentTimeMillis(), true,
true true
); );
// invitee adds sharer back // second sharer does the same
contactId21 = contactManager2.addContact(author1,
author2.getId(), master, clock.currentTimeMillis(), true,
true
);
// invitee adds sharers back
contactId0 = contactManager1.addContact(author0, contactId0 = contactManager1.addContact(author0,
author1.getId(), master, clock.currentTimeMillis(), true, author1.getId(), master, clock.currentTimeMillis(), true,
true true
); );
contactId2 = contactManager1.addContact(author2,
author1.getId(), master, clock.currentTimeMillis(), true,
true
);
} }
private void addForumForSharer() throws DbException { private void addForumForSharer() throws DbException {
@@ -725,6 +835,8 @@ public class ForumSharingIntegrationTest extends BriarTestCase {
t0.getEventBus().addListener(listener0); t0.getEventBus().addListener(listener0);
listener1 = new InviteeListener(accept); listener1 = new InviteeListener(accept);
t1.getEventBus().addListener(listener1); t1.getEventBus().addListener(listener1);
listener2 = new SharerListener();
t2.getEventBus().addListener(listener2);
} }
private void syncToInvitee() throws IOException, TimeoutException { private void syncToInvitee() throws IOException, TimeoutException {

View File

@@ -153,7 +153,7 @@ public class AvailableForumsActivity extends BriarActivity
@Override @Override
public void onItemClick(AvailableForumsItem item, boolean accept) { public void onItemClick(AvailableForumsItem item, boolean accept) {
respondToInvitation(item.getForum(), accept); respondToInvitation(item, accept);
// show toast // show toast
int res = R.string.forum_declined_toast; int res = R.string.forum_declined_toast;
@@ -161,12 +161,16 @@ public class AvailableForumsActivity extends BriarActivity
Toast.makeText(this, res, LENGTH_SHORT).show(); Toast.makeText(this, res, LENGTH_SHORT).show();
} }
private void respondToInvitation(final Forum f, final boolean accept) { private void respondToInvitation(final AvailableForumsItem item,
runOnDbThread(new Runnable() { final boolean accept) {
briarController.runOnDbThread(new Runnable() {
@Override @Override
public void run() { public void run() {
try { try {
forumSharingManager.respondToInvitation(f, accept); Forum f = item.getForum();
for (Contact c : item.getContacts()) {
forumSharingManager.respondToInvitation(f, c, accept);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e); LOG.log(WARNING, e.toString(), e);

View File

@@ -24,7 +24,8 @@ public interface ForumSharingManager {
/** /**
* Responds to a pending forum invitation * Responds to a pending forum invitation
*/ */
void respondToInvitation(Forum f, boolean accept) throws DbException; void respondToInvitation(Forum f, Contact c, boolean accept)
throws DbException;
/** /**
* Returns all forum sharing messages sent by the Contact * Returns all forum sharing messages sent by the Contact

View File

@@ -283,13 +283,14 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
} }
@Override @Override
public void respondToInvitation(Forum f, boolean accept) public void respondToInvitation(Forum f, Contact c, boolean accept)
throws DbException { throws DbException {
Transaction txn = db.startTransaction(false); Transaction txn = db.startTransaction(false);
try { try {
// find session state based on forum // find session state based on forum
InviteeSessionState localState = getSessionStateForResponse(txn, f); InviteeSessionState localState =
getSessionStateForResponse(txn, f, c);
// define action // define action
InviteeSessionState.Action localAction; InviteeSessionState.Action localAction;
@@ -562,17 +563,15 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
"More than one session state found for message with session ID " + "More than one session state found for message with session ID " +
Arrays.hashCode(sessionId.getBytes())); Arrays.hashCode(sessionId.getBytes()));
} }
if (map.isEmpty()) {
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { if (warn && LOG.isLoggable(WARNING)) {
return fromBdfDictionary(m.getValue()); LOG.warning(
"No session state found for message with session ID " +
Arrays.hashCode(sessionId.getBytes()));
}
throw new FormatException();
} }
return fromBdfDictionary(map.values().iterator().next());
if (warn && LOG.isLoggable(WARNING)) {
LOG.warning(
"No session state found for message with session ID " +
Arrays.hashCode(sessionId.getBytes()));
}
throw new FormatException();
} }
} }
@@ -590,11 +589,12 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
} }
private InviteeSessionState getSessionStateForResponse(Transaction txn, private InviteeSessionState getSessionStateForResponse(Transaction txn,
Forum f) throws DbException, FormatException { Forum f, Contact c) throws DbException, FormatException {
// query for invitee states for that forum in state await response // query for invitee states for that forum in state await response
BdfDictionary query = BdfDictionary.of( BdfDictionary query = BdfDictionary.of(
new BdfEntry(IS_SHARER, false), new BdfEntry(IS_SHARER, false),
new BdfEntry(CONTACT_ID, c.getId().getInt()),
new BdfEntry(FORUM_ID, f.getId()), new BdfEntry(FORUM_ID, f.getId()),
new BdfEntry(STATE, new BdfEntry(STATE,
InviteeSessionState.State.AWAIT_LOCAL_RESPONSE InviteeSessionState.State.AWAIT_LOCAL_RESPONSE
@@ -604,26 +604,24 @@ class ForumSharingManagerImpl extends BdfIncomingMessageHook
Map<MessageId, BdfDictionary> map = clientHelper Map<MessageId, BdfDictionary> map = clientHelper
.getMessageMetadataAsDictionary(txn, localGroup.getId(), query); .getMessageMetadataAsDictionary(txn, localGroup.getId(), query);
// TODO we might have been invited to the same forum from several people
// in different sessions, so we need to handle this (#391)
if (map.size() > 1 && LOG.isLoggable(WARNING)) { if (map.size() > 1 && LOG.isLoggable(WARNING)) {
LOG.warning( LOG.warning(
"More than one session state found for forum with ID " + "More than one session state found for forum with ID " +
Arrays.hashCode(f.getId().getBytes()) + Arrays.hashCode(f.getId().getBytes()) +
" in state AWAIT_LOCAL_RESPONSE"); " in state AWAIT_LOCAL_RESPONSE for contact " +
c.getAuthor().getName());
} }
if (map.isEmpty()) {
for (Map.Entry<MessageId, BdfDictionary> m : map.entrySet()) { if (LOG.isLoggable(WARNING)) {
return (InviteeSessionState) fromBdfDictionary(m.getValue()); LOG.warning(
"No session state found for forum with ID " +
Arrays.hashCode(f.getId().getBytes()) +
" in state AWAIT_LOCAL_RESPONSE");
}
throw new DbException();
} }
return (InviteeSessionState) fromBdfDictionary(
if (LOG.isLoggable(WARNING)) { map.values().iterator().next());
LOG.warning(
"No session state found for forum with ID " +
Arrays.hashCode(f.getId().getBytes()) +
" in state AWAIT_LOCAL_RESPONSE");
}
throw new DbException();
} }
private ForumSharingSessionState getSessionStateForLeaving(Transaction txn, private ForumSharingSessionState getSessionStateForLeaving(Transaction txn,