mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-11 18:29:05 +01:00
Added support for local transport details. Each bundle contains the sender's latest transport details.
This commit is contained in:
@@ -1,5 +1,7 @@
|
|||||||
package net.sf.briar.api.protocol;
|
package net.sf.briar.api.protocol;
|
||||||
|
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/** A bundle of acknowledgements, subscriptions, and batches of messages. */
|
/** A bundle of acknowledgements, subscriptions, and batches of messages. */
|
||||||
public interface Bundle {
|
public interface Bundle {
|
||||||
|
|
||||||
@@ -29,6 +31,12 @@ public interface Bundle {
|
|||||||
/** Adds a subscription to the bundle. Cannot be called after seal(). */
|
/** Adds a subscription to the bundle. Cannot be called after seal(). */
|
||||||
void addSubscription(GroupId g);
|
void addSubscription(GroupId g);
|
||||||
|
|
||||||
|
/** Returns the transport details contained in the bundle. */
|
||||||
|
Map<String, String> getTransports();
|
||||||
|
|
||||||
|
/** Adds a transport detail to the bundle. Cannot be called after seal(). */
|
||||||
|
void addTransport(String key, String value);
|
||||||
|
|
||||||
/** Returns the batches of messages contained in the bundle. */
|
/** Returns the batches of messages contained in the bundle. */
|
||||||
Iterable<Batch> getBatches();
|
Iterable<Batch> getBatches();
|
||||||
|
|
||||||
|
|||||||
@@ -119,18 +119,8 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void addSubscription(T txn, GroupId g) throws DbException;
|
void addSubscription(T txn, GroupId g) throws DbException;
|
||||||
|
|
||||||
/**
|
// FIXME: Replace these two methods with a setSubscriptions() method
|
||||||
* Records a contact's subscription to a group.
|
|
||||||
* <p>
|
|
||||||
* Locking: contacts read, subscriptions write.
|
|
||||||
*/
|
|
||||||
void addSubscription(T txn, ContactId c, GroupId g) throws DbException;
|
void addSubscription(T txn, ContactId c, GroupId g) throws DbException;
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes all recorded subscriptions for the given contact.
|
|
||||||
* <p>
|
|
||||||
* Locking: contacts read, subscriptions write.
|
|
||||||
*/
|
|
||||||
void clearSubscriptions(T txn, ContactId c) throws DbException;
|
void clearSubscriptions(T txn, ContactId c) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -245,6 +235,13 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
Set<GroupId> getSubscriptions(T txn) throws DbException;
|
Set<GroupId> getSubscriptions(T txn) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the local transport details.
|
||||||
|
* <p>
|
||||||
|
* Locking: transports read.
|
||||||
|
*/
|
||||||
|
Map<String, String> getTransports(T txn) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the transport details for the given contact.
|
* Returns the transport details for the given contact.
|
||||||
* <p>
|
* <p>
|
||||||
@@ -323,6 +320,14 @@ interface Database<T> {
|
|||||||
*/
|
*/
|
||||||
void setStatus(T txn, ContactId c, MessageId m, Status s) throws DbException;
|
void setStatus(T txn, ContactId c, MessageId m, Status s) throws DbException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the local transport details, replacing any existing transport
|
||||||
|
* details.
|
||||||
|
* <p>
|
||||||
|
* Locking: transports write.
|
||||||
|
*/
|
||||||
|
void setTransports(T txn, Map<String, String> transports) throws DbException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the transport details for the given contact, replacing any existing
|
* Sets the transport details for the given contact, replacing any existing
|
||||||
* transport details.
|
* transport details.
|
||||||
|
|||||||
@@ -150,15 +150,21 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
private static final String INDEX_STATUSES_BY_CONTACT =
|
private static final String INDEX_STATUSES_BY_CONTACT =
|
||||||
"CREATE INDEX statusesByContact ON statuses (contactId)";
|
"CREATE INDEX statusesByContact ON statuses (contactId)";
|
||||||
|
|
||||||
private static final String CREATE_TRANSPORTS =
|
private static final String CREATE_CONTACT_TRANSPORTS =
|
||||||
"CREATE TABLE transports"
|
"CREATE TABLE contactTransports"
|
||||||
+ " (contactId INT NOT NULL,"
|
+ " (contactId INT NOT NULL,"
|
||||||
+ " detailKey VARCHAR NOT NULL,"
|
+ " key VARCHAR NOT NULL,"
|
||||||
+ " detailValue VARCHAR NOT NULL,"
|
+ " value VARCHAR NOT NULL,"
|
||||||
+ " PRIMARY KEY (contactId, detailKey),"
|
+ " PRIMARY KEY (contactId, key),"
|
||||||
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
|
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
|
||||||
+ " ON DELETE CASCADE)";
|
+ " ON DELETE CASCADE)";
|
||||||
|
|
||||||
|
private static final String CREATE_LOCAL_TRANSPORTS =
|
||||||
|
"CREATE TABLE localTransports"
|
||||||
|
+ " (key VARCHAR NOT NULL,"
|
||||||
|
+ " value VARCHAR NOT NULL,"
|
||||||
|
+ " PRIMARY KEY (key))";
|
||||||
|
|
||||||
private static final Logger LOG =
|
private static final Logger LOG =
|
||||||
Logger.getLogger(JdbcDatabase.class.getName());
|
Logger.getLogger(JdbcDatabase.class.getName());
|
||||||
|
|
||||||
@@ -249,8 +255,11 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
s.executeUpdate(INDEX_STATUSES_BY_MESSAGE);
|
s.executeUpdate(INDEX_STATUSES_BY_MESSAGE);
|
||||||
s.executeUpdate(INDEX_STATUSES_BY_CONTACT);
|
s.executeUpdate(INDEX_STATUSES_BY_CONTACT);
|
||||||
if(LOG.isLoggable(Level.FINE))
|
if(LOG.isLoggable(Level.FINE))
|
||||||
LOG.fine("Creating transports table");
|
LOG.fine("Creating contact transports table");
|
||||||
s.executeUpdate(insertHashType(CREATE_TRANSPORTS));
|
s.executeUpdate(insertHashType(CREATE_CONTACT_TRANSPORTS));
|
||||||
|
if(LOG.isLoggable(Level.FINE))
|
||||||
|
LOG.fine("Creating local transports table");
|
||||||
|
s.executeUpdate(insertHashType(CREATE_LOCAL_TRANSPORTS));
|
||||||
s.close();
|
s.close();
|
||||||
} catch(SQLException e) {
|
} catch(SQLException e) {
|
||||||
tryToClose(s);
|
tryToClose(s);
|
||||||
@@ -418,8 +427,8 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
ps.close();
|
ps.close();
|
||||||
// Store the contact's transport details
|
// Store the contact's transport details
|
||||||
if(transports != null) {
|
if(transports != null) {
|
||||||
sql = "INSERT INTO transports"
|
sql = "INSERT INTO contactTransports"
|
||||||
+ " (contactId, detailKey, detailValue)"
|
+ " (contactId, key, value)"
|
||||||
+ " VALUES (?, ?, ?)";
|
+ " VALUES (?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
@@ -1102,12 +1111,33 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Map<String, String> getTransports(Connection txn)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
ResultSet rs = null;
|
||||||
|
try {
|
||||||
|
String sql = "SELECT key, value FROM localTransports";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
rs = ps.executeQuery();
|
||||||
|
Map<String, String> transports = new TreeMap<String, String>();
|
||||||
|
while(rs.next()) transports.put(rs.getString(1), rs.getString(2));
|
||||||
|
rs.close();
|
||||||
|
ps.close();
|
||||||
|
return transports;
|
||||||
|
} catch(SQLException e) {
|
||||||
|
tryToClose(rs);
|
||||||
|
tryToClose(ps);
|
||||||
|
tryToClose(txn);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public Map<String, String> getTransports(Connection txn, ContactId c)
|
public Map<String, String> getTransports(Connection txn, ContactId c)
|
||||||
throws DbException {
|
throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
ResultSet rs = null;
|
ResultSet rs = null;
|
||||||
try {
|
try {
|
||||||
String sql = "SELECT detailKey, detailValue FROM transports"
|
String sql = "SELECT key, value FROM contactTransports"
|
||||||
+ " WHERE contactId = ?";
|
+ " WHERE contactId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
@@ -1373,20 +1403,52 @@ abstract class JdbcDatabase implements Database<Connection> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setTransports(Connection txn, Map<String, String> transports)
|
||||||
|
throws DbException {
|
||||||
|
PreparedStatement ps = null;
|
||||||
|
try {
|
||||||
|
// Delete any existing transports
|
||||||
|
String sql = "DELETE FROM localTransports";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
ps.executeUpdate();
|
||||||
|
ps.close();
|
||||||
|
// Store the new transports
|
||||||
|
if(transports != null) {
|
||||||
|
sql = "INSERT INTO localTransports (key, value)"
|
||||||
|
+ " VALUES (?, ?)";
|
||||||
|
ps = txn.prepareStatement(sql);
|
||||||
|
for(Entry<String, String> e : transports.entrySet()) {
|
||||||
|
ps.setString(1, e.getKey());
|
||||||
|
ps.setString(2, e.getValue());
|
||||||
|
ps.addBatch();
|
||||||
|
}
|
||||||
|
int[] rowsAffectedArray = ps.executeBatch();
|
||||||
|
assert rowsAffectedArray.length == transports.size();
|
||||||
|
for(int i = 0; i < rowsAffectedArray.length; i++) {
|
||||||
|
assert rowsAffectedArray[i] == 1;
|
||||||
|
}
|
||||||
|
ps.close();
|
||||||
|
}
|
||||||
|
} catch(SQLException e) {
|
||||||
|
tryToClose(ps);
|
||||||
|
tryToClose(txn);
|
||||||
|
throw new DbException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void setTransports(Connection txn, ContactId c,
|
public void setTransports(Connection txn, ContactId c,
|
||||||
Map<String, String> transports) throws DbException {
|
Map<String, String> transports) throws DbException {
|
||||||
PreparedStatement ps = null;
|
PreparedStatement ps = null;
|
||||||
try {
|
try {
|
||||||
// Delete any existing transports
|
// Delete any existing transports
|
||||||
String sql = "DELETE FROM transports WHERE contactId = ?";
|
String sql = "DELETE FROM contactTransports WHERE contactId = ?";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
ps.executeUpdate();
|
ps.executeUpdate();
|
||||||
ps.close();
|
ps.close();
|
||||||
// Store the new transports
|
// Store the new transports
|
||||||
if(transports != null) {
|
if(transports != null) {
|
||||||
sql = "INSERT INTO transports"
|
sql = "INSERT INTO contactTransports (contactId, key, value)"
|
||||||
+ " (contactId, detailKey, detailValue)"
|
|
||||||
+ " VALUES (?, ?, ?)";
|
+ " VALUES (?, ?, ?)";
|
||||||
ps = txn.prepareStatement(sql);
|
ps = txn.prepareStatement(sql);
|
||||||
ps.setInt(1, c.getInt());
|
ps.setInt(1, c.getInt());
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package net.sf.briar.db;
|
|||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
import java.util.concurrent.locks.ReentrantReadWriteLock;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
@@ -237,6 +238,33 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
|||||||
} finally {
|
} finally {
|
||||||
contactLock.readLock().unlock();
|
contactLock.readLock().unlock();
|
||||||
}
|
}
|
||||||
|
// Add transport details
|
||||||
|
contactLock.readLock().lock();
|
||||||
|
try {
|
||||||
|
if(!containsContact(c)) throw new NoSuchContactException();
|
||||||
|
transportLock.readLock().lock();
|
||||||
|
try {
|
||||||
|
Txn txn = db.startTransaction();
|
||||||
|
try {
|
||||||
|
int numTransports = 0;
|
||||||
|
Map<String, String> transports = db.getTransports(txn);
|
||||||
|
for(Entry<String, String> e : transports.entrySet()) {
|
||||||
|
b.addTransport(e.getKey(), e.getValue());
|
||||||
|
numTransports++;
|
||||||
|
}
|
||||||
|
if(LOG.isLoggable(Level.FINE))
|
||||||
|
LOG.fine("Added " + numTransports + " transports");
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
} catch(DbException e) {
|
||||||
|
db.abortTransaction(txn);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
transportLock.readLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
contactLock.readLock().unlock();
|
||||||
|
}
|
||||||
// Add as many messages as possible to the bundle
|
// Add as many messages as possible to the bundle
|
||||||
long capacity = b.getCapacity();
|
long capacity = b.getCapacity();
|
||||||
while(true) {
|
while(true) {
|
||||||
@@ -448,7 +476,27 @@ class ReadWriteLockDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
|||||||
subscriptionLock.writeLock().unlock();
|
subscriptionLock.writeLock().unlock();
|
||||||
}
|
}
|
||||||
} finally {
|
} finally {
|
||||||
contactLock.readLock().lock();
|
contactLock.readLock().unlock();
|
||||||
|
}
|
||||||
|
// Update the contact's transport details
|
||||||
|
contactLock.readLock().lock();
|
||||||
|
try {
|
||||||
|
if(!containsContact(c)) throw new NoSuchContactException();
|
||||||
|
transportLock.writeLock().lock();
|
||||||
|
try {
|
||||||
|
Txn txn = db.startTransaction();
|
||||||
|
try {
|
||||||
|
db.setTransports(txn, c, b.getTransports());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
} catch(DbException e) {
|
||||||
|
db.abortTransaction(txn);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
transportLock.writeLock().unlock();
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
contactLock.readLock().unlock();
|
||||||
}
|
}
|
||||||
// Store the messages
|
// Store the messages
|
||||||
int batches = 0;
|
int batches = 0;
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import java.util.HashSet;
|
|||||||
import java.util.Iterator;
|
import java.util.Iterator;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import java.util.Map.Entry;
|
||||||
import java.util.logging.Level;
|
import java.util.logging.Level;
|
||||||
import java.util.logging.Logger;
|
import java.util.logging.Logger;
|
||||||
|
|
||||||
@@ -178,6 +179,27 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Add transport details
|
||||||
|
synchronized(contactLock) {
|
||||||
|
if(!containsContact(c)) throw new NoSuchContactException();
|
||||||
|
synchronized(transportLock) {
|
||||||
|
Txn txn = db.startTransaction();
|
||||||
|
try {
|
||||||
|
int numTransports = 0;
|
||||||
|
Map<String, String> transports = db.getTransports(txn);
|
||||||
|
for(Entry<String, String> e : transports.entrySet()) {
|
||||||
|
b.addTransport(e.getKey(), e.getValue());
|
||||||
|
numTransports++;
|
||||||
|
}
|
||||||
|
if(LOG.isLoggable(Level.FINE))
|
||||||
|
LOG.fine("Added " + numTransports + " transports");
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
} catch(DbException e) {
|
||||||
|
db.abortTransaction(txn);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Add as many messages as possible to the bundle
|
// Add as many messages as possible to the bundle
|
||||||
long capacity = b.getCapacity();
|
long capacity = b.getCapacity();
|
||||||
while(true) {
|
while(true) {
|
||||||
@@ -338,6 +360,20 @@ class SynchronizedDatabaseComponent<Txn> extends DatabaseComponentImpl<Txn> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
// Update the contact's transport details
|
||||||
|
synchronized(contactLock) {
|
||||||
|
if(!containsContact(c)) throw new NoSuchContactException();
|
||||||
|
synchronized(transportLock) {
|
||||||
|
Txn txn = db.startTransaction();
|
||||||
|
try {
|
||||||
|
db.setTransports(txn, c, b.getTransports());
|
||||||
|
db.commitTransaction(txn);
|
||||||
|
} catch(DbException e) {
|
||||||
|
db.abortTransaction(txn);
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
// Store the messages
|
// Store the messages
|
||||||
int batches = 0;
|
int batches = 0;
|
||||||
for(Batch batch : b.getBatches()) {
|
for(Batch batch : b.getBatches()) {
|
||||||
|
|||||||
@@ -511,6 +511,10 @@ public abstract class DatabaseComponentTest extends TestCase {
|
|||||||
oneOf(database).getSubscriptions(txn);
|
oneOf(database).getSubscriptions(txn);
|
||||||
will(returnValue(Collections.singleton(groupId)));
|
will(returnValue(Collections.singleton(groupId)));
|
||||||
oneOf(bundle).addSubscription(groupId);
|
oneOf(bundle).addSubscription(groupId);
|
||||||
|
// Add transports to the bundle
|
||||||
|
oneOf(database).getTransports(txn);
|
||||||
|
will(returnValue(Collections.singletonMap("foo", "bar")));
|
||||||
|
oneOf(bundle).addTransport("foo", "bar");
|
||||||
// Prepare to add batches to the bundle
|
// Prepare to add batches to the bundle
|
||||||
oneOf(bundle).getCapacity();
|
oneOf(bundle).getCapacity();
|
||||||
will(returnValue((long) ONE_MEGABYTE));
|
will(returnValue((long) ONE_MEGABYTE));
|
||||||
@@ -575,6 +579,8 @@ public abstract class DatabaseComponentTest extends TestCase {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testReceivedBundle() throws DbException {
|
public void testReceivedBundle() throws DbException {
|
||||||
|
final Map<String, String> transports =
|
||||||
|
Collections.singletonMap("foo", "bar");
|
||||||
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);
|
||||||
@@ -598,6 +604,10 @@ public abstract class DatabaseComponentTest extends TestCase {
|
|||||||
oneOf(bundle).getSubscriptions();
|
oneOf(bundle).getSubscriptions();
|
||||||
will(returnValue(Collections.singleton(groupId)));
|
will(returnValue(Collections.singleton(groupId)));
|
||||||
oneOf(database).addSubscription(txn, contactId, groupId);
|
oneOf(database).addSubscription(txn, contactId, groupId);
|
||||||
|
// Transports
|
||||||
|
oneOf(bundle).getTransports();
|
||||||
|
will(returnValue(transports));
|
||||||
|
oneOf(database).setTransports(txn, contactId, transports);
|
||||||
// Batches
|
// Batches
|
||||||
oneOf(bundle).getBatches();
|
oneOf(bundle).getBatches();
|
||||||
will(returnValue(Collections.singleton(batch)));
|
will(returnValue(Collections.singleton(batch)));
|
||||||
|
|||||||
@@ -776,6 +776,12 @@ public class H2DatabaseTest extends TestCase {
|
|||||||
// Remove the transport details
|
// Remove the transport details
|
||||||
db.setTransports(txn, contactId, null);
|
db.setTransports(txn, contactId, null);
|
||||||
assertEquals(Collections.emptyMap(), db.getTransports(txn, contactId));
|
assertEquals(Collections.emptyMap(), db.getTransports(txn, contactId));
|
||||||
|
// Set the local transport details
|
||||||
|
db.setTransports(txn, transports);
|
||||||
|
assertEquals(transports, db.getTransports(txn));
|
||||||
|
// Remove the local transport details
|
||||||
|
db.setTransports(txn, null);
|
||||||
|
assertEquals(Collections.emptyMap(), db.getTransports(txn));
|
||||||
db.commitTransaction(txn);
|
db.commitTransaction(txn);
|
||||||
|
|
||||||
db.close();
|
db.close();
|
||||||
|
|||||||
Reference in New Issue
Block a user