Keep a linked list of the IDs of subscribed groups.

This commit is contained in:
akwizgran
2012-05-17 21:25:10 +02:00
parent 78e1513aae
commit 2f4b347cdf
3 changed files with 152 additions and 0 deletions

View File

@@ -42,6 +42,7 @@ import net.sf.briar.api.transport.ConnectionContext;
import net.sf.briar.api.transport.ConnectionContextFactory;
import net.sf.briar.api.transport.ConnectionWindow;
import net.sf.briar.api.transport.ConnectionWindowFactory;
import net.sf.briar.util.ByteUtils;
import net.sf.briar.util.FileUtils;
/**
@@ -58,6 +59,12 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " start BIGINT NOT NULL,"
+ " PRIMARY KEY (groupId))";
private static final String CREATE_SUBSCRIPTION_IDS =
"CREATE TABLE subscriptionIds"
+ " (groupId HASH," // Null for the head of the list
+ " nextId HASH," // Null for the tail of the list
+ " deleted BIGINT NOT NULL)";
private static final String CREATE_CONTACTS =
"CREATE TABLE contacts"
+ " (contactId COUNTER,"
@@ -326,6 +333,7 @@ abstract class JdbcDatabase implements Database<Connection> {
try {
s = txn.createStatement();
s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTIONS));
s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTION_IDS));
s.executeUpdate(insertTypeNames(CREATE_CONTACTS));
s.executeUpdate(insertTypeNames(CREATE_MESSAGES));
s.executeUpdate(INDEX_MESSAGES_BY_PARENT);
@@ -721,7 +729,9 @@ abstract class JdbcDatabase implements Database<Connection> {
public void addSubscription(Connection txn, Group g) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Add the group to the subscriptions table
String sql = "INSERT INTO subscriptions"
+ " (groupId, groupName, groupKey, start)"
+ " VALUES (?, ?, ?, ?)";
@@ -733,7 +743,78 @@ abstract class JdbcDatabase implements Database<Connection> {
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
// Insert the group ID into the linked list
byte[] id = g.getId().getBytes();
sql = "SELECT groupId, nextId, deleted FROM subscriptionIds"
+ " ORDER BY groupId";
ps = txn.prepareStatement(sql);
rs = ps.executeQuery();
if(rs.next()) {
// The head pointer of the list exists
byte[] groupId = rs.getBytes(1);
if(groupId != null) throw new DbStateException();
byte[] nextId = rs.getBytes(2);
long deleted = rs.getLong(3);
// Scan through the list to find the insertion point
while(nextId != null && ByteUtils.compare(id, nextId) > 0) {
if(!rs.next()) throw new DbStateException();
groupId = rs.getBytes(1);
if(groupId == null) throw new DbStateException();
nextId = rs.getBytes(2);
deleted = rs.getLong(3);
}
rs.close();
ps.close();
// Update the previous element
if(groupId == null) {
// Inserting at the head of the list
sql = "UPDATE subscriptionIds SET nextId = ?"
+ " WHERE groupId IS NULL";
ps = txn.prepareStatement(sql);
ps.setBytes(1, id);
} else {
// Inserting in the middle or at the tail of the list
sql = "UPDATE subscriptionIds SET nextId = ?"
+ " WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, id);
ps.setBytes(2, groupId);
}
affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
// Insert the new element
sql = "INSERT INTO subscriptionIds (groupId, nextId, deleted)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, id);
if(nextId == null) ps.setNull(2, Types.BINARY); // At the tail
else ps.setBytes(2, nextId); // In the middle
ps.setLong(3, deleted);
affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
} else {
// The head pointer of the list does not exist
rs.close();
ps.close();
sql = "INSERT INTO subscriptionIds (nextId, deleted)"
+ " VALUES (?, ZERO())";
ps = txn.prepareStatement(sql);
ps.setBytes(1, id);
affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
sql = "INSERT INTO subscriptionIds (groupId, deleted)"
+ " VALUES (?, ZERO())";
ps = txn.prepareStatement(sql);
ps.setBytes(1, id);
affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
}
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
@@ -2096,14 +2177,43 @@ abstract class JdbcDatabase implements Database<Connection> {
public void removeSubscription(Connection txn, GroupId g)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
// Remove the group from the subscriptions table
String sql = "DELETE FROM subscriptions WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
// Remove the group ID from the linked list
sql = "SELECT nextId FROM subscriptionIds WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
rs = ps.executeQuery();
if(!rs.next()) throw new DbStateException();
byte[] nextId = rs.getBytes(1);
if(rs.next()) throw new DbStateException();
rs.close();
ps.close();
sql = "DELETE FROM subscriptionIds WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
sql = "UPDATE subscriptionIds SET nextId = ?, deleted = ?"
+ " WHERE nextId = ?";
ps = txn.prepareStatement(sql);
if(nextId == null) ps.setNull(1, Types.BINARY); // At the tail
else ps.setBytes(1, nextId); // At the head or in the middle
ps.setLong(2, System.currentTimeMillis());
ps.setBytes(3, g.getBytes());
affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}

View File

@@ -10,6 +10,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
@@ -1882,6 +1883,29 @@ public class H2DatabaseTest extends BriarTestCase {
db.close();
}
@Test
public void testMultipleSubscriptionsAndUnsubscriptions() throws Exception {
// Create some groups
List<Group> groups = new ArrayList<Group>();
for(int i = 0; i < 100; i++) {
GroupId id = new GroupId(TestUtils.getRandomId());
groups.add(groupFactory.createGroup(id, "Group name", null));
}
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add the groups to the database
for(Group g : groups) db.addSubscription(txn, g);
// Remove the groups in a different order
Collections.shuffle(groups);
for(Group g : groups) db.removeSubscription(txn, g.getId());
db.commitTransaction(txn);
db.close();
}
@Test
public void testExceptionHandling() throws Exception {
Database<Connection> db = open(false);

View File

@@ -62,4 +62,22 @@ public class ByteUtils {
assert result < 1 << bits;
return result;
}
/**
* Compares two byte arrays and returns -1, 0, or +1 if the first array is
* less than, equal to, or greater than the second array, respectively.
* <p>
* If one of the arrays is a prefix of the other, the longer array is
* considered to be greater. Bytes are treated as unsigned.
*/
public static int compare(byte[] b1, byte[] b2) {
for(int i = 0; i < b1.length || i < b2.length; i++) {
if(i == b1.length) return -1;
if(i == b2.length) return 1;
int b1i = b1[i] & 0xff, b2i = b2[i] & 0xff;
if(b1i < b2i) return -1;
if(b1i > b2i) return 1;
}
return 0;
}
}