Moved transactions out of database component.

This commit is contained in:
akwizgran
2016-02-11 13:35:46 +00:00
parent ef2b2b9710
commit de8cc50fb4
24 changed files with 1828 additions and 1451 deletions

View File

@@ -1,6 +1,7 @@
package org.briarproject.api.contact; package org.briarproject.api.contact;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
@@ -30,10 +31,10 @@ public interface ContactManager {
void removeContact(ContactId c) throws DbException; void removeContact(ContactId c) throws DbException;
interface AddContactHook { interface AddContactHook {
void addingContact(Contact c); void addingContact(Transaction txn, Contact c) throws DbException;
} }
interface RemoveContactHook { interface RemoveContactHook {
void removingContact(Contact c); void removingContact(Transaction txn, Contact c) throws DbException;
} }
} }

View File

@@ -26,72 +26,85 @@ import java.util.Map;
/** /**
* Encapsulates the database implementation and exposes high-level operations * Encapsulates the database implementation and exposes high-level operations
* to other components. * to other components.
* <p> * <p/>
* This interface's methods are blocking, but they do not call out into other * This interface's methods are blocking, but they do not call out into other
* components except to broadcast {@link org.briarproject.api.event.Event * components except to broadcast {@link org.briarproject.api.event.Event
* Events}, so they can safely be called while holding locks. * Events}, so they can safely be called while holding locks.
*/ */
public interface DatabaseComponent { public interface DatabaseComponent {
/** Opens the database and returns true if the database already existed. */ /**
* Opens the database and returns true if the database already existed.
*/
boolean open() throws DbException; boolean open() throws DbException;
/** Waits for any open transactions to finish and closes the database. */ /**
* Waits for any open transactions to finish and closes the database.
*/
void close() throws DbException, IOException; void close() throws DbException, IOException;
/** Starts a new transaction and returns an object representing it. */ /**
* Starts a new transaction and returns an object representing it.
*/
Transaction startTransaction() throws DbException; Transaction startTransaction() throws DbException;
/** /**
* Aborts the given transaction - no changes made during the transaction * Ends a transaction. If the transaction's
* will be applied to the database. * {@link Transaction#setComplete() commit} flag is set, the
* transaction is committed, otherwise it is aborted.
*/ */
void abortTransaction(Transaction txn); void endTransaction(Transaction txn) throws DbException;
/**
* Commits the given transaction - all changes made during the transaction
* will be applied to the database.
*/
void commitTransaction(Transaction txn) throws DbException;
/** /**
* Stores a contact associated with the given local and remote pseudonyms, * Stores a contact associated with the given local and remote pseudonyms,
* and returns an ID for the contact. * and returns an ID for the contact.
*/ */
ContactId addContact(Author remote, AuthorId local) throws DbException; ContactId addContact(Transaction txn, Author remote, AuthorId local)
/** Stores a group. */
void addGroup(Group g) throws DbException;
/** Stores a local pseudonym. */
void addLocalAuthor(LocalAuthor a) throws DbException;
/** Stores a local message. */
void addLocalMessage(Message m, ClientId c, Metadata meta, boolean shared)
throws DbException; throws DbException;
/** Stores a transport. */ /**
void addTransport(TransportId t, int maxLatency) throws DbException; * Stores a group.
*/
void addGroup(Transaction txn, Group g) throws DbException;
/**
* Stores a local pseudonym.
*/
void addLocalAuthor(Transaction txn, LocalAuthor a) throws DbException;
/**
* Stores a local message.
*/
void addLocalMessage(Transaction txn, Message m, ClientId c, Metadata meta,
boolean shared) throws DbException;
/**
* Stores a transport.
*/
void addTransport(Transaction txn, TransportId t, int maxLatency)
throws DbException;
/** /**
* Stores transport keys for a newly added contact. * Stores transport keys for a newly added contact.
*/ */
void addTransportKeys(ContactId c, TransportKeys k) throws DbException; void addTransportKeys(Transaction txn, ContactId c, TransportKeys k)
throws DbException;
/** /**
* Deletes the message with the given ID. The message ID and any other * Deletes the message with the given ID. The message ID and any other
* associated data are not deleted. * associated data are not deleted.
*/ */
void deleteMessage(MessageId m) throws DbException; void deleteMessage(Transaction txn, MessageId m) throws DbException;
/** Deletes any metadata associated with the given message. */ /** Deletes any metadata associated with the given message. */
void deleteMessageMetadata(MessageId m) throws DbException; void deleteMessageMetadata(Transaction txn, MessageId m) throws DbException;
/** /**
* Returns an acknowledgement for the given contact, or null if there are * Returns an acknowledgement for the given contact, or null if there are
* no messages to acknowledge. * no messages to acknowledge.
*/ */
Ack generateAck(ContactId c, int maxMessages) throws DbException; Ack generateAck(Transaction txn, ContactId c, int maxMessages)
throws DbException;
/** /**
* Returns a batch of raw messages for the given contact, with a total * Returns a batch of raw messages for the given contact, with a total
@@ -99,22 +112,23 @@ public interface DatabaseComponent {
* transport with the given maximum latency. Returns null if there are no * transport with the given maximum latency. Returns null if there are no
* sendable messages that fit in the given length. * sendable messages that fit in the given length.
*/ */
Collection<byte[]> generateBatch(ContactId c, int maxLength, Collection<byte[]> generateBatch(Transaction txn, ContactId c,
int maxLatency) throws DbException; int maxLength, int maxLatency) throws DbException;
/** /**
* Returns an offer for the given contact for transmission over a * Returns an offer for the given contact for transmission over a
* transport with the given maximum latency, or null if there are no * transport with the given maximum latency, or null if there are no
* messages to offer. * messages to offer.
*/ */
Offer generateOffer(ContactId c, int maxMessages, int maxLatency) Offer generateOffer(Transaction txn, ContactId c, int maxMessages,
throws DbException; int maxLatency) throws DbException;
/** /**
* Returns a request for the given contact, or null if there are no * Returns a request for the given contact, or null if there are no
* messages to request. * messages to request.
*/ */
Request generateRequest(ContactId c, int maxMessages) throws DbException; Request generateRequest(Transaction txn, ContactId c, int maxMessages)
throws DbException;
/** /**
* Returns a batch of raw messages for the given contact, with a total * Returns a batch of raw messages for the given contact, with a total
@@ -123,158 +137,226 @@ public interface DatabaseComponent {
* requested by the contact are returned. Returns null if there are no * requested by the contact are returned. Returns null if there are no
* sendable messages that fit in the given length. * sendable messages that fit in the given length.
*/ */
Collection<byte[]> generateRequestedBatch(ContactId c, int maxLength, Collection<byte[]> generateRequestedBatch(Transaction txn, ContactId c,
int maxLatency) throws DbException; int maxLength, int maxLatency) throws DbException;
/** Returns the contact with the given ID. */ /**
Contact getContact(ContactId c) throws DbException; * Returns the contact with the given ID.
*/
Contact getContact(Transaction txn, ContactId c) throws DbException;
/** Returns all contacts. */ /**
Collection<Contact> getContacts() throws DbException; * Returns all contacts.
*/
Collection<Contact> getContacts(Transaction txn) throws DbException;
/** Returns all contacts associated with the given local pseudonym. */ /**
Collection<ContactId> getContacts(AuthorId a) throws DbException; * Returns all contacts associated with the given local pseudonym.
*/
Collection<ContactId> getContacts(Transaction txn, AuthorId a)
throws DbException;
/** Returns the unique ID for this device. */ /**
DeviceId getDeviceId() throws DbException; * Returns the unique ID for this device.
*/
DeviceId getDeviceId(Transaction txn) throws DbException;
/** Returns the group with the given ID. */ /**
Group getGroup(GroupId g) throws DbException; * Returns the group with the given ID.
*/
Group getGroup(Transaction txn, GroupId g) throws DbException;
/** Returns the metadata for the given group. */ /**
Metadata getGroupMetadata(GroupId g) throws DbException; * Returns the metadata for the given group.
*/
Metadata getGroupMetadata(Transaction txn, GroupId g) throws DbException;
/** Returns all groups belonging to the given client. */ /**
Collection<Group> getGroups(ClientId c) throws DbException; * Returns all groups belonging to the given client.
*/
Collection<Group> getGroups(Transaction txn, ClientId c) throws DbException;
/** Returns the local pseudonym with the given ID. */ /**
LocalAuthor getLocalAuthor(AuthorId a) throws DbException; * Returns the local pseudonym with the given ID.
*/
LocalAuthor getLocalAuthor(Transaction txn, AuthorId a) throws DbException;
/** Returns all local pseudonyms. */ /**
Collection<LocalAuthor> getLocalAuthors() throws DbException; * Returns all local pseudonyms.
*/
Collection<LocalAuthor> getLocalAuthors(Transaction txn) throws DbException;
/** /**
* Returns the IDs of any messages that need to be validated by the given * Returns the IDs of any messages that need to be validated by the given
* client. * client.
*/ */
Collection<MessageId> getMessagesToValidate(ClientId c) throws DbException; Collection<MessageId> getMessagesToValidate(Transaction txn, ClientId c)
/** Returns the message with the given ID, in serialised form. */
byte[] getRawMessage(MessageId m) throws DbException;
/** Returns the metadata for all messages in the given group. */
Map<MessageId, Metadata> getMessageMetadata(GroupId g)
throws DbException; throws DbException;
/** Returns the metadata for the given message. */ /**
Metadata getMessageMetadata(MessageId m) throws DbException; * Returns the message with the given ID, in serialised form.
*/
byte[] getRawMessage(Transaction txn, MessageId m) throws DbException;
/**
* Returns the metadata for all messages in the given group.
*/
Map<MessageId, Metadata> getMessageMetadata(Transaction txn, GroupId g)
throws DbException;
/**
* Returns the metadata for the given message.
*/
Metadata getMessageMetadata(Transaction txn, MessageId m)
throws DbException;
/** /**
* Returns the status of all messages in the given group with respect to * Returns the status of all messages in the given group with respect to
* the given contact. * the given contact.
*/ */
Collection<MessageStatus> getMessageStatus(ContactId c, GroupId g) Collection<MessageStatus> getMessageStatus(Transaction txn, ContactId c,
throws DbException; GroupId g) throws DbException;
/** /**
* Returns the status of the given message with respect to the given * Returns the status of the given message with respect to the given
* contact. * contact.
*/ */
MessageStatus getMessageStatus(ContactId c, MessageId m) MessageStatus getMessageStatus(Transaction txn, ContactId c, MessageId m)
throws DbException; throws DbException;
/** Returns all settings in the given namespace. */ /**
Settings getSettings(String namespace) throws DbException; * Returns all settings in the given namespace.
*/
Settings getSettings(Transaction txn, String namespace) throws DbException;
/** Returns all transport keys for the given transport. */ /**
Map<ContactId, TransportKeys> getTransportKeys(TransportId t) * Returns all transport keys for the given transport.
*/
Map<ContactId, TransportKeys> getTransportKeys(Transaction txn,
TransportId t) throws DbException;
/**
* Returns the maximum latencies in milliseconds of all transports.
*/
Map<TransportId, Integer> getTransportLatencies(Transaction txn)
throws DbException; throws DbException;
/** Returns the maximum latencies in milliseconds of all transports. */
Map<TransportId, Integer> getTransportLatencies() throws DbException;
/** /**
* Increments the outgoing stream counter for the given contact and * Increments the outgoing stream counter for the given contact and
* transport in the given rotation period . * transport in the given rotation period .
*/ */
void incrementStreamCounter(ContactId c, TransportId t, long rotationPeriod) void incrementStreamCounter(Transaction txn, ContactId c, TransportId t,
throws DbException; long rotationPeriod) throws DbException;
/** Returns true if the given group is visible to the given contact. */ /**
boolean isVisibleToContact(ContactId c, GroupId g) throws DbException; * Returns true if the given group is visible to the given contact.
*/
boolean isVisibleToContact(Transaction txn, ContactId c, GroupId g)
throws DbException;
/** /**
* Merges the given metadata with the existing metadata for the given * Merges the given metadata with the existing metadata for the given
* group. * group.
*/ */
void mergeGroupMetadata(GroupId g, Metadata meta) throws DbException; void mergeGroupMetadata(Transaction txn, GroupId g, Metadata meta)
throws DbException;
/** /**
* Merges the given metadata with the existing metadata for the given * Merges the given metadata with the existing metadata for the given
* message. * message.
*/ */
void mergeMessageMetadata(MessageId m, Metadata meta) throws DbException; void mergeMessageMetadata(Transaction txn, MessageId m, Metadata meta)
throws DbException;
/** /**
* Merges the given settings with the existing settings in the given * Merges the given settings with the existing settings in the given
* namespace. * namespace.
*/ */
void mergeSettings(Settings s, String namespace) throws DbException; void mergeSettings(Transaction txn, Settings s, String namespace)
throws DbException;
/** Processes an ack from the given contact. */ /**
void receiveAck(ContactId c, Ack a) throws DbException; * Processes an ack from the given contact.
*/
void receiveAck(Transaction txn, ContactId c, Ack a) throws DbException;
/** Processes a message from the given contact. */ /**
void receiveMessage(ContactId c, Message m) throws DbException; * Processes a message from the given contact.
*/
void receiveMessage(Transaction txn, ContactId c, Message m)
throws DbException;
/** Processes an offer from the given contact. */ /**
void receiveOffer(ContactId c, Offer o) throws DbException; * Processes an offer from the given contact.
*/
void receiveOffer(Transaction txn, ContactId c, Offer o) throws DbException;
/** Processes a request from the given contact. */ /**
void receiveRequest(ContactId c, Request r) throws DbException; * Processes a request from the given contact.
*/
void receiveRequest(Transaction txn, ContactId c, Request r)
throws DbException;
/** Removes a contact (and all associated state) from the database. */ /**
void removeContact(ContactId c) throws DbException; * Removes a contact (and all associated state) from the database.
*/
void removeContact(Transaction txn, ContactId c) throws DbException;
/** Removes a group (and all associated state) from the database. */ /**
void removeGroup(Group g) throws DbException; * Removes a group (and all associated state) from the database.
*/
void removeGroup(Transaction txn, Group g) throws DbException;
/** /**
* Removes a local pseudonym (and all associated state) from the database. * Removes a local pseudonym (and all associated state) from the database.
*/ */
void removeLocalAuthor(AuthorId a) throws DbException; void removeLocalAuthor(Transaction txn, AuthorId a) throws DbException;
/** Removes a transport (and all associated state) from the database. */ /**
void removeTransport(TransportId t) throws DbException; * Removes a transport (and all associated state) from the database.
*/
void removeTransport(Transaction txn, TransportId t) throws DbException;
/** Sets the status of the given contact. */ /**
void setContactStatus(ContactId c, StorageStatus s) throws DbException; * Sets the status of the given contact.
*/
void setContactStatus(Transaction txn, ContactId c, StorageStatus s)
throws DbException;
/** Sets the status of the given local pseudonym. */ /**
void setLocalAuthorStatus(AuthorId a, StorageStatus s) * Sets the status of the given local pseudonym.
throws DbException; */
void setLocalAuthorStatus(Transaction txn, AuthorId a, StorageStatus s)
throws DbException;
/** Marks the given message as shared or unshared. */ /**
void setMessageShared(Message m, boolean shared) throws DbException; * Marks the given message as shared or unshared.
*/
void setMessageShared(Transaction txn, Message m, boolean shared)
throws DbException;
/** Marks the given message as valid or invalid. */ /**
void setMessageValid(Message m, ClientId c, boolean valid) * Marks the given message as valid or invalid.
*/
void setMessageValid(Transaction txn, Message m, ClientId c, boolean valid)
throws DbException; throws DbException;
/** /**
* Sets the reordering window for the given contact and transport in the * Sets the reordering window for the given contact and transport in the
* given rotation period. * given rotation period.
*/ */
void setReorderingWindow(ContactId c, TransportId t, long rotationPeriod, void setReorderingWindow(Transaction txn, ContactId c, TransportId t,
long base, byte[] bitmap) throws DbException; long rotationPeriod, long base, byte[] bitmap) throws DbException;
/** Makes a group visible or invisible to a contact. */ /**
void setVisibleToContact(ContactId c, GroupId g, boolean visible) * Makes a group visible or invisible to a contact.
throws DbException; */
void setVisibleToContact(Transaction txn, ContactId c, GroupId g,
boolean visible) throws DbException;
/** /**
* Stores the given transport keys, deleting any keys they have replaced. * Stores the given transport keys, deleting any keys they have replaced.
*/ */
void updateTransportKeys(Map<ContactId, TransportKeys> keys) void updateTransportKeys(Transaction txn,
throws DbException; Map<ContactId, TransportKeys> keys) throws DbException;
} }

