Added columns to the DB to support retrieval of message headers.

This commit is contained in:
akwizgran
2013-02-28 12:49:48 +00:00
parent 88aea1bb72
commit bfd4ee5e9f
17 changed files with 296 additions and 229 deletions

View File

@@ -10,7 +10,8 @@ import net.sf.briar.api.Rating;
import net.sf.briar.api.TransportConfig;
import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.MessageHeader;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
@@ -263,19 +264,20 @@ interface Database<T> {
*/
byte[] getMessageBody(T txn, MessageId m) throws DbException;
/**
* Returns the header of the message identified by the given ID.
* <p>
* Locking: message read.
*/
MessageHeader getMessageHeader(T txn, MessageId m) throws DbException;
/**
* Returns the headers of all messages in the given group.
* <p>
* Locking: message read.
*/
Collection<MessageHeader> getMessageHeaders(T txn, GroupId g)
Collection<GroupMessageHeader> getMessageHeaders(T txn, GroupId g)
throws DbException;
/**
* Returns the headers of all private messages.
* <p>
* Locking: message read.
*/
Collection<PrivateMessageHeader> getPrivateMessageHeaders(T txn)
throws DbException;
/**

View File

@@ -30,19 +30,20 @@ import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.db.DatabaseComponent;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.MessageHeader;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.db.NoSuchContactException;
import net.sf.briar.api.db.NoSuchMessageException;
import net.sf.briar.api.db.NoSuchSubscriptionException;
import net.sf.briar.api.db.NoSuchTransportException;
import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.db.event.ContactAddedEvent;
import net.sf.briar.api.db.event.ContactRemovedEvent;
import net.sf.briar.api.db.event.DatabaseEvent;
import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.db.event.LocalRetentionTimeUpdatedEvent;
import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
import net.sf.briar.api.db.event.MessageAddedEvent;
import net.sf.briar.api.db.event.MessageExpiredEvent;
import net.sf.briar.api.db.event.MessageReceivedEvent;
import net.sf.briar.api.db.event.RatingChangedEvent;
import net.sf.briar.api.db.event.RemoteRetentionTimeUpdatedEvent;
@@ -52,6 +53,7 @@ import net.sf.briar.api.db.event.TransportAddedEvent;
import net.sf.briar.api.db.event.TransportRemovedEvent;
import net.sf.briar.api.lifecycle.ShutdownManager;
import net.sf.briar.api.messaging.Ack;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
@@ -258,7 +260,7 @@ DatabaseCleaner.Callback {
try {
// Don't store the message if the user has
// unsubscribed from the group
if(db.containsSubscription(txn, m.getGroup()))
if(db.containsSubscription(txn, m.getGroup().getId()))
added = storeGroupMessage(txn, m, null);
db.commitTransaction(txn);
} catch(DbException e) {
@@ -318,8 +320,8 @@ DatabaseCleaner.Callback {
private int calculateSendability(T txn, Message m) throws DbException {
int sendability = 0;
// One point for a good rating
AuthorId a = m.getAuthor();
if(a != null && db.getRating(txn, a) == GOOD) sendability++;
Author a = m.getAuthor();
if(a != null && db.getRating(txn, a.getId()) == GOOD) sendability++;
// One point per sendable child (backward inclusion)
sendability += db.getNumberOfSendableChildren(txn, m.getId());
return sendability;
@@ -445,10 +447,10 @@ DatabaseCleaner.Callback {
}
/**
* If the given message is already in the database, returns false.
* Otherwise stores the message and marks it as new or seen with respect to
* the given contact, depending on whether the message is outgoing or
* incoming, respectively; or returns false if the message is already in
* the database.
* incoming, respectively.
* <p>
* Locking: contact read, message write.
*/
@@ -457,9 +459,7 @@ DatabaseCleaner.Callback {
if(m.getGroup() != null) throw new IllegalArgumentException();
if(m.getAuthor() != null) throw new IllegalArgumentException();
if(!db.addPrivateMessage(txn, m, c)) return false;
MessageId id = m.getId();
if(incoming) db.addStatus(txn, c, id, true);
else db.addStatus(txn, c, id, false);
db.addStatus(txn, c, m.getId(), incoming);
// Count the bytes stored
synchronized(spaceLock) {
bytesStoredSinceLastCheck += m.getSerialised().length;
@@ -878,16 +878,18 @@ DatabaseCleaner.Callback {
}
}
public MessageHeader getMessageHeader(MessageId m) throws DbException {
public Collection<GroupMessageHeader> getMessageHeaders(GroupId g)
throws DbException {
messageLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsMessage(txn, m))
throw new NoSuchMessageException();
MessageHeader h = db.getMessageHeader(txn, m);
if(!db.containsSubscription(txn, g))
throw new NoSuchSubscriptionException();
Collection<GroupMessageHeader> headers =
db.getMessageHeaders(txn, g);
db.commitTransaction(txn);
return h;
return headers;
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
@@ -897,16 +899,14 @@ DatabaseCleaner.Callback {
}
}
public Collection<MessageHeader> getMessageHeaders(GroupId g)
public Collection<PrivateMessageHeader> getPrivateMessageHeaders()
throws DbException {
messageLock.readLock().lock();
try {
T txn = db.startTransaction();
try {
if(!db.containsSubscription(txn, g))
throw new NoSuchSubscriptionException();
Collection<MessageHeader> headers =
db.getMessageHeaders(txn, g);
Collection<PrivateMessageHeader> headers =
db.getPrivateMessageHeaders(txn);
db.commitTransaction(txn);
return headers;
} catch(DbException e) {
@@ -1285,9 +1285,9 @@ DatabaseCleaner.Callback {
private boolean storeMessage(T txn, ContactId c, Message m)
throws DbException {
if(m.getTimestamp() > clock.currentTimeMillis()) return false;
GroupId g = m.getGroup();
Group g = m.getGroup();
if(g == null) return storePrivateMessage(txn, m, c, true);
if(!db.containsVisibleSubscription(txn, c, g)) return false;
if(!db.containsVisibleSubscription(txn, c, g.getId())) return false;
return storeGroupMessage(txn, m, c);
}
@@ -1846,7 +1846,7 @@ DatabaseCleaner.Callback {
} finally {
contactLock.readLock().unlock();
}
if(removed) callListeners(new LocalRetentionTimeUpdatedEvent());
if(removed) callListeners(new MessageExpiredEvent());
return removed;
}

View File

@@ -1,6 +1,7 @@
package net.sf.briar.db;
import static java.sql.Types.BINARY;
import static java.sql.Types.VARCHAR;
import static java.util.logging.Level.INFO;
import static java.util.logging.Level.WARNING;
import static net.sf.briar.api.Rating.UNRATED;
@@ -34,7 +35,9 @@ import net.sf.briar.api.TransportProperties;
import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.db.DbClosedException;
import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.MessageHeader;
import net.sf.briar.api.db.GroupMessageHeader;
import net.sf.briar.api.db.PrivateMessageHeader;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
@@ -117,7 +120,9 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " (messageId HASH NOT NULL,"
+ " parentId HASH," // Null for the first msg in a thread
+ " groupId HASH," // Null for private messages
+ " authorId HASH," // Null for private or anonymous msgs
+ " authorId HASH," // Null for private/anon messages
+ " authorName VARCHAR," // Null for private/anon messages
+ " authorKey VARCHAR," // Null for private/anon messages
+ " subject VARCHAR NOT NULL,"
+ " timestamp BIGINT NOT NULL,"
+ " length INT NOT NULL,"
@@ -620,29 +625,38 @@ abstract class JdbcDatabase implements Database<Connection> {
public boolean addGroupMessage(Connection txn, Message m)
throws DbException {
assert m.getGroup() != null;
if(m.getGroup() == null) throw new IllegalArgumentException();
if(containsMessage(txn, m.getId())) return false;
PreparedStatement ps = null;
try {
String sql = "INSERT INTO messages (messageId, parentId, groupId,"
+ " authorId, subject, timestamp, length, bodyStart,"
+ " bodyLength, raw, sendability, read, starred)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ZERO(), FALSE,"
+ " FALSE)";
+ " authorId, authorName, authorKey, subject, timestamp,"
+ " length, bodyStart, bodyLength, raw, sendability, read,"
+ " starred)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ZERO(),"
+ " FALSE, FALSE)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getId().getBytes());
if(m.getParent() == null) ps.setNull(2, BINARY);
else ps.setBytes(2, m.getParent().getBytes());
ps.setBytes(3, m.getGroup().getBytes());
if(m.getAuthor() == null) ps.setNull(4, BINARY);
else ps.setBytes(4, m.getAuthor().getBytes());
ps.setString(5, m.getSubject());
ps.setLong(6, m.getTimestamp());
ps.setBytes(3, m.getGroup().getId().getBytes());
Author a = m.getAuthor();
if(a == null) {
ps.setNull(4, BINARY);
ps.setNull(5, VARCHAR);
ps.setNull(6, BINARY);
} else {
ps.setBytes(4, a.getId().getBytes());
ps.setString(5, a.getName());
ps.setBytes(6, a.getPublicKey());
}
ps.setString(7, m.getSubject());
ps.setLong(8, m.getTimestamp());
byte[] raw = m.getSerialised();
ps.setInt(7, raw.length);
ps.setInt(8, m.getBodyStart());
ps.setInt(9, m.getBodyLength());
ps.setBytes(10, raw);
ps.setInt(9, raw.length);
ps.setInt(10, m.getBodyStart());
ps.setInt(11, m.getBodyLength());
ps.setBytes(12, raw);
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
@@ -686,13 +700,13 @@ abstract class JdbcDatabase implements Database<Connection> {
public boolean addPrivateMessage(Connection txn, Message m, ContactId c)
throws DbException {
assert m.getGroup() == null;
if(m.getGroup() != null) throw new IllegalArgumentException();
if(containsMessage(txn, m.getId())) return false;
PreparedStatement ps = null;
try {
String sql = "INSERT INTO messages"
+ " (messageId, parentId, subject, timestamp, length,"
+ " bodyStart, bodyLength, raw, contactId, read, starred)"
String sql = "INSERT INTO messages (messageId, parentId, subject,"
+ " timestamp, length, bodyStart, bodyLength, raw,"
+ " contactId, read, starred)"
+ " VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, FALSE, FALSE)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getId().getBytes());
@@ -1211,33 +1225,42 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public MessageHeader getMessageHeader(Connection txn, MessageId m)
throws DbException {
public Collection<GroupMessageHeader> getMessageHeaders(Connection txn,
GroupId g) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT parentId, authorId, groupId, subject,"
+ " timestamp, read, starred"
String sql = "SELECT messageId, parentId, authorId, authorName,"
+ " authorKey, subject, timestamp, read, starred"
+ " FROM messages"
+ " WHERE messageId = ?";
+ " WHERE groupId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
ps.setBytes(1, g.getBytes());
rs = ps.executeQuery();
if(!rs.next()) throw new DbStateException();
byte[] b = rs.getBytes(1);
MessageId parent = b == null ? null : new MessageId(b);
AuthorId author = new AuthorId(rs.getBytes(2));
b = rs.getBytes(3);
GroupId group = b == null ? null : new GroupId(b);
String subject = rs.getString(4);
long timestamp = rs.getLong(5);
boolean read = rs.getBoolean(6);
boolean starred = rs.getBoolean(7);
if(rs.next()) throw new DbStateException();
List<GroupMessageHeader> headers =
new ArrayList<GroupMessageHeader>();
while(rs.next()) {
MessageId id = new MessageId(rs.getBytes(1));
byte[] b = rs.getBytes(2);
MessageId parent = b == null ? null : new MessageId(b);
Author author = null;
b = rs.getBytes(3);
if(b != null) {
AuthorId authorId = new AuthorId(b);
String authorName = rs.getString(4);
byte[] authorKey = rs.getBytes(5);
author = new Author(authorId, authorName, authorKey);
}
String subject = rs.getString(6);
long timestamp = rs.getLong(7);
boolean read = rs.getBoolean(8);
boolean starred = rs.getBoolean(9);
headers.add(new GroupMessageHeader(id, parent, subject,
timestamp, read, starred, g, author));
}
rs.close();
ps.close();
return new MessageHeader(m, parent, group, author, subject,
timestamp, read, starred);
return Collections.unmodifiableList(headers);
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
@@ -1245,30 +1268,33 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public Collection<MessageHeader> getMessageHeaders(Connection txn,
GroupId g) throws DbException {
public Collection<PrivateMessageHeader> getPrivateMessageHeaders(
Connection txn) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT messageId, parentId, authorId, subject,"
+ " timestamp, read, starred"
+ " FROM messages"
+ " WHERE groupId = ?";
String sql = "SELECT m.messageId, parentId, subject, timestamp,"
+ " contactId, read, starred, seen"
+ " FROM messages AS m"
+ " JOIN statuses AS s"
+ " ON m.messageId = s.messageId"
+ " WHERE groupId IS NULL";
ps = txn.prepareStatement(sql);
ps.setBytes(1, g.getBytes());
rs = ps.executeQuery();
List<MessageHeader> headers = new ArrayList<MessageHeader>();
List<PrivateMessageHeader> headers =
new ArrayList<PrivateMessageHeader>();
while(rs.next()) {
MessageId id = new MessageId(rs.getBytes(1));
byte[] p = rs.getBytes(2);
MessageId parent = p == null ? null : new MessageId(p);
AuthorId author = new AuthorId(rs.getBytes(3));
String subject = rs.getString(4);
long timestamp = rs.getLong(5);
byte[] b = rs.getBytes(2);
MessageId parent = b == null ? null : new MessageId(b);
String subject = rs.getString(3);
long timestamp = rs.getLong(4);
ContactId contactId = new ContactId(rs.getInt(5));
boolean read = rs.getBoolean(6);
boolean starred = rs.getBoolean(7);
headers.add(new MessageHeader(id, parent, g, author, subject,
timestamp, read, starred));
boolean seen = rs.getBoolean(8);
headers.add(new PrivateMessageHeader(id, parent, subject,
timestamp, read, starred, contactId, !seen));
}
rs.close();
ps.close();

View File

@@ -20,9 +20,7 @@ import net.sf.briar.api.clock.Clock;
import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageFactory;
import net.sf.briar.api.messaging.MessageId;
@@ -150,12 +148,9 @@ class MessageFactoryImpl implements MessageFactory {
}
// Hash the message, including the signatures, to get the message ID
w.removeConsumer(digestingConsumer);
byte[] raw = out.toByteArray();
MessageId id = new MessageId(messageDigest.digest());
GroupId groupId = group == null ? null : group.getId();
AuthorId authorId = author == null ? null : author.getId();
return new MessageImpl(id, parent, groupId, authorId, subject,
timestamp, raw, bodyStart, body.length);
return new MessageImpl(id, parent, group, author, subject,
timestamp, out.toByteArray(), bodyStart, body.length);
}
private void writeGroup(Writer w, Group g) throws IOException {

View File

@@ -1,8 +1,8 @@
package net.sf.briar.messaging;
import static net.sf.briar.api.messaging.MessagingConstants.MAX_BODY_LENGTH;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;
@@ -10,15 +10,15 @@ import net.sf.briar.api.messaging.MessageId;
class MessageImpl implements Message {
private final MessageId id, parent;
private final GroupId group;
private final AuthorId author;
private final Group group;
private final Author author;
private final String subject;
private final long timestamp;
private final byte[] raw;
private final int bodyStart, bodyLength;
public MessageImpl(MessageId id, MessageId parent, GroupId group,
AuthorId author, String subject, long timestamp, byte[] raw,
public MessageImpl(MessageId id, MessageId parent, Group group,
Author author, String subject, long timestamp, byte[] raw,
int bodyStart, int bodyLength) {
if(bodyStart + bodyLength > raw.length)
throw new IllegalArgumentException();
@@ -43,11 +43,11 @@ class MessageImpl implements Message {
return parent;
}
public GroupId getGroup() {
public Group getGroup() {
return group;
}
public AuthorId getAuthor() {
public Author getAuthor() {
return author;
}

View File

@@ -8,9 +8,7 @@ import net.sf.briar.api.crypto.CryptoComponent;
import net.sf.briar.api.crypto.KeyParser;
import net.sf.briar.api.crypto.MessageDigest;
import net.sf.briar.api.messaging.Author;
import net.sf.briar.api.messaging.AuthorId;
import net.sf.briar.api.messaging.Group;
import net.sf.briar.api.messaging.GroupId;
import net.sf.briar.api.messaging.Message;
import net.sf.briar.api.messaging.MessageId;
import net.sf.briar.api.messaging.MessageVerifier;
@@ -55,10 +53,7 @@ class MessageVerifierImpl implements MessageVerifier {
if(!signature.verify(m.getGroupSignature()))
throw new GeneralSecurityException();
}
GroupId groupId = group == null ? null : group.getId();
AuthorId authorId = author == null ? null : author.getId();
return new MessageImpl(id, m.getParent(), groupId, authorId,
m.getSubject(), m.getTimestamp(), raw, m.getBodyStart(),
m.getBodyLength());
return new MessageImpl(id, m.getParent(), group, author, m.getSubject(),
m.getTimestamp(), raw, m.getBodyStart(), m.getBodyLength());
}
}

View File

@@ -29,7 +29,7 @@ import net.sf.briar.api.db.DbException;
import net.sf.briar.api.db.event.ContactRemovedEvent;
import net.sf.briar.api.db.event.DatabaseEvent;
import net.sf.briar.api.db.event.DatabaseListener;
import net.sf.briar.api.db.event.LocalRetentionTimeUpdatedEvent;
import net.sf.briar.api.db.event.MessageExpiredEvent;
import net.sf.briar.api.db.event.LocalSubscriptionsUpdatedEvent;
import net.sf.briar.api.db.event.LocalTransportsUpdatedEvent;
import net.sf.briar.api.db.event.MessageAddedEvent;
@@ -134,7 +134,7 @@ abstract class DuplexConnection implements DatabaseListener {
if(e instanceof ContactRemovedEvent) {
ContactRemovedEvent c = (ContactRemovedEvent) e;
if(contactId.equals(c.getContactId())) dispose(false, true);
} else if(e instanceof LocalRetentionTimeUpdatedEvent) {
} else if(e instanceof MessageExpiredEvent) {
dbExecutor.execute(new GenerateRetentionUpdate());
} else if(e instanceof LocalSubscriptionsUpdatedEvent) {
LocalSubscriptionsUpdatedEvent l =