View File

@@ -1,15 +1,38 @@
package org.briarproject.api.db; package org.briarproject.api.db;
/** A wrapper around a database transaction. */ /**
* A wrapper around a database transaction. Transactions are not thread-safe.
*/
public class Transaction { public class Transaction {
private final Object txn; private final Object txn;
private boolean complete = false;
public Transaction(Object txn) { public Transaction(Object txn) {
this.txn = txn; this.txn = txn;
} }
/**
* Returns the database transaction. The type of the returned object
* depends on the database implementation.
*/
public Object unbox() { public Object unbox() {
return txn; return txn;
} }
/**
* Returns true if the transaction is ready to be committed.
*/
public boolean isComplete() {
return complete;
}
/**
* Marks the transaction as ready to be committed. This method must not be
* called more than once.
*/
public void setComplete() {
if (complete) throw new IllegalStateException();
complete = true;
}
} }

View File

@@ -1,6 +1,7 @@
package org.briarproject.api.identity; package org.briarproject.api.identity;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import java.util.Collection; import java.util.Collection;
@@ -25,10 +26,11 @@ public interface IdentityManager {
void removeLocalAuthor(AuthorId a) throws DbException; void removeLocalAuthor(AuthorId a) throws DbException;
interface AddIdentityHook { interface AddIdentityHook {
void addingIdentity(LocalAuthor a); void addingIdentity(Transaction txn, LocalAuthor a) throws DbException;
} }
interface RemoveIdentityHook { interface RemoveIdentityHook {
void removingIdentity(LocalAuthor a); void removingIdentity(Transaction txn, LocalAuthor a)
throws DbException;
} }
} }

View File

@@ -1,6 +1,8 @@
package org.briarproject.api.sync; package org.briarproject.api.sync;
import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
/** /**
* Responsible for managing message validators and passing them messages to * Responsible for managing message validators and passing them messages to
@@ -35,6 +37,7 @@ public interface ValidationManager {
void registerValidationHook(ValidationHook hook); void registerValidationHook(ValidationHook hook);
interface ValidationHook { interface ValidationHook {
void validatingMessage(Message m, ClientId c, Metadata meta); void validatingMessage(Transaction txn, Message m, ClientId c,
Metadata meta) throws DbException;
} }
} }

View File

@@ -8,6 +8,7 @@ import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.db.NoSuchContactException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.ContactAddedEvent; import org.briarproject.api.event.ContactAddedEvent;
import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
@@ -52,19 +53,31 @@ class ContactManagerImpl implements ContactManager, Service,
public boolean start() { public boolean start() {
// Finish adding/removing any partly added/removed contacts // Finish adding/removing any partly added/removed contacts
try { try {
for (Contact c : db.getContacts()) { List<ContactId> added = new ArrayList<ContactId>();
if (c.getStatus().equals(ADDING)) { List<ContactId> removed = new ArrayList<ContactId>();
for (AddContactHook hook : addHooks) Transaction txn = db.startTransaction();
hook.addingContact(c); try {
db.setContactStatus(c.getId(), ACTIVE); for (Contact c : db.getContacts(txn)) {
eventBus.broadcast(new ContactAddedEvent(c.getId())); if (c.getStatus().equals(ADDING)) {
} else if (c.getStatus().equals(REMOVING)) { for (AddContactHook hook : addHooks)
for (RemoveContactHook hook : removeHooks) hook.addingContact(txn, c);
hook.removingContact(c); db.setContactStatus(txn, c.getId(), ACTIVE);
db.removeContact(c.getId()); added.add(c.getId());
eventBus.broadcast(new ContactRemovedEvent(c.getId())); } else if (c.getStatus().equals(REMOVING)) {
for (RemoveContactHook hook : removeHooks)
hook.removingContact(txn, c);
db.removeContact(txn, c.getId());
removed.add(c.getId());
}
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
for (ContactId c : added)
eventBus.broadcast(new ContactAddedEvent(c));
for (ContactId c : removed)
eventBus.broadcast(new ContactRemovedEvent(c));
return true; return true;
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -90,24 +103,46 @@ class ContactManagerImpl implements ContactManager, Service,
@Override @Override
public ContactId addContact(Author remote, AuthorId local) public ContactId addContact(Author remote, AuthorId local)
throws DbException { throws DbException {
ContactId c = db.addContact(remote, local); ContactId c;
Contact contact = db.getContact(c); Transaction txn = db.startTransaction();
for (AddContactHook hook : addHooks) hook.addingContact(contact); try {
db.setContactStatus(c, ACTIVE); c = db.addContact(txn, remote, local);
Contact contact = db.getContact(txn, c);
for (AddContactHook hook : addHooks)
hook.addingContact(txn, contact);
db.setContactStatus(txn, c, ACTIVE);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
eventBus.broadcast(new ContactAddedEvent(c)); eventBus.broadcast(new ContactAddedEvent(c));
return c; return c;
} }
@Override @Override
public Contact getContact(ContactId c) throws DbException { public Contact getContact(ContactId c) throws DbException {
Contact contact = db.getContact(c); Contact contact;
Transaction txn = db.startTransaction();
try {
contact = db.getContact(txn, c);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (contact.getStatus().equals(ACTIVE)) return contact; if (contact.getStatus().equals(ACTIVE)) return contact;
throw new NoSuchContactException(); throw new NoSuchContactException();
} }
@Override @Override
public Collection<Contact> getContacts() throws DbException { public Collection<Contact> getContacts() throws DbException {
Collection<Contact> contacts = db.getContacts(); Collection<Contact> contacts;
Transaction txn = db.startTransaction();
try {
contacts = db.getContacts(txn);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
// Filter out any contacts that are being added or removed // Filter out any contacts that are being added or removed
List<Contact> active = new ArrayList<Contact>(contacts.size()); List<Contact> active = new ArrayList<Contact>(contacts.size());
for (Contact c : contacts) for (Contact c : contacts)
@@ -117,21 +152,30 @@ class ContactManagerImpl implements ContactManager, Service,
@Override @Override
public void removeContact(ContactId c) throws DbException { public void removeContact(ContactId c) throws DbException {
Contact contact = db.getContact(c); Transaction txn = db.startTransaction();
db.setContactStatus(c, REMOVING); try {
for (RemoveContactHook hook : removeHooks) removeContact(txn, c);
hook.removingContact(contact); txn.setComplete();
db.removeContact(c); } finally {
db.endTransaction(txn);
}
eventBus.broadcast(new ContactRemovedEvent(c)); eventBus.broadcast(new ContactRemovedEvent(c));
} }
private void removeContact(Transaction txn, ContactId c)
throws DbException {
Contact contact = db.getContact(txn, c);
db.setContactStatus(txn, c, REMOVING);
for (RemoveContactHook hook : removeHooks)
hook.removingContact(txn, contact);
db.removeContact(txn, c);
}
@Override @Override
public void removingIdentity(LocalAuthor a) { public void removingIdentity(Transaction txn, LocalAuthor a)
throws DbException {
// Remove any contacts of the local pseudonym that's being removed // Remove any contacts of the local pseudonym that's being removed
try { for (ContactId c : db.getContacts(txn, a.getId()))
for (ContactId c : db.getContacts(a.getId())) removeContact(c); removeContact(txn, c);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
} }

File diff suppressed because it is too large Load Diff

View File

@@ -4,7 +4,6 @@ import com.google.inject.Inject;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
import org.briarproject.api.data.BdfReader; import org.briarproject.api.data.BdfReader;
import org.briarproject.api.data.BdfReaderFactory; import org.briarproject.api.data.BdfReaderFactory;
@@ -13,13 +12,13 @@ import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumPost; import org.briarproject.api.forum.ForumPost;
import org.briarproject.api.forum.ForumPostHeader; import org.briarproject.api.forum.ForumPostHeader;
import org.briarproject.api.identity.Author; import org.briarproject.api.identity.Author;
import org.briarproject.api.identity.AuthorId; import org.briarproject.api.identity.AuthorId;
import org.briarproject.api.identity.IdentityManager;
import org.briarproject.api.identity.LocalAuthor; import org.briarproject.api.identity.LocalAuthor;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
import org.briarproject.api.sync.Group; import org.briarproject.api.sync.Group;
@@ -37,7 +36,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
@@ -59,22 +57,14 @@ class ForumManagerImpl implements ForumManager {
Logger.getLogger(ForumManagerImpl.class.getName()); Logger.getLogger(ForumManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final ContactManager contactManager;
private final IdentityManager identityManager;
private final BdfReaderFactory bdfReaderFactory; private final BdfReaderFactory bdfReaderFactory;
private final MetadataEncoder metadataEncoder; private final MetadataEncoder metadataEncoder;
private final MetadataParser metadataParser; private final MetadataParser metadataParser;
/** Ensures isolation between database reads and writes. */
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
@Inject @Inject
ForumManagerImpl(DatabaseComponent db, ContactManager contactManager, ForumManagerImpl(DatabaseComponent db, BdfReaderFactory bdfReaderFactory,
IdentityManager identityManager, BdfReaderFactory bdfReaderFactory,
MetadataEncoder metadataEncoder, MetadataParser metadataParser) { MetadataEncoder metadataEncoder, MetadataParser metadataParser) {
this.db = db; this.db = db;
this.contactManager = contactManager;
this.identityManager = identityManager;
this.bdfReaderFactory = bdfReaderFactory; this.bdfReaderFactory = bdfReaderFactory;
this.metadataEncoder = metadataEncoder; this.metadataEncoder = metadataEncoder;
this.metadataParser = metadataParser; this.metadataParser = metadataParser;
@@ -87,7 +77,6 @@ class ForumManagerImpl implements ForumManager {
@Override @Override
public void addLocalPost(ForumPost p) throws DbException { public void addLocalPost(ForumPost p) throws DbException {
lock.writeLock().lock();
try { try {
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("timestamp", p.getMessage().getTimestamp()); d.put("timestamp", p.getMessage().getTimestamp());
@@ -105,45 +94,65 @@ class ForumManagerImpl implements ForumManager {
d.put("local", true); d.put("local", true);
d.put("read", true); d.put("read", true);
Metadata meta = metadataEncoder.encode(d); Metadata meta = metadataEncoder.encode(d);
db.addLocalMessage(p.getMessage(), CLIENT_ID, meta, true); Transaction txn = db.startTransaction();
try {
db.addLocalMessage(txn, p.getMessage(), CLIENT_ID, meta, true);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@Override @Override
public Forum getForum(GroupId g) throws DbException { public Forum getForum(GroupId g) throws DbException {
lock.readLock().lock();
try { try {
return parseForum(db.getGroup(g)); Group group;
Transaction txn = db.startTransaction();
try {
group = db.getGroup(txn, g);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return parseForum(group);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public Collection<Forum> getForums() throws DbException { public Collection<Forum> getForums() throws DbException {
lock.readLock().lock();
try { try {
Collection<Group> groups;
Transaction txn = db.startTransaction();
try {
groups = db.getGroups(txn, CLIENT_ID);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
List<Forum> forums = new ArrayList<Forum>(); List<Forum> forums = new ArrayList<Forum>();
for (Group g : db.getGroups(CLIENT_ID)) forums.add(parseForum(g)); for (Group g : groups) forums.add(parseForum(g));
return Collections.unmodifiableList(forums); return Collections.unmodifiableList(forums);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public byte[] getPostBody(MessageId m) throws DbException { public byte[] getPostBody(MessageId m) throws DbException {
lock.readLock().lock();
try { try {
byte[] raw = db.getRawMessage(m); byte[] raw;
Transaction txn = db.startTransaction();
try {
raw = db.getRawMessage(txn, m);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
ByteArrayInputStream in = new ByteArrayInputStream(raw, ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH); MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in); BdfReader r = bdfReaderFactory.createReader(in);
@@ -164,74 +173,76 @@ class ForumManagerImpl implements ForumManager {
} catch (IOException e) { } catch (IOException e) {
// Shouldn't happen with ByteArrayInputStream // Shouldn't happen with ByteArrayInputStream
throw new RuntimeException(e); throw new RuntimeException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public Collection<ForumPostHeader> getPostHeaders(GroupId g) public Collection<ForumPostHeader> getPostHeaders(GroupId g)
throws DbException { throws DbException {
lock.readLock().lock(); Set<AuthorId> localAuthorIds = new HashSet<AuthorId>();
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>();
Map<MessageId, Metadata> metadata;
Transaction txn = db.startTransaction();
try { try {
// Load the IDs of the user's identities // Load the IDs of the user's identities
Set<AuthorId> localAuthorIds = new HashSet<AuthorId>(); for (LocalAuthor a : db.getLocalAuthors(txn))
for (LocalAuthor a : identityManager.getLocalAuthors())
localAuthorIds.add(a.getId()); localAuthorIds.add(a.getId());
// Load the IDs of contacts' identities // Load the IDs of contacts' identities
Set<AuthorId> contactAuthorIds = new HashSet<AuthorId>(); for (Contact c : db.getContacts(txn))
for (Contact c : contactManager.getContacts())
contactAuthorIds.add(c.getAuthor().getId()); contactAuthorIds.add(c.getAuthor().getId());
// Load and parse the metadata // Load the metadata
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); metadata = db.getMessageMetadata(txn, g);
Collection<ForumPostHeader> headers = txn.setComplete();
new ArrayList<ForumPostHeader>();
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
MessageId messageId = e.getKey();
Metadata meta = e.getValue();
try {
BdfDictionary d = metadataParser.parse(meta);
long timestamp = d.getInteger("timestamp");
Author author = null;
Author.Status authorStatus = ANONYMOUS;
BdfDictionary d1 = d.getDictionary("author", null);
if (d1 != null) {
AuthorId authorId = new AuthorId(d1.getRaw("id"));
String name = d1.getString("name");
byte[] publicKey = d1.getRaw("publicKey");
author = new Author(authorId, name, publicKey);
if (localAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else if (contactAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else authorStatus = UNKNOWN;
}
String contentType = d.getString("contentType");
boolean read = d.getBoolean("read");
headers.add(new ForumPostHeader(messageId, timestamp,
author, authorStatus, contentType, read));
} catch (FormatException ex) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, ex.toString(), ex);
}
}
return headers;
} finally { } finally {
lock.readLock().unlock(); db.endTransaction(txn);
} }
// Parse the metadata
Collection<ForumPostHeader> headers = new ArrayList<ForumPostHeader>();
for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
try {
BdfDictionary d = metadataParser.parse(e.getValue());
long timestamp = d.getInteger("timestamp");
Author author = null;
Author.Status authorStatus = ANONYMOUS;
BdfDictionary d1 = d.getDictionary("author", null);
if (d1 != null) {
AuthorId authorId = new AuthorId(d1.getRaw("id"));
String name = d1.getString("name");
byte[] publicKey = d1.getRaw("publicKey");
author = new Author(authorId, name, publicKey);
if (localAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else if (contactAuthorIds.contains(authorId))
authorStatus = VERIFIED;
else authorStatus = UNKNOWN;
}
String contentType = d.getString("contentType");
boolean read = d.getBoolean("read");
headers.add(new ForumPostHeader(e.getKey(), timestamp, author,
authorStatus, contentType, read));
} catch (FormatException ex) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, ex.toString(), ex);
}
}
return headers;
} }
@Override @Override
public void setReadFlag(MessageId m, boolean read) throws DbException { public void setReadFlag(MessageId m, boolean read) throws DbException {
lock.writeLock().lock();
try { try {
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("read", read); d.put("read", read);
db.mergeMessageMetadata(m, metadataEncoder.encode(d)); Metadata meta = metadataEncoder.encode(d);
Transaction txn = db.startTransaction();
try {
db.mergeMessageMetadata(txn, m, meta);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} finally {
lock.writeLock().unlock();
} }
} }

View File

@@ -5,7 +5,6 @@ import com.google.inject.Inject;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.AddContactHook;
import org.briarproject.api.contact.ContactManager.RemoveContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
@@ -18,6 +17,7 @@ import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.forum.Forum; import org.briarproject.api.forum.Forum;
import org.briarproject.api.forum.ForumManager; import org.briarproject.api.forum.ForumManager;
import org.briarproject.api.forum.ForumSharingManager; import org.briarproject.api.forum.ForumSharingManager;
@@ -45,10 +45,8 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.Set; import java.util.Set;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger; import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH; import static org.briarproject.api.forum.ForumConstants.FORUM_SALT_LENGTH;
import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH; import static org.briarproject.api.forum.ForumConstants.MAX_FORUM_NAME_LENGTH;
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
@@ -62,11 +60,7 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0]; private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
private static final Logger LOG =
Logger.getLogger(ForumSharingManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final ContactManager contactManager;
private final ForumManager forumManager; private final ForumManager forumManager;
private final GroupFactory groupFactory; private final GroupFactory groupFactory;
private final PrivateGroupFactory privateGroupFactory; private final PrivateGroupFactory privateGroupFactory;
@@ -79,18 +73,14 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
private final Clock clock; private final Clock clock;
private final Group localGroup; private final Group localGroup;
/** Ensures isolation between database reads and writes. */
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
@Inject @Inject
ForumSharingManagerImpl(DatabaseComponent db, ForumSharingManagerImpl(DatabaseComponent db,
ContactManager contactManager, ForumManager forumManager, ForumManager forumManager, GroupFactory groupFactory,
GroupFactory groupFactory, PrivateGroupFactory privateGroupFactory, PrivateGroupFactory privateGroupFactory,
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory, MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder, BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
MetadataParser metadataParser, SecureRandom random, Clock clock) { MetadataParser metadataParser, SecureRandom random, Clock clock) {
this.db = db; this.db = db;
this.contactManager = contactManager;
this.forumManager = forumManager; this.forumManager = forumManager;
this.groupFactory = groupFactory; this.groupFactory = groupFactory;
this.privateGroupFactory = privateGroupFactory; this.privateGroupFactory = privateGroupFactory;
@@ -106,57 +96,39 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
} }
@Override @Override
public void addingContact(Contact c) { public void addingContact(Transaction txn, Contact c) throws DbException {
lock.writeLock().lock();
try { try {
// Create a group to share with the contact // Create a group to share with the contact
Group g = getContactGroup(c); Group g = getContactGroup(c);
// Store the group and share it with the contact // Store the group and share it with the contact
db.addGroup(g); db.addGroup(txn, g);
db.setVisibleToContact(c.getId(), g.getId(), true); db.setVisibleToContact(txn, c.getId(), g.getId(), true);
// Attach the contact ID to the group // Attach the contact ID to the group
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("contactId", c.getId().getInt()); d.put("contactId", c.getId().getInt());
db.mergeGroupMetadata(g.getId(), metadataEncoder.encode(d)); db.mergeGroupMetadata(txn, g.getId(), metadataEncoder.encode(d));
// Share any forums that are shared with all contacts // Share any forums that are shared with all contacts
List<Forum> shared = getForumsSharedWithAllContacts(); List<Forum> shared = getForumsSharedWithAllContacts(txn);
storeMessage(g.getId(), shared, 0); storeMessage(txn, g.getId(), shared, 0);
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch (FormatException e) { } catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); throw new DbException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@Override @Override
public void removingContact(Contact c) { public void removingContact(Transaction txn, Contact c) throws DbException {
lock.writeLock().lock(); db.removeGroup(txn, getContactGroup(c));
try {
db.removeGroup(getContactGroup(c));
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
lock.writeLock().unlock();
}
} }
@Override @Override
public void validatingMessage(Message m, ClientId c, Metadata meta) { public void validatingMessage(Transaction txn, Message m, ClientId c,
Metadata meta) throws DbException {
if (c.equals(CLIENT_ID)) { if (c.equals(CLIENT_ID)) {
lock.writeLock().lock();
try { try {
ContactId contactId = getContactId(m.getGroupId()); ContactId contactId = getContactId(txn, m.getGroupId());
setForumVisibility(contactId, getVisibleForums(m)); setForumVisibility(txn, contactId, getVisibleForums(txn, m));
} catch (DbException e) {
if (LOG.isLoggable(WARNING))
LOG.log(WARNING, e.toString(), e);
} catch (FormatException e) { } catch (FormatException e) {
if (LOG.isLoggable(WARNING)) throw new DbException(e);
LOG.log(WARNING, e.toString(), e);
} finally {
lock.writeLock().unlock();
} }
} }
} }
@@ -179,149 +151,162 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
@Override @Override
public void addForum(Forum f) throws DbException { public void addForum(Forum f) throws DbException {
lock.writeLock().lock(); Transaction txn = db.startTransaction();
try { try {
db.addGroup(f.getGroup()); db.addGroup(txn, f.getGroup());
txn.setComplete();
} finally { } finally {
lock.writeLock().unlock(); db.endTransaction(txn);
} }
} }
@Override @Override
public void removeForum(Forum f) throws DbException { public void removeForum(Forum f) throws DbException {
lock.writeLock().lock();
try { try {
// Update the list of forums shared with each contact // Update the list shared with each contact
for (Contact c : contactManager.getContacts()) { Transaction txn = db.startTransaction();
Group contactGroup = getContactGroup(c); try {
removeFromList(contactGroup.getId(), f); for (Contact c : db.getContacts(txn))
removeFromList(txn, getContactGroup(c).getId(), f);
db.removeGroup(txn, f.getGroup());
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
db.removeGroup(f.getGroup());
} catch (IOException e) { } catch (IOException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@Override @Override
public Collection<Forum> getAvailableForums() throws DbException { public Collection<Forum> getAvailableForums() throws DbException {
lock.readLock().lock();
try { try {
// Get any forums we subscribe to
Set<Group> subscribed = new HashSet<Group>(db.getGroups(
forumManager.getClientId()));
// Get all forums shared by contacts
Set<Forum> available = new HashSet<Forum>(); Set<Forum> available = new HashSet<Forum>();
for (Contact c : contactManager.getContacts()) { Transaction txn = db.startTransaction();
Group g = getContactGroup(c); try {
// Find the latest update version // Get any forums we subscribe to
LatestUpdate latest = findLatest(g.getId(), false); Set<Group> subscribed = new HashSet<Group>(db.getGroups(txn,
if (latest != null) { forumManager.getClientId()));
// Retrieve and parse the latest update // Get all forums shared by contacts
byte[] raw = db.getRawMessage(latest.messageId); for (Contact c : db.getContacts(txn)) {
for (Forum f : parseForumList(raw)) { Group g = getContactGroup(c);
if (!subscribed.contains(f.getGroup())) // Find the latest update version
available.add(f); LatestUpdate latest = findLatest(txn, g.getId(), false);
if (latest != null) {
// Retrieve and parse the latest update
byte[] raw = db.getRawMessage(txn, latest.messageId);
for (Forum f : parseForumList(raw)) {
if (!subscribed.contains(f.getGroup()))
available.add(f);
}
} }
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
return Collections.unmodifiableSet(available); return Collections.unmodifiableSet(available);
} catch (IOException e) { } catch (IOException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public Collection<Contact> getSharedBy(GroupId g) throws DbException { public Collection<Contact> getSharedBy(GroupId g) throws DbException {
lock.readLock().lock();
try { try {
List<Contact> subscribers = new ArrayList<Contact>(); List<Contact> subscribers = new ArrayList<Contact>();
for (Contact c : contactManager.getContacts()) { Transaction txn = db.startTransaction();
Group contactGroup = getContactGroup(c); try {
if (listContains(contactGroup.getId(), g, false)) for (Contact c : db.getContacts(txn)) {
subscribers.add(c); if (listContains(txn, getContactGroup(c).getId(), g, false))
subscribers.add(c);
}
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
return Collections.unmodifiableList(subscribers); return Collections.unmodifiableList(subscribers);
} catch (IOException e) { } catch (IOException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public Collection<ContactId> getSharedWith(GroupId g) throws DbException { public Collection<ContactId> getSharedWith(GroupId g) throws DbException {
lock.readLock().lock();
try { try {
List<ContactId> shared = new ArrayList<ContactId>(); List<ContactId> shared = new ArrayList<ContactId>();
for (Contact c : contactManager.getContacts()) { Transaction txn = db.startTransaction();
Group contactGroup = getContactGroup(c); try {
if (listContains(contactGroup.getId(), g, true)) for (Contact c : db.getContacts(txn)) {
shared.add(c.getId()); if (listContains(txn, getContactGroup(c).getId(), g, true))
shared.add(c.getId());
}
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
return Collections.unmodifiableList(shared); return Collections.unmodifiableList(shared);
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public void setSharedWith(GroupId g, Collection<ContactId> shared) public void setSharedWith(GroupId g, Collection<ContactId> shared)
throws DbException { throws DbException {
lock.writeLock().lock();
try { try {
// Retrieve the forum Transaction txn = db.startTransaction();
Forum f = parseForum(db.getGroup(g)); try {
// Remove the forum from the list of forums shared with all contacts // Retrieve the forum
removeFromList(localGroup.getId(), f); Forum f = parseForum(db.getGroup(txn, g));
// Update the list of forums shared with each contact // Remove the forum from the list shared with all contacts
shared = new HashSet<ContactId>(shared); removeFromList(txn, localGroup.getId(), f);
for (Contact c : contactManager.getContacts()) { // Update the list shared with each contact
Group contactGroup = getContactGroup(c); shared = new HashSet<ContactId>(shared);
if (shared.contains(c.getId())) { for (Contact c : db.getContacts(txn)) {
if (addToList(contactGroup.getId(), f)) { Group cg = getContactGroup(c);
// If the contact is sharing the forum, make it visible if (shared.contains(c.getId())) {
if (listContains(contactGroup.getId(), g, false)) if (addToList(txn, cg.getId(), f)) {
db.setVisibleToContact(c.getId(), g, true); if (listContains(txn, cg.getId(), g, false))
db.setVisibleToContact(txn, c.getId(), g, true);
}
} else {
removeFromList(txn, cg.getId(), f);
db.setVisibleToContact(txn, c.getId(), g, false);
} }
} else {
removeFromList(contactGroup.getId(), f);
db.setVisibleToContact(c.getId(), g, false);
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@Override @Override
public void setSharedWithAll(GroupId g) throws DbException { public void setSharedWithAll(GroupId g) throws DbException {
lock.writeLock().lock();
try { try {
// Retrieve the forum Transaction txn = db.startTransaction();
Forum f = parseForum(db.getGroup(g)); try {
// Add the forum to the list of forums shared with all contacts // Retrieve the forum
addToList(localGroup.getId(), f); Forum f = parseForum(db.getGroup(txn, g));
// Add the forum to the list of forums shared with each contact // Add the forum to the list shared with all contacts
for (Contact c : contactManager.getContacts()) { addToList(txn, localGroup.getId(), f);
Group contactGroup = getContactGroup(c); // Add the forum to the list shared with each contact
if (addToList(contactGroup.getId(), f)) { for (Contact c : db.getContacts(txn)) {
// If the contact is sharing the forum, make it visible Group cg = getContactGroup(c);
if (listContains(contactGroup.getId(), g, false)) if (addToList(txn, cg.getId(), f)) {
db.setVisibleToContact(getContactId(g), g, true); if (listContains(txn, cg.getId(), g, false))
db.setVisibleToContact(txn, c.getId(), g, true);
}
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
} catch (FormatException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@@ -329,23 +314,21 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
return privateGroupFactory.createPrivateGroup(CLIENT_ID, c); return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
} }
// Locking: lock.writeLock private List<Forum> getForumsSharedWithAllContacts(Transaction txn)
private List<Forum> getForumsSharedWithAllContacts() throws DbException, throws DbException, FormatException {
FormatException {
// Ensure the local group exists // Ensure the local group exists
db.addGroup(localGroup); db.addGroup(txn, localGroup);
// Find the latest update in the local group // Find the latest update in the local group
LatestUpdate latest = findLatest(localGroup.getId(), true); LatestUpdate latest = findLatest(txn, localGroup.getId(), true);
if (latest == null) return Collections.emptyList(); if (latest == null) return Collections.emptyList();
// Retrieve and parse the latest update // Retrieve and parse the latest update
return parseForumList(db.getRawMessage(latest.messageId)); return parseForumList(db.getRawMessage(txn, latest.messageId));
} }
// Locking: lock.readLock private LatestUpdate findLatest(Transaction txn, GroupId g, boolean local)
private LatestUpdate findLatest(GroupId g, boolean local)
throws DbException, FormatException { throws DbException, FormatException {
LatestUpdate latest = null; LatestUpdate latest = null;
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
for (Entry<MessageId, Metadata> e : metadata.entrySet()) { for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
BdfDictionary d = metadataParser.parse(e.getValue()); BdfDictionary d = metadataParser.parse(e.getValue());
if (d.getBoolean("local") != local) continue; if (d.getBoolean("local") != local) continue;
@@ -384,16 +367,20 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
} }
} }
// Locking: lock.writeLock private void storeMessage(Transaction txn, GroupId g, List<Forum> forums,
private void storeMessage(GroupId g, List<Forum> forums, long version) long version) throws DbException {
throws DbException, FormatException { try {
byte[] body = encodeForumList(forums, version); byte[] body = encodeForumList(forums, version);
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
Message m = messageFactory.createMessage(g, now, body); Message m = messageFactory.createMessage(g, now, body);
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("version", version); d.put("version", version);
d.put("local", true); d.put("local", true);
db.addLocalMessage(m, CLIENT_ID, metadataEncoder.encode(d), true); db.addLocalMessage(txn, m, CLIENT_ID, metadataEncoder.encode(d),
true);
} catch (FormatException e) {
throw new RuntimeException(e);
}
} }
private byte[] encodeForumList(List<Forum> forums, long version) { private byte[] encodeForumList(List<Forum> forums, long version) {
@@ -418,23 +405,21 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
return out.toByteArray(); return out.toByteArray();
} }
// Locking: lock.readLock private ContactId getContactId(Transaction txn, GroupId contactGroupId)
private ContactId getContactId(GroupId contactGroupId) throws DbException, throws DbException, FormatException {
FormatException { Metadata meta = db.getGroupMetadata(txn, contactGroupId);
Metadata meta = db.getGroupMetadata(contactGroupId);
BdfDictionary d = metadataParser.parse(meta); BdfDictionary d = metadataParser.parse(meta);
return new ContactId(d.getInteger("contactId").intValue()); return new ContactId(d.getInteger("contactId").intValue());
} }
// Locking: lock.readLock private Set<GroupId> getVisibleForums(Transaction txn,
private Set<GroupId> getVisibleForums(Message remoteUpdate) Message remoteUpdate) throws DbException, FormatException {
throws DbException, FormatException {
// Get the latest local update // Get the latest local update
LatestUpdate local = findLatest(remoteUpdate.getGroupId(), true); LatestUpdate local = findLatest(txn, remoteUpdate.getGroupId(), true);
// If there's no local update, no forums are visible // If there's no local update, no forums are visible
if (local == null) return Collections.emptySet(); if (local == null) return Collections.emptySet();
// Intersect the sets of shared forums // Intersect the sets of shared forums
byte[] localRaw = db.getRawMessage(local.messageId); byte[] localRaw = db.getRawMessage(txn, local.messageId);
Set<Forum> shared = new HashSet<Forum>(parseForumList(localRaw)); Set<Forum> shared = new HashSet<Forum>(parseForumList(localRaw));
shared.retainAll(parseForumList(remoteUpdate.getRaw())); shared.retainAll(parseForumList(remoteUpdate.getRaw()));
// Forums in the intersection should be visible // Forums in the intersection should be visible
@@ -443,16 +428,15 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
return visible; return visible;
} }
// Locking: lock.writeLock private void setForumVisibility(Transaction txn, ContactId c,
private void setForumVisibility(ContactId c, Set<GroupId> visible) Set<GroupId> visible) throws DbException {
throws DbException { for (Group g : db.getGroups(txn, forumManager.getClientId())) {
for (Group g : db.getGroups(forumManager.getClientId())) { boolean isVisible = db.isVisibleToContact(txn, c, g.getId());
boolean isVisible = db.isVisibleToContact(c, g.getId());
boolean shouldBeVisible = visible.contains(g.getId()); boolean shouldBeVisible = visible.contains(g.getId());
if (isVisible && !shouldBeVisible) if (isVisible && !shouldBeVisible)
db.setVisibleToContact(c, g.getId(), false); db.setVisibleToContact(txn, c, g.getId(), false);
else if (!isVisible && shouldBeVisible) else if (!isVisible && shouldBeVisible)
db.setVisibleToContact(c, g.getId(), true); db.setVisibleToContact(txn, c, g.getId(), true);
} }
} }
@@ -491,38 +475,38 @@ class ForumSharingManagerImpl implements ForumSharingManager, AddContactHook,
} }
} }
// Locking: lock.readLock private boolean listContains(Transaction txn, GroupId g, GroupId forum,
private boolean listContains(GroupId g, GroupId forum, boolean local) boolean local) throws DbException, FormatException {
throws DbException, FormatException { LatestUpdate latest = findLatest(txn, g, local);
LatestUpdate latest = findLatest(g, local);
if (latest == null) return false; if (latest == null) return false;
List<Forum> list = parseForumList(db.getRawMessage(latest.messageId)); byte[] raw = db.getRawMessage(txn, latest.messageId);
List<Forum> list = parseForumList(raw);
for (Forum f : list) if (f.getId().equals(forum)) return true; for (Forum f : list) if (f.getId().equals(forum)) return true;
return false; return false;
} }
// Locking: lock.writeLock private boolean addToList(Transaction txn, GroupId g, Forum f)
private boolean addToList(GroupId g, Forum f) throws DbException, throws DbException, FormatException {
FormatException { LatestUpdate latest = findLatest(txn, g, true);
LatestUpdate latest = findLatest(g, true);
if (latest == null) { if (latest == null) {
storeMessage(g, Collections.singletonList(f), 0); storeMessage(txn, g, Collections.singletonList(f), 0);
return true; return true;
} }
List<Forum> list = parseForumList(db.getRawMessage(latest.messageId)); byte[] raw = db.getRawMessage(txn, latest.messageId);
List<Forum> list = parseForumList(raw);
if (list.contains(f)) return false; if (list.contains(f)) return false;
list.add(f); list.add(f);
storeMessage(g, list, latest.version + 1); storeMessage(txn, g, list, latest.version + 1);
return true; return true;
} }
// Locking: lock.writeLock private void removeFromList(Transaction txn, GroupId g, Forum f)
private void removeFromList(GroupId g, Forum f) throws DbException, throws DbException, FormatException {
FormatException { LatestUpdate latest = findLatest(txn, g, true);
LatestUpdate latest = findLatest(g, true);
if (latest == null) return; if (latest == null) return;
List<Forum> list = parseForumList(db.getRawMessage(latest.messageId)); byte[] raw = db.getRawMessage(txn, latest.messageId);
if (list.remove(f)) storeMessage(g, list, latest.version + 1); List<Forum> list = parseForumList(raw);
if (list.remove(f)) storeMessage(txn, g, list, latest.version + 1);
} }
private static class LatestUpdate { private static class LatestUpdate {

View File

@@ -5,6 +5,7 @@ import com.google.inject.Inject;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.NoSuchLocalAuthorException; import org.briarproject.api.db.NoSuchLocalAuthorException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.LocalAuthorAddedEvent; import org.briarproject.api.event.LocalAuthorAddedEvent;
import org.briarproject.api.event.LocalAuthorRemovedEvent; import org.briarproject.api.event.LocalAuthorRemovedEvent;
@@ -47,19 +48,31 @@ class IdentityManagerImpl implements IdentityManager, Service {
public boolean start() { public boolean start() {
// Finish adding/removing any partly added/removed pseudonyms // Finish adding/removing any partly added/removed pseudonyms
try { try {
for (LocalAuthor a : db.getLocalAuthors()) { List<AuthorId> added = new ArrayList<AuthorId>();
if (a.getStatus().equals(ADDING)) { List<AuthorId> removed = new ArrayList<AuthorId>();
for (AddIdentityHook hook : addHooks) Transaction txn = db.startTransaction();
hook.addingIdentity(a); try {
db.setLocalAuthorStatus(a.getId(), ACTIVE); for (LocalAuthor a : db.getLocalAuthors(txn)) {
eventBus.broadcast(new LocalAuthorAddedEvent(a.getId())); if (a.getStatus().equals(ADDING)) {
} else if (a.getStatus().equals(REMOVING)) { for (AddIdentityHook hook : addHooks)
for (RemoveIdentityHook hook : removeHooks) hook.addingIdentity(txn, a);
hook.removingIdentity(a); db.setLocalAuthorStatus(txn, a.getId(), ACTIVE);
db.removeLocalAuthor(a.getId()); added.add(a.getId());
eventBus.broadcast(new LocalAuthorRemovedEvent(a.getId())); } else if (a.getStatus().equals(REMOVING)) {
for (RemoveIdentityHook hook : removeHooks)
hook.removingIdentity(txn, a);
db.removeLocalAuthor(txn, a.getId());
removed.add(a.getId());
}
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
for (AuthorId a : added)
eventBus.broadcast(new LocalAuthorAddedEvent(a));
for (AuthorId a : removed)
eventBus.broadcast(new LocalAuthorRemovedEvent(a));
return true; return true;
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -84,22 +97,43 @@ class IdentityManagerImpl implements IdentityManager, Service {
@Override @Override
public void addLocalAuthor(LocalAuthor localAuthor) throws DbException { public void addLocalAuthor(LocalAuthor localAuthor) throws DbException {
db.addLocalAuthor(localAuthor); Transaction txn = db.startTransaction();
for (AddIdentityHook hook : addHooks) hook.addingIdentity(localAuthor); try {
db.setLocalAuthorStatus(localAuthor.getId(), ACTIVE); db.addLocalAuthor(txn, localAuthor);
for (AddIdentityHook hook : addHooks)
hook.addingIdentity(txn, localAuthor);
db.setLocalAuthorStatus(txn, localAuthor.getId(), ACTIVE);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
eventBus.broadcast(new LocalAuthorAddedEvent(localAuthor.getId())); eventBus.broadcast(new LocalAuthorAddedEvent(localAuthor.getId()));
} }
@Override @Override
public LocalAuthor getLocalAuthor(AuthorId a) throws DbException { public LocalAuthor getLocalAuthor(AuthorId a) throws DbException {
LocalAuthor author = db.getLocalAuthor(a); LocalAuthor author;
Transaction txn = db.startTransaction();
try {
author = db.getLocalAuthor(txn, a);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (author.getStatus().equals(ACTIVE)) return author; if (author.getStatus().equals(ACTIVE)) return author;
throw new NoSuchLocalAuthorException(); throw new NoSuchLocalAuthorException();
} }
@Override @Override
public Collection<LocalAuthor> getLocalAuthors() throws DbException { public Collection<LocalAuthor> getLocalAuthors() throws DbException {
Collection<LocalAuthor> authors = db.getLocalAuthors(); Collection<LocalAuthor> authors;
Transaction txn = db.startTransaction();
try {
authors = db.getLocalAuthors(txn);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
// Filter out any pseudonyms that are being added or removed // Filter out any pseudonyms that are being added or removed
List<LocalAuthor> active = new ArrayList<LocalAuthor>(authors.size()); List<LocalAuthor> active = new ArrayList<LocalAuthor>(authors.size());
for (LocalAuthor a : authors) for (LocalAuthor a : authors)
@@ -109,11 +143,17 @@ class IdentityManagerImpl implements IdentityManager, Service {
@Override @Override
public void removeLocalAuthor(AuthorId a) throws DbException { public void removeLocalAuthor(AuthorId a) throws DbException {
LocalAuthor localAuthor = db.getLocalAuthor(a); Transaction txn = db.startTransaction();
db.setLocalAuthorStatus(a, REMOVING); try {
for (RemoveIdentityHook hook : removeHooks) LocalAuthor localAuthor = db.getLocalAuthor(txn, a);
hook.removingIdentity(localAuthor); db.setLocalAuthorStatus(txn, a, REMOVING);
db.removeLocalAuthor(a); for (RemoveIdentityHook hook : removeHooks)
hook.removingIdentity(txn, localAuthor);
db.removeLocalAuthor(txn, a);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
eventBus.broadcast(new LocalAuthorRemovedEvent(a)); eventBus.broadcast(new LocalAuthorRemovedEvent(a));
} }
} }

View File

@@ -5,7 +5,6 @@ import com.google.inject.Inject;
import org.briarproject.api.FormatException; import org.briarproject.api.FormatException;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.AddContactHook;
import org.briarproject.api.contact.ContactManager.RemoveContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
@@ -16,7 +15,7 @@ import org.briarproject.api.data.MetadataParser;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.NoSuchContactException; import org.briarproject.api.db.Transaction;
import org.briarproject.api.messaging.MessagingManager; import org.briarproject.api.messaging.MessagingManager;
import org.briarproject.api.messaging.PrivateMessage; import org.briarproject.api.messaging.PrivateMessage;
import org.briarproject.api.messaging.PrivateMessageHeader; import org.briarproject.api.messaging.PrivateMessageHeader;
@@ -50,19 +49,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
Logger.getLogger(MessagingManagerImpl.class.getName()); Logger.getLogger(MessagingManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final ContactManager contactManager;
private final PrivateGroupFactory privateGroupFactory; private final PrivateGroupFactory privateGroupFactory;
private final BdfReaderFactory bdfReaderFactory; private final BdfReaderFactory bdfReaderFactory;
private final MetadataEncoder metadataEncoder; private final MetadataEncoder metadataEncoder;
private final MetadataParser metadataParser; private final MetadataParser metadataParser;
@Inject @Inject
MessagingManagerImpl(DatabaseComponent db, ContactManager contactManager, MessagingManagerImpl(DatabaseComponent db,
PrivateGroupFactory privateGroupFactory, PrivateGroupFactory privateGroupFactory,
BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder, BdfReaderFactory bdfReaderFactory, MetadataEncoder metadataEncoder,
MetadataParser metadataParser) { MetadataParser metadataParser) {
this.db = db; this.db = db;
this.contactManager = contactManager;
this.privateGroupFactory = privateGroupFactory; this.privateGroupFactory = privateGroupFactory;
this.bdfReaderFactory = bdfReaderFactory; this.bdfReaderFactory = bdfReaderFactory;
this.metadataEncoder = metadataEncoder; this.metadataEncoder = metadataEncoder;
@@ -70,19 +67,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
} }
@Override @Override
public void addingContact(Contact c) { public void addingContact(Transaction txn, Contact c) throws DbException {
try { try {
// Create a group to share with the contact // Create a group to share with the contact
Group g = getContactGroup(c); Group g = getContactGroup(c);
// Store the group and share it with the contact // Store the group and share it with the contact
db.addGroup(g); db.addGroup(txn, g);
db.setVisibleToContact(c.getId(), g.getId(), true); db.setVisibleToContact(txn, c.getId(), g.getId(), true);
// Attach the contact ID to the group // Attach the contact ID to the group
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("contactId", c.getId().getInt()); d.put("contactId", c.getId().getInt());
db.mergeGroupMetadata(g.getId(), metadataEncoder.encode(d)); db.mergeGroupMetadata(txn, g.getId(), metadataEncoder.encode(d));
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -93,12 +88,8 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
} }
@Override @Override
public void removingContact(Contact c) { public void removingContact(Transaction txn, Contact c) throws DbException {
try { db.removeGroup(txn, getContactGroup(c));
db.removeGroup(getContactGroup(c));
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
}
} }
@Override @Override
@@ -108,15 +99,22 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override @Override
public void addLocalMessage(PrivateMessage m) throws DbException { public void addLocalMessage(PrivateMessage m) throws DbException {
BdfDictionary d = new BdfDictionary();
d.put("timestamp", m.getMessage().getTimestamp());
if (m.getParent() != null) d.put("parent", m.getParent().getBytes());
d.put("contentType", m.getContentType());
d.put("local", true);
d.put("read", true);
try { try {
BdfDictionary d = new BdfDictionary();
d.put("timestamp", m.getMessage().getTimestamp());
if (m.getParent() != null)
d.put("parent", m.getParent().getBytes());
d.put("contentType", m.getContentType());
d.put("local", true);
d.put("read", true);
Metadata meta = metadataEncoder.encode(d); Metadata meta = metadataEncoder.encode(d);
db.addLocalMessage(m.getMessage(), CLIENT_ID, meta, true); Transaction txn = db.startTransaction();
try {
db.addLocalMessage(txn, m.getMessage(), CLIENT_ID, meta, true);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
@@ -125,26 +123,48 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override @Override
public ContactId getContactId(GroupId g) throws DbException { public ContactId getContactId(GroupId g) throws DbException {
try { try {
BdfDictionary d = metadataParser.parse(db.getGroupMetadata(g)); Metadata meta;
long id = d.getInteger("contactId"); Transaction txn = db.startTransaction();
return new ContactId((int) id); try {
meta = db.getGroupMetadata(txn, g);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
BdfDictionary d = metadataParser.parse(meta);
return new ContactId(d.getInteger("contactId").intValue());
} catch (FormatException e) { } catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); throw new DbException(e);
throw new NoSuchContactException();
} }
} }
@Override @Override
public GroupId getConversationId(ContactId c) throws DbException { public GroupId getConversationId(ContactId c) throws DbException {
return getContactGroup(contactManager.getContact(c)).getId(); Contact contact;
Transaction txn = db.startTransaction();
try {
contact = db.getContact(txn, c);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return getContactGroup(contact).getId();
} }
@Override @Override
public Collection<PrivateMessageHeader> getMessageHeaders(ContactId c) public Collection<PrivateMessageHeader> getMessageHeaders(ContactId c)
throws DbException { throws DbException {
GroupId groupId = getConversationId(c); GroupId g = getConversationId(c);
Map<MessageId, Metadata> metadata = db.getMessageMetadata(groupId); Map<MessageId, Metadata> metadata;
Collection<MessageStatus> statuses = db.getMessageStatus(c, groupId); Collection<MessageStatus> statuses;
Transaction txn = db.startTransaction();
try {
metadata = db.getMessageMetadata(txn, g);
statuses = db.getMessageStatus(txn, c, g);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
Collection<PrivateMessageHeader> headers = Collection<PrivateMessageHeader> headers =
new ArrayList<PrivateMessageHeader>(); new ArrayList<PrivateMessageHeader>();
for (MessageStatus s : statuses) { for (MessageStatus s : statuses) {
@@ -168,7 +188,14 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override @Override
public byte[] getMessageBody(MessageId m) throws DbException { public byte[] getMessageBody(MessageId m) throws DbException {
byte[] raw = db.getRawMessage(m); byte[] raw;
Transaction txn = db.startTransaction();
try {
raw = db.getRawMessage(txn, m);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
ByteArrayInputStream in = new ByteArrayInputStream(raw, ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH); MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in); BdfReader r = bdfReaderFactory.createReader(in);
@@ -191,10 +218,17 @@ class MessagingManagerImpl implements MessagingManager, AddContactHook,
@Override @Override
public void setReadFlag(MessageId m, boolean read) throws DbException { public void setReadFlag(MessageId m, boolean read) throws DbException {
BdfDictionary d = new BdfDictionary();
d.put("read", read);
try { try {
db.mergeMessageMetadata(m, metadataEncoder.encode(d)); BdfDictionary d = new BdfDictionary();
d.put("read", read);
Metadata meta = metadataEncoder.encode(d);
Transaction txn = db.startTransaction();
try {
db.mergeMessageMetadata(txn, m, meta);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (FormatException e) { } catch (FormatException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }

View File

@@ -4,6 +4,7 @@ import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.TransportDisabledEvent; import org.briarproject.api.event.TransportDisabledEvent;
import org.briarproject.api.event.TransportEnabledEvent; import org.briarproject.api.event.TransportEnabledEvent;
@@ -27,6 +28,7 @@ import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
import org.briarproject.api.properties.TransportProperties; import org.briarproject.api.properties.TransportProperties;
import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.properties.TransportPropertyManager;
import org.briarproject.api.settings.Settings; import org.briarproject.api.settings.Settings;
import org.briarproject.api.settings.SettingsManager;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.api.ui.UiCallback; import org.briarproject.api.ui.UiCallback;
@@ -60,6 +62,7 @@ class PluginManagerImpl implements PluginManager, Service {
private final DatabaseComponent db; private final DatabaseComponent db;
private final Poller poller; private final Poller poller;
private final ConnectionManager connectionManager; private final ConnectionManager connectionManager;
private final SettingsManager settingsManager;
private final TransportPropertyManager transportPropertyManager; private final TransportPropertyManager transportPropertyManager;
private final UiCallback uiCallback; private final UiCallback uiCallback;
private final Map<TransportId, Plugin> plugins; private final Map<TransportId, Plugin> plugins;
@@ -72,6 +75,7 @@ class PluginManagerImpl implements PluginManager, Service {
DuplexPluginConfig duplexPluginConfig, Clock clock, DuplexPluginConfig duplexPluginConfig, Clock clock,
DatabaseComponent db, Poller poller, DatabaseComponent db, Poller poller,
ConnectionManager connectionManager, ConnectionManager connectionManager,
SettingsManager settingsManager,
TransportPropertyManager transportPropertyManager, TransportPropertyManager transportPropertyManager,
UiCallback uiCallback) { UiCallback uiCallback) {
this.ioExecutor = ioExecutor; this.ioExecutor = ioExecutor;
@@ -82,6 +86,7 @@ class PluginManagerImpl implements PluginManager, Service {
this.db = db; this.db = db;
this.poller = poller; this.poller = poller;
this.connectionManager = connectionManager; this.connectionManager = connectionManager;
this.settingsManager = settingsManager;
this.transportPropertyManager = transportPropertyManager; this.transportPropertyManager = transportPropertyManager;
this.uiCallback = uiCallback; this.uiCallback = uiCallback;
plugins = new ConcurrentHashMap<TransportId, Plugin>(); plugins = new ConcurrentHashMap<TransportId, Plugin>();
@@ -181,7 +186,13 @@ class PluginManagerImpl implements PluginManager, Service {
} }
try { try {
long start = clock.currentTimeMillis(); long start = clock.currentTimeMillis();
db.addTransport(id, plugin.getMaxLatency()); Transaction txn = db.startTransaction();
try {
db.addTransport(txn, id, plugin.getMaxLatency());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
long duration = clock.currentTimeMillis() - start; long duration = clock.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Adding transport took " + duration + " ms"); LOG.info("Adding transport took " + duration + " ms");
@@ -244,7 +255,13 @@ class PluginManagerImpl implements PluginManager, Service {
} }
try { try {
long start = clock.currentTimeMillis(); long start = clock.currentTimeMillis();
db.addTransport(id, plugin.getMaxLatency()); Transaction txn = db.startTransaction();
try {
db.addTransport(txn, id, plugin.getMaxLatency());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
long duration = clock.currentTimeMillis() - start; long duration = clock.currentTimeMillis() - start;
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Adding transport took " + duration + " ms"); LOG.info("Adding transport took " + duration + " ms");
@@ -319,7 +336,7 @@ class PluginManagerImpl implements PluginManager, Service {
public Settings getSettings() { public Settings getSettings() {
try { try {
return db.getSettings(id.getString()); return settingsManager.getSettings(id.getString());
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return new Settings(); return new Settings();
@@ -348,7 +365,7 @@ class PluginManagerImpl implements PluginManager, Service {
public void mergeSettings(Settings s) { public void mergeSettings(Settings s) {
try { try {
db.mergeSettings(s, id.getString()); settingsManager.mergeSettings(s, id.getString());
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} }

View File

@@ -7,7 +7,6 @@ import org.briarproject.api.FormatException;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.contact.Contact; import org.briarproject.api.contact.Contact;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.contact.ContactManager.AddContactHook; import org.briarproject.api.contact.ContactManager.AddContactHook;
import org.briarproject.api.contact.ContactManager.RemoveContactHook; import org.briarproject.api.contact.ContactManager.RemoveContactHook;
import org.briarproject.api.data.BdfDictionary; import org.briarproject.api.data.BdfDictionary;
@@ -21,6 +20,7 @@ import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.NoSuchGroupException; import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.properties.TransportProperties; import org.briarproject.api.properties.TransportProperties;
import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.properties.TransportPropertyManager;
import org.briarproject.api.sync.ClientId; import org.briarproject.api.sync.ClientId;
@@ -41,10 +41,7 @@ import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Map.Entry; import java.util.Map.Entry;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.logging.Logger;
import static java.util.logging.Level.WARNING;
import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH; import static org.briarproject.api.properties.TransportPropertyConstants.MAX_PROPERTY_LENGTH;
import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH; import static org.briarproject.api.sync.SyncConstants.MESSAGE_HEADER_LENGTH;
@@ -57,11 +54,7 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0]; private static final byte[] LOCAL_GROUP_DESCRIPTOR = new byte[0];
private static final Logger LOG =
Logger.getLogger(TransportPropertyManagerImpl.class.getName());
private final DatabaseComponent db; private final DatabaseComponent db;
private final ContactManager contactManager;
private final PrivateGroupFactory privateGroupFactory; private final PrivateGroupFactory privateGroupFactory;
private final MessageFactory messageFactory; private final MessageFactory messageFactory;
private final BdfReaderFactory bdfReaderFactory; private final BdfReaderFactory bdfReaderFactory;
@@ -71,18 +64,13 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
private final Clock clock; private final Clock clock;
private final Group localGroup; private final Group localGroup;
/** Ensures isolation between database reads and writes. */
private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
@Inject @Inject
TransportPropertyManagerImpl(DatabaseComponent db, TransportPropertyManagerImpl(DatabaseComponent db,
ContactManager contactManager, GroupFactory groupFactory, GroupFactory groupFactory, PrivateGroupFactory privateGroupFactory,
PrivateGroupFactory privateGroupFactory,
MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory, MessageFactory messageFactory, BdfReaderFactory bdfReaderFactory,
BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder, BdfWriterFactory bdfWriterFactory, MetadataEncoder metadataEncoder,
MetadataParser metadataParser, Clock clock) { MetadataParser metadataParser, Clock clock) {
this.db = db; this.db = db;
this.contactManager = contactManager;
this.privateGroupFactory = privateGroupFactory; this.privateGroupFactory = privateGroupFactory;
this.messageFactory = messageFactory; this.messageFactory = messageFactory;
this.bdfReaderFactory = bdfReaderFactory; this.bdfReaderFactory = bdfReaderFactory;
@@ -95,165 +83,171 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
@Override @Override
public void addingContact(Contact c) { public void addingContact(Transaction txn, Contact c) throws DbException {
lock.writeLock().lock(); // Create a group to share with the contact
try { Group g = getContactGroup(c);
// Create a group to share with the contact // Store the group and share it with the contact
Group g = getContactGroup(c); db.addGroup(txn, g);
// Store the group and share it with the contact db.setVisibleToContact(txn, c.getId(), g.getId(), true);
db.addGroup(g); // Copy the latest local properties into the group
db.setVisibleToContact(c.getId(), g.getId(), true); DeviceId dev = db.getDeviceId(txn);
// Copy the latest local properties into the group Map<TransportId, TransportProperties> local = getLocalProperties();
DeviceId dev = db.getDeviceId(); for (Entry<TransportId, TransportProperties> e : local.entrySet()) {
Map<TransportId, TransportProperties> local = getLocalProperties(); storeMessage(txn, g.getId(), dev, e.getKey(), e.getValue(), 1,
for (Entry<TransportId, TransportProperties> e : local.entrySet()) { true, true);
storeMessage(g.getId(), dev, e.getKey(), e.getValue(), 1, true,
true);
}
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} catch (FormatException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
lock.writeLock().unlock();
} }
} }
@Override @Override
public void removingContact(Contact c) { public void removingContact(Transaction txn, Contact c) throws DbException {
lock.writeLock().lock(); db.removeGroup(txn, getContactGroup(c));
try {
db.removeGroup(getContactGroup(c));
} catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally {
lock.writeLock().unlock();
}
} }
@Override @Override
public void addRemoteProperties(ContactId c, DeviceId dev, public void addRemoteProperties(ContactId c, DeviceId dev,
Map<TransportId, TransportProperties> props) throws DbException { Map<TransportId, TransportProperties> props) throws DbException {
lock.writeLock().lock(); Transaction txn = db.startTransaction();
try { try {
Group g = getContactGroup(contactManager.getContact(c)); Group g = getContactGroup(db.getContact(txn, c));
for (Entry<TransportId, TransportProperties> e : props.entrySet()) { for (Entry<TransportId, TransportProperties> e : props.entrySet()) {
storeMessage(g.getId(), dev, e.getKey(), e.getValue(), 0, false, storeMessage(txn, g.getId(), dev, e.getKey(), e.getValue(), 0,
false); false, false);
} }
} catch (FormatException e) { txn.setComplete();
throw new DbException(e);
} finally { } finally {
lock.writeLock().unlock(); db.endTransaction(txn);
} }
} }
@Override @Override
public Map<TransportId, TransportProperties> getLocalProperties() public Map<TransportId, TransportProperties> getLocalProperties()
throws DbException { throws DbException {
lock.readLock().lock();
try { try {
// Find the latest local update for each transport
Map<TransportId, LatestUpdate> latest =
findLatest(localGroup.getId(), true);
// Retrieve and parse the latest local properties
Map<TransportId, TransportProperties> local = Map<TransportId, TransportProperties> local =
new HashMap<TransportId, TransportProperties>(); new HashMap<TransportId, TransportProperties>();
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) { Transaction txn = db.startTransaction();
byte[] raw = db.getRawMessage(e.getValue().messageId); try {
local.put(e.getKey(), parseProperties(raw)); // Find the latest local update for each transport
Map<TransportId, LatestUpdate> latest = findLatest(txn,
localGroup.getId(), true);
// Retrieve and parse the latest local properties
for (Entry<TransportId, LatestUpdate> e : latest.entrySet()) {
byte[] raw = db.getRawMessage(txn, e.getValue().messageId);
local.put(e.getKey(), parseProperties(raw));
}
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
return Collections.unmodifiableMap(local); return Collections.unmodifiableMap(local);
} catch (NoSuchGroupException e) { } catch (NoSuchGroupException e) {
// Local group doesn't exist - there are no local properties // Local group doesn't exist - there are no local properties
return Collections.emptyMap(); return Collections.emptyMap();
} catch (IOException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public TransportProperties getLocalProperties(TransportId t) public TransportProperties getLocalProperties(TransportId t)
throws DbException { throws DbException {
lock.readLock().lock();
try { try {
// Find the latest local update TransportProperties p = null;
LatestUpdate latest = findLatest(localGroup.getId(), t, true); Transaction txn = db.startTransaction();
if (latest == null) return null; try {
// Retrieve and parse the latest local properties // Find the latest local update
return parseProperties(db.getRawMessage(latest.messageId)); LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
true);
if (latest != null) {
// Retrieve and parse the latest local properties
byte[] raw = db.getRawMessage(txn, latest.messageId);
p = parseProperties(raw);
}
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return p;
} catch (NoSuchGroupException e) { } catch (NoSuchGroupException e) {
// Local group doesn't exist - there are no local properties // Local group doesn't exist - there are no local properties
return null; return null;
} catch (IOException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public Map<ContactId, TransportProperties> getRemoteProperties( public Map<ContactId, TransportProperties> getRemoteProperties(
TransportId t) throws DbException { TransportId t) throws DbException {
lock.readLock().lock();
try { try {
Map<ContactId, TransportProperties> remote = Map<ContactId, TransportProperties> remote =
new HashMap<ContactId, TransportProperties>(); new HashMap<ContactId, TransportProperties>();
for (Contact c : contactManager.getContacts()) { Transaction txn = db.startTransaction();
Group g = getContactGroup(c); try {
// Find the latest remote update for (Contact c : db.getContacts(txn)) {
LatestUpdate latest = findLatest(g.getId(), t, false); Group g = getContactGroup(c);
if (latest != null) { // Find the latest remote update
// Retrieve and parse the latest remote properties LatestUpdate latest = findLatest(txn, g.getId(), t, false);
byte[] raw = db.getRawMessage(latest.messageId); if (latest != null) {
remote.put(c.getId(), parseProperties(raw)); // Retrieve and parse the latest remote properties
byte[] raw = db.getRawMessage(txn, latest.messageId);
remote.put(c.getId(), parseProperties(raw));
}
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
return Collections.unmodifiableMap(remote); return Collections.unmodifiableMap(remote);
} catch (IOException e) { } catch (FormatException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.readLock().unlock();
} }
} }
@Override @Override
public void mergeLocalProperties(TransportId t, TransportProperties p) public void mergeLocalProperties(TransportId t, TransportProperties p)
throws DbException { throws DbException {
lock.writeLock().lock();
try { try {
// Create the local group if necessary Transaction txn = db.startTransaction();
db.addGroup(localGroup); try {
// Merge the new properties with any existing properties // Create the local group if necessary
TransportProperties merged; db.addGroup(txn, localGroup);
LatestUpdate latest = findLatest(localGroup.getId(), t, true); // Merge the new properties with any existing properties
if (latest == null) { TransportProperties merged;
merged = p; boolean changed;
} else { LatestUpdate latest = findLatest(txn, localGroup.getId(), t,
byte[] raw = db.getRawMessage(latest.messageId); true);
TransportProperties old = parseProperties(raw); if (latest == null) {
merged = new TransportProperties(old); merged = p;
merged.putAll(p); changed = true;
if (merged.equals(old)) return; // Unchanged } else {
byte[] raw = db.getRawMessage(txn, latest.messageId);
TransportProperties old = parseProperties(raw);
merged = new TransportProperties(old);
merged.putAll(p);
changed = !merged.equals(old);
}
if (changed) {
// Store the merged properties in the local group
DeviceId dev = db.getDeviceId(txn);
long version = latest == null ? 1 : latest.version + 1;
storeMessage(txn, localGroup.getId(), dev, t, merged,
version, true, false);
// Store the merged properties in each contact's group
for (Contact c : db.getContacts(txn)) {
Group g = getContactGroup(c);
latest = findLatest(txn, g.getId(), t, true);
version = latest == null ? 1 : latest.version + 1;
storeMessage(txn, g.getId(), dev, t, merged, version,
true, true);
}
}
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
// Store the merged properties in the local group } catch (FormatException e) {
DeviceId dev = db.getDeviceId();
long version = latest == null ? 1 : latest.version + 1;
storeMessage(localGroup.getId(), dev, t, merged, version, true,
false);
// Store the merged properties in each contact's group
for (Contact c : contactManager.getContacts()) {
Group g = getContactGroup(c);
latest = findLatest(g.getId(), t, true);
version = latest == null ? 1 : latest.version + 1;
storeMessage(g.getId(), dev, t, merged, version, true, true);
}
} catch (IOException e) {
throw new DbException(e); throw new DbException(e);
} finally {
lock.writeLock().unlock();
} }
} }
@@ -261,18 +255,22 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
return privateGroupFactory.createPrivateGroup(CLIENT_ID, c); return privateGroupFactory.createPrivateGroup(CLIENT_ID, c);
} }
// Locking: lock.writeLock private void storeMessage(Transaction txn, GroupId g, DeviceId dev,
private void storeMessage(GroupId g, DeviceId dev, TransportId t, TransportId t, TransportProperties p, long version, boolean local,
TransportProperties p, long version, boolean local, boolean shared) boolean shared) throws DbException {
throws DbException, FormatException { try {
byte[] body = encodeProperties(dev, t, p, version); byte[] body = encodeProperties(dev, t, p, version);
long now = clock.currentTimeMillis(); long now = clock.currentTimeMillis();
Message m = messageFactory.createMessage(g, now, body); Message m = messageFactory.createMessage(g, now, body);
BdfDictionary d = new BdfDictionary(); BdfDictionary d = new BdfDictionary();
d.put("transportId", t.getString()); d.put("transportId", t.getString());
d.put("version", version); d.put("version", version);
d.put("local", local); d.put("local", local);
db.addLocalMessage(m, CLIENT_ID, metadataEncoder.encode(d), shared); Metadata meta = metadataEncoder.encode(d);
db.addLocalMessage(txn, m, CLIENT_ID, meta, shared);
} catch (FormatException e) {
throw new RuntimeException(e);
}
} }
private byte[] encodeProperties(DeviceId dev, TransportId t, private byte[] encodeProperties(DeviceId dev, TransportId t,
@@ -293,12 +291,11 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
return out.toByteArray(); return out.toByteArray();
} }
// Locking: lock.readLock private Map<TransportId, LatestUpdate> findLatest(Transaction txn,
private Map<TransportId, LatestUpdate> findLatest(GroupId g, boolean local) GroupId g, boolean local) throws DbException, FormatException {
throws DbException, FormatException {
Map<TransportId, LatestUpdate> latestUpdates = Map<TransportId, LatestUpdate> latestUpdates =
new HashMap<TransportId, LatestUpdate>(); new HashMap<TransportId, LatestUpdate>();
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
for (Entry<MessageId, Metadata> e : metadata.entrySet()) { for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
BdfDictionary d = metadataParser.parse(e.getValue()); BdfDictionary d = metadataParser.parse(e.getValue());
if (d.getBoolean("local") == local) { if (d.getBoolean("local") == local) {
@@ -312,11 +309,10 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
return latestUpdates; return latestUpdates;
} }
// Locking: lock.readLock private LatestUpdate findLatest(Transaction txn, GroupId g, TransportId t,
private LatestUpdate findLatest(GroupId g, TransportId t, boolean local) boolean local) throws DbException, FormatException {
throws DbException, FormatException {
LatestUpdate latest = null; LatestUpdate latest = null;
Map<MessageId, Metadata> metadata = db.getMessageMetadata(g); Map<MessageId, Metadata> metadata = db.getMessageMetadata(txn, g);
for (Entry<MessageId, Metadata> e : metadata.entrySet()) { for (Entry<MessageId, Metadata> e : metadata.entrySet()) {
BdfDictionary d = metadataParser.parse(e.getValue()); BdfDictionary d = metadataParser.parse(e.getValue());
if (d.getString("transportId").equals(t.getString()) if (d.getString("transportId").equals(t.getString())
@@ -330,25 +326,32 @@ class TransportPropertyManagerImpl implements TransportPropertyManager,
} }
private TransportProperties parseProperties(byte[] raw) private TransportProperties parseProperties(byte[] raw)
throws IOException { throws FormatException {
TransportProperties p = new TransportProperties(); TransportProperties p = new TransportProperties();
ByteArrayInputStream in = new ByteArrayInputStream(raw, ByteArrayInputStream in = new ByteArrayInputStream(raw,
MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH); MESSAGE_HEADER_LENGTH, raw.length - MESSAGE_HEADER_LENGTH);
BdfReader r = bdfReaderFactory.createReader(in); BdfReader r = bdfReaderFactory.createReader(in);
r.readListStart(); try {
r.skipRaw(); // Device ID r.readListStart();
r.skipString(); // Transport ID r.skipRaw(); // Device ID
r.skipInteger(); // Version r.skipString(); // Transport ID
r.readDictionaryStart(); r.skipInteger(); // Version
while (!r.hasDictionaryEnd()) { r.readDictionaryStart();
String key = r.readString(MAX_PROPERTY_LENGTH); while (!r.hasDictionaryEnd()) {
String value = r.readString(MAX_PROPERTY_LENGTH); String key = r.readString(MAX_PROPERTY_LENGTH);
p.put(key, value); String value = r.readString(MAX_PROPERTY_LENGTH);
p.put(key, value);
}
r.readDictionaryEnd();
r.readListEnd();
if (!r.eof()) throw new FormatException();
return p;
} catch (FormatException e) {
throw e;
} catch (IOException e) {
// Shouldn't happen with ByteArrayInputStream
throw new RuntimeException(e);
} }
r.readDictionaryEnd();
r.readListEnd();
if (!r.eof()) throw new FormatException();
return p;
} }
private static class LatestUpdate { private static class LatestUpdate {

View File

@@ -4,6 +4,7 @@ import com.google.inject.Inject;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.settings.Settings; import org.briarproject.api.settings.Settings;
import org.briarproject.api.settings.SettingsManager; import org.briarproject.api.settings.SettingsManager;
@@ -18,11 +19,24 @@ class SettingsManagerImpl implements SettingsManager {
@Override @Override
public Settings getSettings(String namespace) throws DbException { public Settings getSettings(String namespace) throws DbException {
return db.getSettings(namespace); Transaction txn = db.startTransaction();
try {
Settings s = db.getSettings(txn, namespace);
txn.setComplete();
return s;
} finally {
db.endTransaction(txn);
}
} }
@Override @Override
public void mergeSettings(Settings s, String namespace) throws DbException { public void mergeSettings(Settings s, String namespace) throws DbException {
db.mergeSettings(s, namespace); Transaction txn = db.startTransaction();
try {
db.mergeSettings(txn, s, namespace);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} }
} }

View File

@@ -4,6 +4,7 @@ import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
@@ -50,8 +51,8 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
private static final ThrowingRunnable<IOException> CLOSE = private static final ThrowingRunnable<IOException> CLOSE =
new ThrowingRunnable<IOException>() { new ThrowingRunnable<IOException>() {
public void run() {} public void run() {}
}; };
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor dbExecutor; private final Executor dbExecutor;
@@ -178,7 +179,14 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Ack a = db.generateAck(contactId, MAX_MESSAGE_IDS); Ack a;
Transaction txn = db.startTransaction();
try {
a = db.generateAck(txn, contactId, MAX_MESSAGE_IDS);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null)); LOG.info("Generated ack: " + (a != null));
if (a != null) writerTasks.add(new WriteAck(a)); if (a != null) writerTasks.add(new WriteAck(a));
@@ -212,8 +220,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Collection<byte[]> b = db.generateRequestedBatch(contactId, Collection<byte[]> b;
MAX_PACKET_PAYLOAD_LENGTH, maxLatency); Transaction txn = db.startTransaction();
try {
b = db.generateRequestedBatch(txn, contactId,
MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated batch: " + (b != null)); LOG.info("Generated batch: " + (b != null));
if (b != null) writerTasks.add(new WriteBatch(b)); if (b != null) writerTasks.add(new WriteBatch(b));
@@ -247,8 +262,15 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Offer o = db.generateOffer(contactId, MAX_MESSAGE_IDS, Offer o;
maxLatency); Transaction txn = db.startTransaction();
try {
o = db.generateOffer(txn, contactId, MAX_MESSAGE_IDS,
maxLatency);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated offer: " + (o != null)); LOG.info("Generated offer: " + (o != null));
if (o != null) writerTasks.add(new WriteOffer(o)); if (o != null) writerTasks.add(new WriteOffer(o));
@@ -282,7 +304,14 @@ class DuplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Request r = db.generateRequest(contactId, MAX_MESSAGE_IDS); Request r;
Transaction txn = db.startTransaction();
try {
r = db.generateRequest(txn, contactId, MAX_MESSAGE_IDS);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated request: " + (r != null)); LOG.info("Generated request: " + (r != null));
if (r != null) writerTasks.add(new WriteRequest(r)); if (r != null) writerTasks.add(new WriteRequest(r));

View File

@@ -5,6 +5,7 @@ import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
@@ -24,7 +25,9 @@ import java.util.logging.Logger;
import static java.util.logging.Level.WARNING; import static java.util.logging.Level.WARNING;
/** An incoming {@link org.briarproject.api.sync.SyncSession SyncSession}. */ /**
* An incoming {@link org.briarproject.api.sync.SyncSession SyncSession}.
*/
class IncomingSession implements SyncSession, EventListener { class IncomingSession implements SyncSession, EventListener {
private static final Logger LOG = private static final Logger LOG =
@@ -103,7 +106,13 @@ class IncomingSession implements SyncSession, EventListener {
public void run() { public void run() {
try { try {
db.receiveAck(contactId, ack); Transaction txn = db.startTransaction();
try {
db.receiveAck(txn, contactId, ack);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt(); interrupt();
@@ -121,7 +130,13 @@ class IncomingSession implements SyncSession, EventListener {
public void run() { public void run() {
try { try {
db.receiveMessage(contactId, message); Transaction txn = db.startTransaction();
try {
db.receiveMessage(txn, contactId, message);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt(); interrupt();
@@ -139,7 +154,13 @@ class IncomingSession implements SyncSession, EventListener {
public void run() { public void run() {
try { try {
db.receiveOffer(contactId, offer); Transaction txn = db.startTransaction();
try {
db.receiveOffer(txn, contactId, offer);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt(); interrupt();
@@ -157,7 +178,13 @@ class IncomingSession implements SyncSession, EventListener {
public void run() { public void run() {
try { try {
db.receiveRequest(contactId, request); Transaction txn = db.startTransaction();
try {
db.receiveRequest(txn, contactId, request);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
interrupt(); interrupt();

View File

@@ -4,6 +4,7 @@ import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
@@ -40,8 +41,8 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
private static final ThrowingRunnable<IOException> CLOSE = private static final ThrowingRunnable<IOException> CLOSE =
new ThrowingRunnable<IOException>() { new ThrowingRunnable<IOException>() {
public void run() {} public void run() {}
}; };
private final DatabaseComponent db; private final DatabaseComponent db;
private final Executor dbExecutor; private final Executor dbExecutor;
@@ -119,7 +120,14 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Ack a = db.generateAck(contactId, MAX_MESSAGE_IDS); Ack a;
Transaction txn = db.startTransaction();
try {
a = db.generateAck(txn, contactId, MAX_MESSAGE_IDS);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated ack: " + (a != null)); LOG.info("Generated ack: " + (a != null));
if (a == null) decrementOutstandingQueries(); if (a == null) decrementOutstandingQueries();
@@ -154,8 +162,15 @@ class SimplexOutgoingSession implements SyncSession, EventListener {
public void run() { public void run() {
if (interrupted) return; if (interrupted) return;
try { try {
Collection<byte[]> b = db.generateBatch(contactId, Collection<byte[]> b;
MAX_PACKET_PAYLOAD_LENGTH, maxLatency); Transaction txn = db.startTransaction();
try {
b = db.generateBatch(txn, contactId,
MAX_PACKET_PAYLOAD_LENGTH, maxLatency);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
if (LOG.isLoggable(INFO)) if (LOG.isLoggable(INFO))
LOG.info("Generated batch: " + (b != null)); LOG.info("Generated batch: " + (b != null));
if (b == null) decrementOutstandingQueries(); if (b == null) decrementOutstandingQueries();

View File

@@ -9,7 +9,7 @@ import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Metadata; import org.briarproject.api.db.Metadata;
import org.briarproject.api.db.NoSuchGroupException; import org.briarproject.api.db.NoSuchGroupException;
import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener; import org.briarproject.api.event.EventListener;
import org.briarproject.api.event.MessageAddedEvent; import org.briarproject.api.event.MessageAddedEvent;
@@ -82,14 +82,17 @@ class ValidationManagerImpl implements ValidationManager, Service,
public void run() { public void run() {
try { try {
// TODO: Don't do all of this in a single DB task // TODO: Don't do all of this in a single DB task
for (MessageId id : db.getMessagesToValidate(c)) { Transaction txn = db.startTransaction();
try { try {
Message m = parseMessage(id, db.getRawMessage(id)); for (MessageId id : db.getMessagesToValidate(txn, c)) {
Group g = db.getGroup(m.getGroupId()); byte[] raw = db.getRawMessage(txn, id);
Message m = parseMessage(id, raw);
Group g = db.getGroup(txn, m.getGroupId());
validateMessage(m, g); validateMessage(m, g);
} catch (NoSuchMessageException e) {
LOG.info("Message removed before validation");
} }
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) if (LOG.isLoggable(WARNING))
@@ -127,17 +130,21 @@ class ValidationManagerImpl implements ValidationManager, Service,
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
if (meta == null) { Transaction txn = db.startTransaction();
db.setMessageValid(m, c, false); try {
} else { if (meta == null) {
for (ValidationHook hook : hooks) db.setMessageValid(txn, m, c, false);
hook.validatingMessage(m, c, meta); } else {
db.mergeMessageMetadata(m.getId(), meta); for (ValidationHook hook : hooks)
db.setMessageValid(m, c, true); hook.validatingMessage(txn, m, c, meta);
db.setMessageShared(m, true); db.mergeMessageMetadata(txn, m.getId(), meta);
db.setMessageValid(txn, m, c, true);
db.setMessageShared(txn, m, true);
}
txn.setComplete();
} finally {
db.endTransaction(txn);
} }
} catch (NoSuchMessageException e) {
LOG.info("Message removed during validation");
} 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);
@@ -159,7 +166,13 @@ class ValidationManagerImpl implements ValidationManager, Service,
dbExecutor.execute(new Runnable() { dbExecutor.execute(new Runnable() {
public void run() { public void run() {
try { try {
validateMessage(m, db.getGroup(m.getGroupId())); Transaction txn = db.startTransaction();
try {
validateMessage(m, db.getGroup(txn, m.getGroupId()));
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (NoSuchGroupException e) { } catch (NoSuchGroupException e) {
LOG.info("Group removed before validation"); LOG.info("Group removed before validation");
} catch (DbException e) { } catch (DbException e) {

View File

@@ -7,6 +7,7 @@ import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DatabaseExecutor; import org.briarproject.api.db.DatabaseExecutor;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.ContactRemovedEvent; import org.briarproject.api.event.ContactRemovedEvent;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventListener; import org.briarproject.api.event.EventListener;
@@ -55,7 +56,14 @@ class KeyManagerImpl implements KeyManager, Service, EventListener {
@Override @Override
public boolean start() { public boolean start() {
try { try {
Map<TransportId, Integer> latencies = db.getTransportLatencies(); Map<TransportId, Integer> latencies;
Transaction txn = db.startTransaction();
try {
latencies = db.getTransportLatencies(txn);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
for (Entry<TransportId, Integer> e : latencies.entrySet()) for (Entry<TransportId, Integer> e : latencies.entrySet())
addTransport(e.getKey(), e.getValue()); addTransport(e.getKey(), e.getValue());
} catch (DbException e) { } catch (DbException e) {

View File

@@ -7,6 +7,7 @@ import org.briarproject.api.crypto.CryptoComponent;
import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.DbException; import org.briarproject.api.db.DbException;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.api.system.Timer; import org.briarproject.api.system.Timer;
import org.briarproject.api.transport.StreamContext; import org.briarproject.api.transport.StreamContext;
@@ -66,7 +67,13 @@ class TransportKeyManager extends TimerTask {
// Load the transport keys from the DB // Load the transport keys from the DB
Map<ContactId, TransportKeys> loaded; Map<ContactId, TransportKeys> loaded;
try { try {
loaded = db.getTransportKeys(transportId); Transaction txn = db.startTransaction();
try {
loaded = db.getTransportKeys(txn, transportId);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
return; return;
@@ -90,7 +97,13 @@ class TransportKeyManager extends TimerTask {
for (Entry<ContactId, TransportKeys> e : current.entrySet()) for (Entry<ContactId, TransportKeys> e : current.entrySet())
addKeys(e.getKey(), new MutableTransportKeys(e.getValue())); addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
// Write any rotated keys back to the DB // Write any rotated keys back to the DB
db.updateTransportKeys(rotated); Transaction txn = db.startTransaction();
try {
db.updateTransportKeys(txn, rotated);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally { } finally {
@@ -135,7 +148,13 @@ class TransportKeyManager extends TimerTask {
// Initialise mutable state for the contact // Initialise mutable state for the contact
addKeys(c, new MutableTransportKeys(k)); addKeys(c, new MutableTransportKeys(k));
// Write the keys back to the DB // Write the keys back to the DB
db.addTransportKeys(c, k); Transaction txn = db.startTransaction();
try {
db.addTransportKeys(txn, c, k);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally { } finally {
@@ -171,8 +190,14 @@ class TransportKeyManager extends TimerTask {
outKeys.getStreamCounter()); outKeys.getStreamCounter());
// Increment the stream counter and write it back to the DB // Increment the stream counter and write it back to the DB
outKeys.incrementStreamCounter(); outKeys.incrementStreamCounter();
db.incrementStreamCounter(c, transportId, Transaction txn = db.startTransaction();
outKeys.getRotationPeriod()); try {
db.incrementStreamCounter(txn, c, transportId,
outKeys.getRotationPeriod());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return ctx; return ctx;
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -210,9 +235,15 @@ class TransportKeyManager extends TimerTask {
inContexts.remove(new Bytes(removeTag)); inContexts.remove(new Bytes(removeTag));
} }
// Write the window back to the DB // Write the window back to the DB
db.setReorderingWindow(tagCtx.contactId, transportId, Transaction txn = db.startTransaction();
inKeys.getRotationPeriod(), window.getBase(), try {
window.getBitmap()); db.setReorderingWindow(txn, tagCtx.contactId, transportId,
inKeys.getRotationPeriod(), window.getBase(),
window.getBitmap());
txn.setComplete();
} finally {
db.endTransaction(txn);
}
return ctx; return ctx;
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
@@ -249,7 +280,13 @@ class TransportKeyManager extends TimerTask {
for (Entry<ContactId, TransportKeys> e : current.entrySet()) for (Entry<ContactId, TransportKeys> e : current.entrySet())
addKeys(e.getKey(), new MutableTransportKeys(e.getValue())); addKeys(e.getKey(), new MutableTransportKeys(e.getValue()));
// Write any rotated keys back to the DB // Write any rotated keys back to the DB
db.updateTransportKeys(rotated); Transaction txn = db.startTransaction();
try {
db.updateTransportKeys(txn, rotated);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
} catch (DbException e) { } catch (DbException e) {
if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e); if (LOG.isLoggable(WARNING)) LOG.log(WARNING, e.toString(), e);
} finally { } finally {

View File

@@ -14,6 +14,7 @@ import org.briarproject.api.db.NoSuchLocalAuthorException;
import org.briarproject.api.db.NoSuchMessageException; import org.briarproject.api.db.NoSuchMessageException;
import org.briarproject.api.db.NoSuchTransportException; import org.briarproject.api.db.NoSuchTransportException;
import org.briarproject.api.db.StorageStatus; import org.briarproject.api.db.StorageStatus;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.GroupAddedEvent; import org.briarproject.api.event.GroupAddedEvent;
import org.briarproject.api.event.GroupRemovedEvent; import org.briarproject.api.event.GroupRemovedEvent;
@@ -50,11 +51,13 @@ import org.junit.Test;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Map;
import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH; import static org.briarproject.api.identity.AuthorConstants.MAX_PUBLIC_KEY_LENGTH;
import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH; import static org.briarproject.api.sync.SyncConstants.MAX_GROUP_DESCRIPTOR_LENGTH;
import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN; import static org.briarproject.api.sync.ValidationManager.Validity.UNKNOWN;
import static org.briarproject.api.sync.ValidationManager.Validity.VALID; import static org.briarproject.api.sync.ValidationManager.Validity.VALID;
import static org.briarproject.api.transport.TransportConstants.REORDERING_WINDOW_SIZE;
import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES; import static org.briarproject.db.DatabaseConstants.MAX_OFFERED_MESSAGES;
import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertFalse;
@@ -121,14 +124,14 @@ public class DatabaseComponentImplTest extends BriarTestCase {
final ShutdownManager shutdown = context.mock(ShutdownManager.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class);
final EventBus eventBus = context.mock(EventBus.class); final EventBus eventBus = context.mock(EventBus.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
exactly(9).of(database).startTransaction();
will(returnValue(txn));
exactly(9).of(database).commitTransaction(txn);
// open() // open()
oneOf(database).open(); oneOf(database).open();
will(returnValue(false)); will(returnValue(false));
oneOf(shutdown).addShutdownHook(with(any(Runnable.class))); oneOf(shutdown).addShutdownHook(with(any(Runnable.class)));
will(returnValue(shutdownHandle)); will(returnValue(shutdownHandle));
// startTransaction()
oneOf(database).startTransaction();
will(returnValue(txn));
// addLocalAuthor() // addLocalAuthor()
oneOf(database).containsLocalAuthor(txn, localAuthorId); oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(false)); will(returnValue(false));
@@ -171,6 +174,8 @@ public class DatabaseComponentImplTest extends BriarTestCase {
oneOf(database).containsLocalAuthor(txn, localAuthorId); oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).removeLocalAuthor(txn, localAuthorId); oneOf(database).removeLocalAuthor(txn, localAuthorId);
// endTransaction()
oneOf(database).commitTransaction(txn);
// close() // close()
oneOf(shutdown).removeShutdownHook(shutdownHandle); oneOf(shutdown).removeShutdownHook(shutdownHandle);
oneOf(database).close(); oneOf(database).close();
@@ -179,15 +184,21 @@ public class DatabaseComponentImplTest extends BriarTestCase {
shutdown); shutdown);
assertFalse(db.open()); assertFalse(db.open());
db.addLocalAuthor(localAuthor); Transaction transaction = db.startTransaction();
assertEquals(contactId, db.addContact(author, localAuthorId)); db.addLocalAuthor(transaction, localAuthor);
assertEquals(Collections.singletonList(contact), db.getContacts()); assertEquals(contactId,
db.addGroup(group); // First time - listeners called db.addContact(transaction, author, localAuthorId));
db.addGroup(group); // Second time - not called assertEquals(Collections.singletonList(contact),
assertEquals(Collections.singletonList(group), db.getGroups(clientId)); db.getContacts(transaction));
db.removeGroup(group); db.addGroup(transaction, group); // First time - listeners called
db.removeContact(contactId); db.addGroup(transaction, group); // Second time - not called
db.removeLocalAuthor(localAuthorId); assertEquals(Collections.singletonList(group),
db.getGroups(transaction, clientId));
db.removeGroup(transaction, group);
db.removeContact(transaction, contactId);
db.removeLocalAuthor(transaction, localAuthorId);
transaction.setComplete();
db.endTransaction(transaction);
db.close(); db.close();
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -211,11 +222,14 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
Transaction transaction = db.startTransaction();
try { try {
db.addLocalMessage(message, clientId, metadata, true); db.addLocalMessage(transaction, message, clientId, metadata, true);
fail(); fail();
} catch (NoSuchGroupException expected) { } catch (NoSuchGroupException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -253,7 +267,13 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.addLocalMessage(message, clientId, metadata, true); Transaction transaction = db.startTransaction();
try {
db.addLocalMessage(transaction, message, clientId, metadata, true);
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -277,126 +297,178 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
Transaction transaction = db.startTransaction();
try { try {
db.addTransportKeys(contactId, createTransportKeys()); db.addTransportKeys(transaction, contactId, createTransportKeys());
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.generateAck(contactId, 123); db.generateAck(transaction, contactId, 123);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.generateBatch(contactId, 123, 456); db.generateBatch(transaction, contactId, 123, 456);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.generateOffer(contactId, 123, 456); db.generateOffer(transaction, contactId, 123, 456);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.generateRequest(contactId, 123); db.generateRequest(transaction, contactId, 123);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.getContact(contactId); db.getContact(transaction, contactId);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.getMessageStatus(contactId, groupId); db.getMessageStatus(transaction, contactId, groupId);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.getMessageStatus(contactId, messageId); db.getMessageStatus(transaction, contactId, messageId);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.incrementStreamCounter(contactId, transportId, 0); db.incrementStreamCounter(transaction, contactId, transportId, 0);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.isVisibleToContact(contactId, groupId); db.isVisibleToContact(transaction, contactId, groupId);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
Ack a = new Ack(Collections.singletonList(messageId)); Ack a = new Ack(Collections.singletonList(messageId));
db.receiveAck(contactId, a); db.receiveAck(transaction, contactId, a);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.receiveMessage(contactId, message); db.receiveMessage(transaction, contactId, message);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
Offer o = new Offer(Collections.singletonList(messageId)); Offer o = new Offer(Collections.singletonList(messageId));
db.receiveOffer(contactId, o); db.receiveOffer(transaction, contactId, o);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
Request r = new Request(Collections.singletonList(messageId)); Request r = new Request(Collections.singletonList(messageId));
db.receiveRequest(contactId, r); db.receiveRequest(transaction, contactId, r);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.removeContact(contactId); db.removeContact(transaction, contactId);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.setReorderingWindow(contactId, transportId, 0, 0, new byte[4]); db.setReorderingWindow(transaction, contactId, transportId, 0, 0,
new byte[REORDERING_WINDOW_SIZE / 8]);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.setVisibleToContact(contactId, groupId, true); db.setVisibleToContact(transaction, contactId, groupId, true);
fail(); fail();
} catch (NoSuchContactException expected) { } catch (NoSuchContactException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -421,25 +493,34 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
Transaction transaction = db.startTransaction();
try { try {
db.addContact(author, localAuthorId); db.addContact(transaction, author, localAuthorId);
fail(); fail();
} catch (NoSuchLocalAuthorException expected) { } catch (NoSuchLocalAuthorException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.getLocalAuthor(localAuthorId); db.getLocalAuthor(transaction, localAuthorId);
fail(); fail();
} catch (NoSuchLocalAuthorException expected) { } catch (NoSuchLocalAuthorException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.removeLocalAuthor(localAuthorId); db.removeLocalAuthor(transaction, localAuthorId);
fail(); fail();
} catch (NoSuchLocalAuthorException expected) { } catch (NoSuchLocalAuthorException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -468,53 +549,74 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
Transaction transaction = db.startTransaction();
try { try {
db.getGroup(groupId); db.getGroup(transaction, groupId);
fail(); fail();
} catch (NoSuchGroupException expected) { } catch (NoSuchGroupException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.getGroupMetadata(groupId); db.getGroupMetadata(transaction, groupId);
fail(); fail();
} catch (NoSuchGroupException expected) { } catch (NoSuchGroupException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.getMessageStatus(contactId, groupId); db.getMessageStatus(transaction, contactId, groupId);
fail(); fail();
} catch (NoSuchGroupException expected) { } catch (NoSuchGroupException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.isVisibleToContact(contactId, groupId); db.isVisibleToContact(transaction, contactId, groupId);
fail(); fail();
} catch (NoSuchGroupException expected) { } catch (NoSuchGroupException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.mergeGroupMetadata(groupId, metadata); db.mergeGroupMetadata(transaction, groupId, metadata);
fail(); fail();
} catch (NoSuchGroupException expected) { } catch (NoSuchGroupException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.removeGroup(group); db.removeGroup(transaction, group);
fail(); fail();
} catch (NoSuchGroupException expected) { } catch (NoSuchGroupException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.setVisibleToContact(contactId, groupId, true); db.setVisibleToContact(transaction, contactId, groupId, true);
fail(); fail();
} catch (NoSuchGroupException expected) { } catch (NoSuchGroupException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -542,60 +644,84 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
Transaction transaction = db.startTransaction();
try { try {
db.deleteMessage(messageId); db.deleteMessage(transaction, messageId);
fail(); fail();
} catch (NoSuchMessageException expected) { } catch (NoSuchMessageException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.deleteMessageMetadata(messageId); db.deleteMessageMetadata(transaction, messageId);
fail(); fail();
} catch (NoSuchMessageException expected) { } catch (NoSuchMessageException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.getRawMessage(messageId); db.getRawMessage(transaction, messageId);
fail(); fail();
} catch (NoSuchMessageException expected) { } catch (NoSuchMessageException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.getMessageMetadata(messageId); db.getMessageMetadata(transaction, messageId);
fail(); fail();
} catch (NoSuchMessageException expected) { } catch (NoSuchMessageException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.getMessageStatus(contactId, messageId); db.getMessageStatus(transaction, contactId, messageId);
fail(); fail();
} catch (NoSuchMessageException expected) { } catch (NoSuchMessageException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.mergeMessageMetadata(messageId, metadata); db.mergeMessageMetadata(transaction, messageId, metadata);
fail(); fail();
} catch (NoSuchMessageException expected) { } catch (NoSuchMessageException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.setMessageShared(message, true); db.setMessageShared(transaction, message, true);
fail(); fail();
} catch (NoSuchMessageException expected) { } catch (NoSuchMessageException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.setMessageValid(message, clientId, true); db.setMessageValid(transaction, message, clientId, true);
fail(); fail();
} catch (NoSuchMessageException expected) { } catch (NoSuchMessageException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -610,22 +736,21 @@ public class DatabaseComponentImplTest extends BriarTestCase {
final ShutdownManager shutdown = context.mock(ShutdownManager.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class);
final EventBus eventBus = context.mock(EventBus.class); final EventBus eventBus = context.mock(EventBus.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// addLocalAuthor() // startTransaction()
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
// addLocalAuthor()
oneOf(database).containsLocalAuthor(txn, localAuthorId); oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).addLocalAuthor(txn, localAuthor); oneOf(database).addLocalAuthor(txn, localAuthor);
oneOf(database).commitTransaction(txn);
// addContact() // addContact()
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsLocalAuthor(txn, localAuthorId); oneOf(database).containsLocalAuthor(txn, localAuthorId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).containsContact(txn, authorId, localAuthorId); oneOf(database).containsContact(txn, authorId, localAuthorId);
will(returnValue(false)); will(returnValue(false));
oneOf(database).addContact(txn, author, localAuthorId); oneOf(database).addContact(txn, author, localAuthorId);
will(returnValue(contactId)); will(returnValue(contactId));
// endTransaction()
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
// Check whether the transport is in the DB (which it's not) // Check whether the transport is in the DB (which it's not)
exactly(4).of(database).startTransaction(); exactly(4).of(database).startTransaction();
@@ -639,35 +764,55 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.addLocalAuthor(localAuthor); Transaction transaction = db.startTransaction();
assertEquals(contactId, db.addContact(author, localAuthorId));
try { try {
db.getTransportKeys(transportId); db.addLocalAuthor(transaction, localAuthor);
fail(); assertEquals(contactId,
} catch (NoSuchTransportException expected) { db.addContact(transaction, author, localAuthorId));
// Expected transaction.setComplete();
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.incrementStreamCounter(contactId, transportId, 0); db.getTransportKeys(transaction, transportId);
fail(); fail();
} catch (NoSuchTransportException expected) { } catch (NoSuchTransportException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.removeTransport(transportId); db.incrementStreamCounter(transaction, contactId, transportId, 0);
fail(); fail();
} catch (NoSuchTransportException expected) { } catch (NoSuchTransportException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
} }
transaction = db.startTransaction();
try { try {
db.setReorderingWindow(contactId, transportId, 0, 0, new byte[4]); db.removeTransport(transaction, transportId);
fail(); fail();
} catch (NoSuchTransportException expected) { } catch (NoSuchTransportException expected) {
// Expected // Expected
} finally {
db.endTransaction(transaction);
}
transaction = db.startTransaction();
try {
db.setReorderingWindow(transaction, contactId, transportId, 0, 0,
new byte[REORDERING_WINDOW_SIZE / 8]);
fail();
} catch (NoSuchTransportException expected) {
// Expected
} finally {
db.endTransaction(transaction);
} }
context.assertIsSatisfied(); context.assertIsSatisfied();
@@ -695,8 +840,14 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
Ack a = db.generateAck(contactId, 123); Transaction transaction = db.startTransaction();
assertEquals(messagesToAck, a.getMessageIds()); try {
Ack a = db.generateAck(transaction, contactId, 123);
assertEquals(messagesToAck, a.getMessageIds());
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -733,8 +884,14 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
assertEquals(messages, db.generateBatch(contactId, size * 2, Transaction transaction = db.startTransaction();
maxLatency)); try {
assertEquals(messages, db.generateBatch(transaction, contactId,
size * 2, maxLatency));
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -764,8 +921,14 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
Offer o = db.generateOffer(contactId, 123, maxLatency); Transaction transaction = db.startTransaction();
assertEquals(ids, o.getMessageIds()); try {
Offer o = db.generateOffer(transaction, contactId, 123, maxLatency);
assertEquals(ids, o.getMessageIds());
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -792,8 +955,14 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
Request r = db.generateRequest(contactId, 123); Transaction transaction = db.startTransaction();
assertEquals(ids, r.getMessageIds()); try {
Request r = db.generateRequest(transaction, contactId, 123);
assertEquals(ids, r.getMessageIds());
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -831,8 +1000,14 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
assertEquals(messages, db.generateRequestedBatch(contactId, size * 2, Transaction transaction = db.startTransaction();
maxLatency)); try {
assertEquals(messages, db.generateRequestedBatch(transaction,
contactId, size * 2, maxLatency));
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -858,7 +1033,14 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.receiveAck(contactId, new Ack(Collections.singletonList(messageId))); Transaction transaction = db.startTransaction();
try {
Ack a = new Ack(Collections.singletonList(messageId));
db.receiveAck(transaction, contactId, a);
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -896,7 +1078,13 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.receiveMessage(contactId, message); Transaction transaction = db.startTransaction();
try {
db.receiveMessage(transaction, contactId, message);
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -926,7 +1114,13 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.receiveMessage(contactId, message); Transaction transaction = db.startTransaction();
try {
db.receiveMessage(transaction, contactId, message);
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -952,7 +1146,13 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.receiveMessage(contactId, message); Transaction transaction = db.startTransaction();
try {
db.receiveMessage(transaction, contactId, message);
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -998,9 +1198,16 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
Offer o = new Offer(Arrays.asList(messageId, messageId1, messageId2, Transaction transaction = db.startTransaction();
messageId3)); try {
db.receiveOffer(contactId, o); Offer o = new Offer(Arrays.asList(messageId, messageId1,
messageId2, messageId3));
db.receiveOffer(transaction, contactId, o);
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -1026,8 +1233,14 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.receiveRequest(contactId, new Request(Collections.singletonList( Transaction transaction = db.startTransaction();
messageId))); try {
Request r = new Request(Collections.singletonList(messageId));
db.receiveRequest(transaction, contactId, r);
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -1056,7 +1269,13 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.setVisibleToContact(contactId, groupId, true); Transaction transaction = db.startTransaction();
try {
db.setVisibleToContact(transaction, contactId, groupId, true);
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -1083,45 +1302,56 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.setVisibleToContact(contactId, groupId, true); Transaction transaction = db.startTransaction();
try {
db.setVisibleToContact(transaction, contactId, groupId, true);
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@Test @Test
public void testTransportKeys() throws Exception { public void testTransportKeys() throws Exception {
final TransportKeys keys = createTransportKeys(); final TransportKeys transportKeys = createTransportKeys();
final Map<ContactId, TransportKeys> keys = Collections.singletonMap(
contactId, transportKeys);
Mockery context = new Mockery(); Mockery context = new Mockery();
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
final Database<Object> database = context.mock(Database.class); final Database<Object> database = context.mock(Database.class);
final ShutdownManager shutdown = context.mock(ShutdownManager.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class);
final EventBus eventBus = context.mock(EventBus.class); final EventBus eventBus = context.mock(EventBus.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// updateTransportKeys() // startTransaction()
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
// updateTransportKeys()
oneOf(database).containsContact(txn, contactId); oneOf(database).containsContact(txn, contactId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).containsTransport(txn, transportId); oneOf(database).containsTransport(txn, transportId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).updateTransportKeys(txn, oneOf(database).updateTransportKeys(txn, keys);
Collections.singletonMap(contactId, keys));
oneOf(database).commitTransaction(txn);
// getTransportKeys() // getTransportKeys()
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).containsTransport(txn, transportId); oneOf(database).containsTransport(txn, transportId);
will(returnValue(true)); will(returnValue(true));
oneOf(database).getTransportKeys(txn, transportId); oneOf(database).getTransportKeys(txn, transportId);
will(returnValue(Collections.singletonMap(contactId, keys))); will(returnValue(keys));
// endTransaction()
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
db.updateTransportKeys(Collections.singletonMap(contactId, keys)); Transaction transaction = db.startTransaction();
assertEquals(Collections.singletonMap(contactId, keys), try {
db.getTransportKeys(transportId)); db.updateTransportKeys(transaction, keys);
assertEquals(keys, db.getTransportKeys(transaction, transportId));
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }
@@ -1162,29 +1392,34 @@ public class DatabaseComponentImplTest extends BriarTestCase {
final ShutdownManager shutdown = context.mock(ShutdownManager.class); final ShutdownManager shutdown = context.mock(ShutdownManager.class);
final EventBus eventBus = context.mock(EventBus.class); final EventBus eventBus = context.mock(EventBus.class);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// mergeSettings() // startTransaction()
oneOf(database).startTransaction(); oneOf(database).startTransaction();
will(returnValue(txn)); will(returnValue(txn));
// mergeSettings()
oneOf(database).getSettings(txn, "namespace"); oneOf(database).getSettings(txn, "namespace");
will(returnValue(before)); will(returnValue(before));
oneOf(database).mergeSettings(txn, update, "namespace"); oneOf(database).mergeSettings(txn, update, "namespace");
oneOf(database).commitTransaction(txn);
oneOf(eventBus).broadcast(with(any(SettingsUpdatedEvent.class))); oneOf(eventBus).broadcast(with(any(SettingsUpdatedEvent.class)));
// mergeSettings() again // mergeSettings() again
oneOf(database).startTransaction();
will(returnValue(txn));
oneOf(database).getSettings(txn, "namespace"); oneOf(database).getSettings(txn, "namespace");
will(returnValue(merged)); will(returnValue(merged));
// endTransaction()
oneOf(database).commitTransaction(txn); oneOf(database).commitTransaction(txn);
}}); }});
DatabaseComponent db = createDatabaseComponent(database, eventBus, DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown); shutdown);
// First merge should broadcast an event Transaction transaction = db.startTransaction();
db.mergeSettings(update, "namespace"); try {
// Second merge should not broadcast an event // First merge should broadcast an event
db.mergeSettings(update, "namespace"); db.mergeSettings(transaction, update, "namespace");
// Second merge should not broadcast an event
db.mergeSettings(transaction, update, "namespace");
transaction.setComplete();
} finally {
db.endTransaction(transaction);
}
context.assertIsSatisfied(); context.assertIsSatisfied();
} }

View File

@@ -3,6 +3,7 @@ package org.briarproject.plugins;
import org.briarproject.BriarTestCase; import org.briarproject.BriarTestCase;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.plugins.ConnectionManager; import org.briarproject.api.plugins.ConnectionManager;
import org.briarproject.api.plugins.duplex.DuplexPlugin; import org.briarproject.api.plugins.duplex.DuplexPlugin;
@@ -14,6 +15,7 @@ import org.briarproject.api.plugins.simplex.SimplexPluginCallback;
import org.briarproject.api.plugins.simplex.SimplexPluginConfig; import org.briarproject.api.plugins.simplex.SimplexPluginConfig;
import org.briarproject.api.plugins.simplex.SimplexPluginFactory; import org.briarproject.api.plugins.simplex.SimplexPluginFactory;
import org.briarproject.api.properties.TransportPropertyManager; import org.briarproject.api.properties.TransportPropertyManager;
import org.briarproject.api.settings.SettingsManager;
import org.briarproject.api.system.Clock; import org.briarproject.api.system.Clock;
import org.briarproject.api.ui.UiCallback; import org.briarproject.api.ui.UiCallback;
import org.briarproject.system.SystemClock; import org.briarproject.system.SystemClock;
@@ -46,6 +48,8 @@ public class PluginManagerImplTest extends BriarTestCase {
final Poller poller = context.mock(Poller.class); final Poller poller = context.mock(Poller.class);
final ConnectionManager connectionManager = final ConnectionManager connectionManager =
context.mock(ConnectionManager.class); context.mock(ConnectionManager.class);
final SettingsManager settingsManager =
context.mock(SettingsManager.class);
final TransportPropertyManager transportPropertyManager = final TransportPropertyManager transportPropertyManager =
context.mock(TransportPropertyManager.class); context.mock(TransportPropertyManager.class);
final UiCallback uiCallback = context.mock(UiCallback.class); final UiCallback uiCallback = context.mock(UiCallback.class);
@@ -55,18 +59,21 @@ public class PluginManagerImplTest extends BriarTestCase {
final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class); final SimplexPlugin simplexPlugin = context.mock(SimplexPlugin.class);
final TransportId simplexId = new TransportId("simplex"); final TransportId simplexId = new TransportId("simplex");
final int simplexLatency = 12345; final int simplexLatency = 12345;
final Transaction simplexTxn = new Transaction(null);
final SimplexPluginFactory simplexFailFactory = final SimplexPluginFactory simplexFailFactory =
context.mock(SimplexPluginFactory.class, "simplexFailFactory"); context.mock(SimplexPluginFactory.class, "simplexFailFactory");
final SimplexPlugin simplexFailPlugin = final SimplexPlugin simplexFailPlugin =
context.mock(SimplexPlugin.class, "simplexFailPlugin"); context.mock(SimplexPlugin.class, "simplexFailPlugin");
final TransportId simplexFailId = new TransportId("simplex1"); final TransportId simplexFailId = new TransportId("simplex1");
final int simplexFailLatency = 23456; final int simplexFailLatency = 23456;
final Transaction simplexFailTxn = new Transaction(null);
// Two duplex plugin factories: one creates a plugin, the other fails // Two duplex plugin factories: one creates a plugin, the other fails
final DuplexPluginFactory duplexFactory = final DuplexPluginFactory duplexFactory =
context.mock(DuplexPluginFactory.class); context.mock(DuplexPluginFactory.class);
final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class); final DuplexPlugin duplexPlugin = context.mock(DuplexPlugin.class);
final TransportId duplexId = new TransportId("duplex"); final TransportId duplexId = new TransportId("duplex");
final int duplexLatency = 34567; final int duplexLatency = 34567;
final Transaction duplexTxn = new Transaction(null);
final DuplexPluginFactory duplexFailFactory = final DuplexPluginFactory duplexFailFactory =
context.mock(DuplexPluginFactory.class, "duplexFailFactory"); context.mock(DuplexPluginFactory.class, "duplexFailFactory");
final TransportId duplexFailId = new TransportId("duplex1"); final TransportId duplexFailId = new TransportId("duplex1");
@@ -82,7 +89,10 @@ public class PluginManagerImplTest extends BriarTestCase {
will(returnValue(simplexPlugin)); // Created will(returnValue(simplexPlugin)); // Created
oneOf(simplexPlugin).getMaxLatency(); oneOf(simplexPlugin).getMaxLatency();
will(returnValue(simplexLatency)); will(returnValue(simplexLatency));
oneOf(db).addTransport(simplexId, simplexLatency); oneOf(db).startTransaction();
will(returnValue(simplexTxn));
oneOf(db).addTransport(simplexTxn, simplexId, simplexLatency);
oneOf(db).endTransaction(simplexTxn);
oneOf(simplexPlugin).start(); oneOf(simplexPlugin).start();
will(returnValue(true)); // Started will(returnValue(true)); // Started
oneOf(simplexPlugin).shouldPoll(); oneOf(simplexPlugin).shouldPoll();
@@ -96,7 +106,11 @@ public class PluginManagerImplTest extends BriarTestCase {
will(returnValue(simplexFailPlugin)); // Created will(returnValue(simplexFailPlugin)); // Created
oneOf(simplexFailPlugin).getMaxLatency(); oneOf(simplexFailPlugin).getMaxLatency();
will(returnValue(simplexFailLatency)); will(returnValue(simplexFailLatency));
oneOf(db).addTransport(simplexFailId, simplexFailLatency); oneOf(db).startTransaction();
will(returnValue(simplexFailTxn));
oneOf(db).addTransport(simplexFailTxn, simplexFailId,
simplexFailLatency);
oneOf(db).endTransaction(simplexFailTxn);
oneOf(simplexFailPlugin).start(); oneOf(simplexFailPlugin).start();
will(returnValue(false)); // Failed to start will(returnValue(false)); // Failed to start
// First duplex plugin // First duplex plugin
@@ -109,7 +123,10 @@ public class PluginManagerImplTest extends BriarTestCase {
will(returnValue(duplexPlugin)); // Created will(returnValue(duplexPlugin)); // Created
oneOf(duplexPlugin).getMaxLatency(); oneOf(duplexPlugin).getMaxLatency();
will(returnValue(duplexLatency)); will(returnValue(duplexLatency));
oneOf(db).addTransport(duplexId, duplexLatency); oneOf(db).startTransaction();
will(returnValue(duplexTxn));
oneOf(db).addTransport(duplexTxn, duplexId, duplexLatency);
oneOf(db).endTransaction(duplexTxn);
oneOf(duplexPlugin).start(); oneOf(duplexPlugin).start();
will(returnValue(true)); // Started will(returnValue(true)); // Started
oneOf(duplexPlugin).shouldPoll(); oneOf(duplexPlugin).shouldPoll();
@@ -128,7 +145,8 @@ public class PluginManagerImplTest extends BriarTestCase {
}}); }});
PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus, PluginManagerImpl p = new PluginManagerImpl(ioExecutor, eventBus,
simplexPluginConfig, duplexPluginConfig, clock, db, poller, simplexPluginConfig, duplexPluginConfig, clock, db, poller,
connectionManager, transportPropertyManager, uiCallback); connectionManager, settingsManager, transportPropertyManager,
uiCallback);
// Two plugins should be started and stopped // Two plugins should be started and stopped
assertTrue(p.start()); assertTrue(p.start());

View File

@@ -13,6 +13,7 @@ import org.briarproject.api.contact.ContactManager;
import org.briarproject.api.crypto.SecretKey; import org.briarproject.api.crypto.SecretKey;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.StorageStatus; import org.briarproject.api.db.StorageStatus;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.Event; import org.briarproject.api.event.Event;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.event.EventListener; import org.briarproject.api.event.EventListener;
@@ -120,7 +121,13 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
lifecycleManager.startServices(); lifecycleManager.startServices();
lifecycleManager.waitForStartup(); lifecycleManager.waitForStartup();
// Add a transport // Add a transport
db.addTransport(transportId, MAX_LATENCY); Transaction txn = db.startTransaction();
try {
db.addTransport(txn, transportId, MAX_LATENCY);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
// Add an identity for Alice // Add an identity for Alice
LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice", LocalAuthor aliceAuthor = new LocalAuthor(aliceId, "Alice",
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp, new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp,
@@ -185,7 +192,13 @@ public class SimplexMessagingIntegrationTest extends BriarTestCase {
lifecycleManager.startServices(); lifecycleManager.startServices();
lifecycleManager.waitForStartup(); lifecycleManager.waitForStartup();
// Add a transport // Add a transport
db.addTransport(transportId, MAX_LATENCY); Transaction txn = db.startTransaction();
try {
db.addTransport(txn, transportId, MAX_LATENCY);
txn.setComplete();
} finally {
db.endTransaction(txn);
}
// Add an identity for Bob // Add an identity for Bob
LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob", LocalAuthor bobAuthor = new LocalAuthor(bobId, "Bob",
new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp, new byte[MAX_PUBLIC_KEY_LENGTH], new byte[123], timestamp,

View File

@@ -5,6 +5,7 @@ import org.briarproject.TestUtils;
import org.briarproject.api.TransportId; import org.briarproject.api.TransportId;
import org.briarproject.api.contact.ContactId; import org.briarproject.api.contact.ContactId;
import org.briarproject.api.db.DatabaseComponent; import org.briarproject.api.db.DatabaseComponent;
import org.briarproject.api.db.Transaction;
import org.briarproject.api.event.EventBus; import org.briarproject.api.event.EventBus;
import org.briarproject.api.sync.Ack; import org.briarproject.api.sync.Ack;
import org.briarproject.api.sync.MessageId; import org.briarproject.api.sync.MessageId;
@@ -49,16 +50,24 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
final SimplexOutgoingSession session = new SimplexOutgoingSession(db, final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, transportId, maxLatency, dbExecutor, eventBus, contactId, transportId, maxLatency,
packetWriter); packetWriter);
final Transaction noAckTxn = new Transaction(null);
final Transaction noMsgTxn = new Transaction(null);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Add listener // Add listener
oneOf(eventBus).addListener(session); oneOf(eventBus).addListener(session);
// No acks to send // No acks to send
oneOf(db).generateAck(contactId, MAX_MESSAGE_IDS); oneOf(db).startTransaction();
will(returnValue(noAckTxn));
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(null)); will(returnValue(null));
oneOf(db).endTransaction(noAckTxn);
// No messages to send // No messages to send
oneOf(db).generateBatch(with(contactId), with(any(int.class)), oneOf(db).startTransaction();
with(maxLatency)); will(returnValue(noMsgTxn));
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
with(any(int.class)), with(maxLatency));
will(returnValue(null)); will(returnValue(null));
oneOf(db).endTransaction(noMsgTxn);
// Flush the output stream // Flush the output stream
oneOf(packetWriter).flush(); oneOf(packetWriter).flush();
// Remove listener // Remove listener
@@ -75,25 +84,41 @@ public class SimplexOutgoingSessionTest extends BriarTestCase {
final SimplexOutgoingSession session = new SimplexOutgoingSession(db, final SimplexOutgoingSession session = new SimplexOutgoingSession(db,
dbExecutor, eventBus, contactId, transportId, maxLatency, dbExecutor, eventBus, contactId, transportId, maxLatency,
packetWriter); packetWriter);
final Transaction ackTxn = new Transaction(null);
final Transaction noAckTxn = new Transaction(null);
final Transaction msgTxn = new Transaction(null);
final Transaction noMsgTxn = new Transaction(null);
context.checking(new Expectations() {{ context.checking(new Expectations() {{
// Add listener // Add listener
oneOf(eventBus).addListener(session); oneOf(eventBus).addListener(session);
// One ack to send // One ack to send
oneOf(db).generateAck(contactId, MAX_MESSAGE_IDS); oneOf(db).startTransaction();
will(returnValue(ackTxn));
oneOf(db).generateAck(ackTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(ack)); will(returnValue(ack));
oneOf(db).endTransaction(ackTxn);
oneOf(packetWriter).writeAck(ack); oneOf(packetWriter).writeAck(ack);
// No more acks
oneOf(db).generateAck(contactId, MAX_MESSAGE_IDS);
will(returnValue(null));
// One message to send // One message to send
oneOf(db).generateBatch(with(contactId), with(any(int.class)), oneOf(db).startTransaction();
with(maxLatency)); will(returnValue(msgTxn));
oneOf(db).generateBatch(with(msgTxn), with(contactId),
with(any(int.class)), with(maxLatency));
will(returnValue(Arrays.asList(raw))); will(returnValue(Arrays.asList(raw)));
oneOf(db).endTransaction(msgTxn);
oneOf(packetWriter).writeMessage(raw); oneOf(packetWriter).writeMessage(raw);
// No more messages // No more acks
oneOf(db).generateBatch(with(contactId), with(any(int.class)), oneOf(db).startTransaction();
with(maxLatency)); will(returnValue(noAckTxn));
oneOf(db).generateAck(noAckTxn, contactId, MAX_MESSAGE_IDS);
will(returnValue(null)); will(returnValue(null));
oneOf(db).endTransaction(noAckTxn);
// No more messages
oneOf(db).startTransaction();
will(returnValue(noMsgTxn));
oneOf(db).generateBatch(with(noMsgTxn), with(contactId),
with(any(int.class)), with(maxLatency));
will(returnValue(null));
oneOf(db).endTransaction(noMsgTxn);
// Flush the output stream // Flush the output stream
oneOf(packetWriter).flush(); oneOf(packetWriter).flush();
// Remove listener // Remove listener