diff --git a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
index 5bd99e5371acc70c8f9b260bd2b5401271e394bf..80df25c16b6724a5c8bad31f6a352e2e1dcd7ccd 100644
--- a/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
+++ b/briar-api/src/net/sf/briar/api/db/DatabaseComponent.java
@@ -152,12 +152,21 @@ public interface DatabaseComponent {
 	/** Returns the local transport properties for the given transport. */
 	TransportProperties getLocalProperties(TransportId t) throws DbException;
 
+	/** Returns the body of the message with the given ID. */
+	byte[] getMessageBody(MessageId m) throws DbException;
+
+	/** Returns the header of the message with the given ID. */
+	MessageHeader getMessageHeader(MessageId m) throws DbException;
+
 	/** Returns the headers of all messages in the given group. */
 	Collection<MessageHeader> getMessageHeaders(GroupId g) throws DbException;
 
 	/** Returns the user's rating for the given author. */
 	Rating getRating(AuthorId a) throws DbException;
 
+	/** Returns true if the given message has been read. */
+	boolean getReadFlag(MessageId m) throws DbException;
+
 	/** Returns all remote transport properties for the given transport. */
 	Map<ContactId, TransportProperties> getRemoteProperties(TransportId t)
 			throws DbException;
@@ -165,6 +174,9 @@ public interface DatabaseComponent {
 	/** Returns all temporary secrets. */
 	Collection<TemporarySecret> getSecrets() throws DbException;
 
+	/** Returns true if the given message has been starred. */
+	boolean getStarredFlag(MessageId m) throws DbException;
+
 	/** Returns the set of groups to which the user subscribes. */
 	Collection<Group> getSubscriptions() throws DbException;
 
@@ -251,9 +263,21 @@ public interface DatabaseComponent {
 	/** Records the user's rating for the given author. */
 	void setRating(AuthorId a, Rating r) throws DbException;
 
+	/**
+	 * Marks the given message read or unread and returns true if it was
+	 * previously read.
+	 */
+	boolean setReadFlag(MessageId m, boolean read) throws DbException;
+
 	/** Records the given messages as having been seen by the given contact. */
 	void setSeen(ContactId c, Collection<MessageId> seen) throws DbException;
 
+	/**
+	 * Marks the given message starred or unstarred and returns true if it was
+	 * previously starred.
+	 */
+	boolean setStarredFlag(MessageId m, boolean starred) throws DbException;
+
 	/**
 	 * Makes the given group visible to the given set of contacts and invisible
 	 * to any other contacts.
diff --git a/briar-api/src/net/sf/briar/api/db/NoSuchMessageException.java b/briar-api/src/net/sf/briar/api/db/NoSuchMessageException.java
new file mode 100644
index 0000000000000000000000000000000000000000..ef24e15a7d4a68d4bba3a34bad94e48ec2ca0744
--- /dev/null
+++ b/briar-api/src/net/sf/briar/api/db/NoSuchMessageException.java
@@ -0,0 +1,11 @@
+package net.sf.briar.api.db;
+
+/**
+ * Thrown when a database operation is attempted for a message that is not in
+ * the database. This exception may occur due to concurrent updates and does
+ * not indicate a database error.
+ */
+public class NoSuchMessageException extends DbException {
+
+	private static final long serialVersionUID = 9191508339698803848L;
+}
diff --git a/briar-core/src/net/sf/briar/db/Database.java b/briar-core/src/net/sf/briar/db/Database.java
index ea1b8e65cb262d24232bab3deb91034b8c60941b..e0bd85aa003ed8171f2c29602b9e86de3e02ed23 100644
--- a/briar-core/src/net/sf/briar/db/Database.java
+++ b/briar-core/src/net/sf/briar/db/Database.java
@@ -234,18 +234,18 @@ interface Database<T> {
 			throws DbException;
 
 	/**
-	 * Returns the message identified by the given ID, in serialised form.
+	 * Returns the body of the message identified by the given ID.
 	 * <p>
 	 * Locking: message read.
 	 */
-	byte[] getMessage(T txn, MessageId m) throws DbException;
+	byte[] getMessageBody(T txn, MessageId m) throws DbException;
 
 	/**
-	 * Returns the body of the message identified by the given ID.
+	 * Returns the header of the message identified by the given ID.
 	 * <p>
 	 * Locking: message read.
 	 */
-	byte[] getMessageBody(T txn, MessageId m) throws DbException;
+	MessageHeader getMessageHeader(T txn, MessageId m) throws DbException;
 
 	/**
 	 * Returns the headers of all messages in the given group.
@@ -255,16 +255,6 @@ interface Database<T> {
 	Collection<MessageHeader> getMessageHeaders(T txn, GroupId g)
 			throws DbException;
 
-	/**
-	 * Returns the message identified by the given ID, in raw format, or null
-	 * if the message is not present in the database or is not sendable to the
-	 * given contact.
-	 * <p>
-	 * Locking: contact read, message read, subscription read.
-	 */
-	byte[] getMessageIfSendable(T txn, ContactId c, MessageId m)
-			throws DbException;
-
 	/**
 	 * Returns the IDs of all messages signed by the given author.
 	 * <p>
@@ -300,6 +290,23 @@ interface Database<T> {
 	 */
 	int getNumberOfSendableChildren(T txn, MessageId m) throws DbException;
 
+	/**
+	 * Returns the message identified by the given ID, in serialised form.
+	 * <p>
+	 * Locking: message read.
+	 */
+	byte[] getRawMessage(T txn, MessageId m) throws DbException;
+
+	/**
+	 * Returns the message identified by the given ID, in serialised form, or
+	 * null if the message is not present in the database or is not sendable to
+	 * the given contact.
+	 * <p>
+	 * Locking: contact read, message read, subscription read.
+	 */
+	byte[] getRawMessageIfSendable(T txn, ContactId c, MessageId m)
+			throws DbException;
+
 	/**
 	 * Returns the IDs of the oldest messages in the database, with a total
 	 * size less than or equal to the given size.
@@ -569,7 +576,7 @@ interface Database<T> {
 	 * <p>
 	 * Locking: message write.
 	 */
-	boolean setRead(T txn, MessageId m, boolean read) throws DbException;
+	boolean setReadFlag(T txn, MessageId m, boolean read) throws DbException;
 
 	/**
 	 * Updates the remote transport properties for the given contact and the
@@ -605,7 +612,8 @@ interface Database<T> {
 	 * <p>
 	 * Locking: message write.
 	 */
-	boolean setStarred(T txn, MessageId m, boolean starred) throws DbException;
+	boolean setStarredFlag(T txn, MessageId m, boolean starred)
+			throws DbException;
 
 	/**
 	 * Sets the status of the given message with respect to the given contact.
diff --git a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
index 251373efe79cfb9f28cdb5772a6ae6c62ad0d3d5..faa69037a2d464876f39db10aa7ba08fde30687b 100644
--- a/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
+++ b/briar-core/src/net/sf/briar/db/DatabaseComponentImpl.java
@@ -29,6 +29,7 @@ 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.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.event.ContactAddedEvent;
@@ -503,7 +504,7 @@ DatabaseCleaner.Callback {
 							throw new NoSuchContactException();
 						ids = db.getSendableMessages(txn, c, maxLength);
 						for(MessageId m : ids) {
-							messages.add(db.getMessage(txn, m));
+							messages.add(db.getRawMessage(txn, m));
 						}
 						db.commitTransaction(txn);
 					} catch(DbException e) {
@@ -555,7 +556,7 @@ DatabaseCleaner.Callback {
 						Iterator<MessageId> it = requested.iterator();
 						while(it.hasNext()) {
 							MessageId m = it.next();
-							byte[] raw = db.getMessageIfSendable(txn, c, m);
+							byte[] raw = db.getRawMessageIfSendable(txn, c, m);
 							if(raw != null) {
 								if(raw.length > maxLength) break;
 								messages.add(raw);
@@ -828,6 +829,44 @@ DatabaseCleaner.Callback {
 		}
 	}
 
+	public byte[] getMessageBody(MessageId m) throws DbException {
+		messageLock.readLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				if(!db.containsMessage(txn, m))
+					throw new NoSuchMessageException();
+				byte[] body = db.getMessageBody(txn, m);
+				db.commitTransaction(txn);
+				return body;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			messageLock.readLock().unlock();
+		}
+	}
+
+	public MessageHeader getMessageHeader(MessageId m) 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);
+				db.commitTransaction(txn);
+				return h;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			messageLock.readLock().unlock();
+		}
+	}
+
 	public Collection<MessageHeader> getMessageHeaders(GroupId g)
 			throws DbException {
 		messageLock.readLock().lock();
@@ -866,6 +905,25 @@ DatabaseCleaner.Callback {
 		}
 	}
 
+	public boolean getReadFlag(MessageId m) throws DbException {
+		messageLock.readLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				if(!db.containsMessage(txn, m))
+					throw new NoSuchMessageException();
+				boolean read = db.getReadFlag(txn, m);
+				db.commitTransaction(txn);
+				return read;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			messageLock.readLock().unlock();
+		}
+	}
+
 	public Map<ContactId, TransportProperties> getRemoteProperties(
 			TransportId t) throws DbException {
 		contactLock.readLock().lock();
@@ -918,6 +976,25 @@ DatabaseCleaner.Callback {
 		}
 	}
 
+	public boolean getStarredFlag(MessageId m) throws DbException {
+		messageLock.readLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				if(!db.containsMessage(txn, m))
+					throw new NoSuchMessageException();
+				boolean starred = db.getStarredFlag(txn, m);
+				db.commitTransaction(txn);
+				return starred;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			messageLock.readLock().unlock();
+		}
+	}
+
 	public Collection<Group> getSubscriptions() throws DbException {
 		subscriptionLock.readLock().lock();
 		try {
@@ -1494,6 +1571,25 @@ DatabaseCleaner.Callback {
 		if(changed) callListeners(new RatingChangedEvent(a, r));
 	}
 
+	public boolean setReadFlag(MessageId m, boolean read) throws DbException {
+		messageLock.writeLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				if(!db.containsMessage(txn, m))
+					throw new NoSuchMessageException();
+				boolean wasRead = db.setReadFlag(txn, m, read);
+				db.commitTransaction(txn);
+				return wasRead;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			messageLock.writeLock().unlock();
+		}
+	}
+
 	public void setSeen(ContactId c, Collection<MessageId> seen)
 			throws DbException {
 		contactLock.readLock().lock();
@@ -1506,9 +1602,8 @@ DatabaseCleaner.Callback {
 					try {
 						if(!db.containsContact(txn, c))
 							throw new NoSuchContactException();
-						for(MessageId m : seen) {
+						for(MessageId m : seen)
 							db.setStatusSeenIfVisible(txn, c, m);
-						}
 						db.commitTransaction(txn);
 					} catch(DbException e) {
 						db.abortTransaction(txn);
@@ -1550,6 +1645,26 @@ DatabaseCleaner.Callback {
 		}
 	}
 
+	public boolean setStarredFlag(MessageId m, boolean starred)
+			throws DbException {
+		messageLock.writeLock().lock();
+		try {
+			T txn = db.startTransaction();
+			try {
+				if(!db.containsMessage(txn, m))
+					throw new NoSuchMessageException();
+				boolean wasStarred = db.setStarredFlag(txn, m, starred);
+				db.commitTransaction(txn);
+				return wasStarred;
+			} catch(DbException e) {
+				db.abortTransaction(txn);
+				throw e;
+			}
+		} finally {
+			messageLock.writeLock().unlock();
+		}
+	}
+
 	public void setVisibility(GroupId g, Collection<ContactId> visible)
 			throws DbException {
 		Collection<ContactId> affected = new ArrayList<ContactId>();
diff --git a/briar-core/src/net/sf/briar/db/JdbcDatabase.java b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
index fced9675b2f80a2b072796344faf3bbd502223bb..897d7c4733c960c23c606c1a1427e73f0d3e73c1 100644
--- a/briar-core/src/net/sf/briar/db/JdbcDatabase.java
+++ b/briar-core/src/net/sf/briar/db/JdbcDatabase.java
@@ -1074,22 +1074,25 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public byte[] getMessage(Connection txn, MessageId m) throws DbException {
+	public byte[] getMessageBody(Connection txn, MessageId m)
+			throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT length, raw FROM messages WHERE messageId = ?";
+			String sql = "SELECT bodyStart, bodyLength, raw FROM messages"
+					+ " WHERE messageId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getBytes());
 			rs = ps.executeQuery();
 			if(!rs.next()) throw new DbStateException();
-			int length = rs.getInt(1);
-			byte[] raw = rs.getBlob(2).getBytes(1, length);
-			if(raw.length != length) throw new DbStateException();
+			int bodyStart = rs.getInt(1);
+			int bodyLength = rs.getInt(2);
+			// Bytes are indexed from 1 rather than 0
+			byte[] body = rs.getBlob(3).getBytes(bodyStart + 1, bodyLength);
 			if(rs.next()) throw new DbStateException();
 			rs.close();
 			ps.close();
-			return raw;
+			return body;
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -1097,25 +1100,33 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public byte[] getMessageBody(Connection txn, MessageId m)
+	public MessageHeader getMessageHeader(Connection txn, MessageId m)
 			throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT bodyStart, bodyLength, raw FROM messages"
+			String sql = "SELECT parentId, authorId, groupId, subject,"
+					+ " timestamp, read, starred"
+					+ " FROM messages"
 					+ " WHERE messageId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, m.getBytes());
 			rs = ps.executeQuery();
 			if(!rs.next()) throw new DbStateException();
-			int bodyStart = rs.getInt(1);
-			int bodyLength = rs.getInt(2);
-			// Bytes are indexed from 1 rather than 0
-			byte[] body = rs.getBlob(3).getBytes(bodyStart + 1, bodyLength);
+			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();
 			rs.close();
 			ps.close();
-			return body;
+			return new MessageHeaderImpl(m, parent, group, author, subject,
+					timestamp, read, starred);
 		} catch(SQLException e) {
 			tryToClose(rs);
 			tryToClose(ps);
@@ -1128,9 +1139,9 @@ abstract class JdbcDatabase implements Database<Connection> {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
 		try {
-			String sql = "SELECT m.messageId, parentId, authorId,"
-					+ " subject, timestamp, read, starred"
-					+ " FROM messages AS m"
+			String sql = "SELECT messageId, parentId, authorId, subject,"
+					+ " timestamp, read, starred"
+					+ " FROM messages"
 					+ " WHERE groupId = ?";
 			ps = txn.prepareStatement(sql);
 			ps.setBytes(1, g.getBytes());
@@ -1143,8 +1154,8 @@ abstract class JdbcDatabase implements Database<Connection> {
 				AuthorId author = new AuthorId(rs.getBytes(3));
 				String subject = rs.getString(4);
 				long timestamp = rs.getLong(5);
-				boolean read = rs.getBoolean(6); // False if absent
-				boolean starred = rs.getBoolean(7); // False if absent
+				boolean read = rs.getBoolean(6);
+				boolean starred = rs.getBoolean(7);
 				headers.add(new MessageHeaderImpl(id, parent, g, author,
 						subject, timestamp, read, starred));
 			}
@@ -1158,70 +1169,6 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public byte[] getMessageIfSendable(Connection txn, ContactId c, MessageId m)
-			throws DbException {
-		PreparedStatement ps = null;
-		ResultSet rs = null;
-		try {
-			// Do we have a sendable private message with the given ID?
-			String sql = "SELECT length, raw FROM messages AS m"
-					+ " JOIN statuses AS s"
-					+ " ON m.messageId = s.messageId"
-					+ " WHERE m.messageId = ? AND m.contactId = ?"
-					+ " AND status = ?";
-			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, m.getBytes());
-			ps.setInt(2, c.getInt());
-			ps.setShort(3, (short) Status.NEW.ordinal());
-			rs = ps.executeQuery();
-			byte[] raw = null;
-			if(rs.next()) {
-				int length = rs.getInt(1);
-				raw = rs.getBlob(2).getBytes(1, length);
-				if(raw.length != length) throw new DbStateException();
-			}
-			if(rs.next()) throw new DbStateException();
-			rs.close();
-			ps.close();
-			if(raw != null) return raw;
-			// Do we have a sendable group message with the given ID?
-			sql = "SELECT length, raw FROM messages AS m"
-					+ " JOIN contactGroups AS cg"
-					+ " ON m.groupId = cg.groupId"
-					+ " JOIN groupVisibilities AS gv"
-					+ " ON m.groupId = gv.groupId"
-					+ " AND cg.contactId = gv.contactId"
-					+ " JOIN retentionVersions AS rv"
-					+ " ON cg.contactId = rv.contactId"
-					+ " JOIN statuses AS s"
-					+ " ON m.messageId = s.messageId"
-					+ " AND cg.contactId = s.contactId"
-					+ " WHERE m.messageId = ?"
-					+ " AND cg.contactId = ?"
-					+ " AND timestamp >= retention"
-					+ " AND status = ?"
-					+ " AND sendability > ZERO()";
-			ps = txn.prepareStatement(sql);
-			ps.setBytes(1, m.getBytes());
-			ps.setInt(2, c.getInt());
-			ps.setShort(3, (short) Status.NEW.ordinal());
-			rs = ps.executeQuery();
-			if(rs.next()) {
-				int length = rs.getInt(1);
-				raw = rs.getBlob(2).getBytes(1, length);
-				if(raw.length != length) throw new DbStateException();
-			}
-			if(rs.next()) throw new DbStateException();
-			rs.close();
-			ps.close();
-			return raw;
-		} catch(SQLException e) {
-			tryToClose(rs);
-			tryToClose(ps);
-			throw new DbException(e);
-		}
-	}
-
 	public Collection<MessageId> getMessagesByAuthor(Connection txn, AuthorId a)
 			throws DbException {
 		PreparedStatement ps = null;
@@ -1406,6 +1353,94 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
+	public byte[] getRawMessage(Connection txn, MessageId m)
+			throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			String sql = "SELECT length, raw FROM messages WHERE messageId = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setBytes(1, m.getBytes());
+			rs = ps.executeQuery();
+			if(!rs.next()) throw new DbStateException();
+			int length = rs.getInt(1);
+			byte[] raw = rs.getBlob(2).getBytes(1, length);
+			if(raw.length != length) throw new DbStateException();
+			if(rs.next()) throw new DbStateException();
+			rs.close();
+			ps.close();
+			return raw;
+		} catch(SQLException e) {
+			tryToClose(rs);
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
+	public byte[] getRawMessageIfSendable(Connection txn, ContactId c,
+			MessageId m) throws DbException {
+		PreparedStatement ps = null;
+		ResultSet rs = null;
+		try {
+			// Do we have a sendable private message with the given ID?
+			String sql = "SELECT length, raw FROM messages AS m"
+					+ " JOIN statuses AS s"
+					+ " ON m.messageId = s.messageId"
+					+ " WHERE m.messageId = ? AND m.contactId = ?"
+					+ " AND status = ?";
+			ps = txn.prepareStatement(sql);
+			ps.setBytes(1, m.getBytes());
+			ps.setInt(2, c.getInt());
+			ps.setShort(3, (short) Status.NEW.ordinal());
+			rs = ps.executeQuery();
+			byte[] raw = null;
+			if(rs.next()) {
+				int length = rs.getInt(1);
+				raw = rs.getBlob(2).getBytes(1, length);
+				if(raw.length != length) throw new DbStateException();
+			}
+			if(rs.next()) throw new DbStateException();
+			rs.close();
+			ps.close();
+			if(raw != null) return raw;
+			// Do we have a sendable group message with the given ID?
+			sql = "SELECT length, raw FROM messages AS m"
+					+ " JOIN contactGroups AS cg"
+					+ " ON m.groupId = cg.groupId"
+					+ " JOIN groupVisibilities AS gv"
+					+ " ON m.groupId = gv.groupId"
+					+ " AND cg.contactId = gv.contactId"
+					+ " JOIN retentionVersions AS rv"
+					+ " ON cg.contactId = rv.contactId"
+					+ " JOIN statuses AS s"
+					+ " ON m.messageId = s.messageId"
+					+ " AND cg.contactId = s.contactId"
+					+ " WHERE m.messageId = ?"
+					+ " AND cg.contactId = ?"
+					+ " AND timestamp >= retention"
+					+ " AND status = ?"
+					+ " AND sendability > ZERO()";
+			ps = txn.prepareStatement(sql);
+			ps.setBytes(1, m.getBytes());
+			ps.setInt(2, c.getInt());
+			ps.setShort(3, (short) Status.NEW.ordinal());
+			rs = ps.executeQuery();
+			if(rs.next()) {
+				int length = rs.getInt(1);
+				raw = rs.getBlob(2).getBytes(1, length);
+				if(raw.length != length) throw new DbStateException();
+			}
+			if(rs.next()) throw new DbStateException();
+			rs.close();
+			ps.close();
+			return raw;
+		} catch(SQLException e) {
+			tryToClose(rs);
+			tryToClose(ps);
+			throw new DbException(e);
+		}
+	}
+
 	public boolean getReadFlag(Connection txn, MessageId m) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -2415,7 +2450,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public boolean setRead(Connection txn, MessageId m, boolean read)
+	public boolean setReadFlag(Connection txn, MessageId m, boolean read)
 			throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -2543,7 +2578,7 @@ abstract class JdbcDatabase implements Database<Connection> {
 		}
 	}
 
-	public boolean setStarred(Connection txn, MessageId m, boolean starred)
+	public boolean setStarredFlag(Connection txn, MessageId m, boolean starred)
 			throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
diff --git a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
index c7322c84ab37e77339d34d229d4af2e3c2a4f054..0286a9798f597ac1e96af5e957c764798705417c 100644
--- a/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
+++ b/briar-tests/src/net/sf/briar/db/DatabaseComponentTest.java
@@ -689,9 +689,9 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			// Get the sendable messages
 			oneOf(database).getSendableMessages(txn, contactId, size * 2);
 			will(returnValue(sendable));
-			oneOf(database).getMessage(txn, messageId);
+			oneOf(database).getRawMessage(txn, messageId);
 			will(returnValue(raw));
-			oneOf(database).getMessage(txn, messageId1);
+			oneOf(database).getRawMessage(txn, messageId1);
 			will(returnValue(raw1));
 			// Record the outstanding messages
 			oneOf(database).addOutstandingMessages(txn, contactId, sendable);
@@ -723,11 +723,11 @@ public abstract class DatabaseComponentTest extends BriarTestCase {
 			allowing(database).containsContact(txn, contactId);
 			will(returnValue(true));
 			// Try to get the requested messages
-			oneOf(database).getMessageIfSendable(txn, contactId, messageId);
+			oneOf(database).getRawMessageIfSendable(txn, contactId, messageId);
 			will(returnValue(null)); // Message is not sendable
-			oneOf(database).getMessageIfSendable(txn, contactId, messageId1);
+			oneOf(database).getRawMessageIfSendable(txn, contactId, messageId1);
 			will(returnValue(raw1)); // Message is sendable
-			oneOf(database).getMessageIfSendable(txn, contactId, messageId2);
+			oneOf(database).getRawMessageIfSendable(txn, contactId, messageId2);
 			will(returnValue(null)); // Message is not sendable
 			// Record the outstanding messages
 			oneOf(database).addOutstandingMessages(txn, contactId,
diff --git a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
index fa7b1ec7dcd0f0e486677e3e6021b6114ee99aff..05fa70c6fb34fa7bcc07b50b750ad7e7dd841704 100644
--- a/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
+++ b/briar-tests/src/net/sf/briar/db/H2DatabaseTest.java
@@ -111,10 +111,10 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertTrue(db.containsContact(txn, contactId));
 		assertTrue(db.containsSubscription(txn, groupId));
 		assertTrue(db.containsMessage(txn, messageId));
-		byte[] raw1 = db.getMessage(txn, messageId);
+		byte[] raw1 = db.getRawMessage(txn, messageId);
 		assertArrayEquals(raw, raw1);
 		assertTrue(db.containsMessage(txn, messageId1));
-		raw1 = db.getMessage(txn, messageId1);
+		raw1 = db.getRawMessage(txn, messageId1);
 		assertArrayEquals(raw, raw1);
 		// Delete the records
 		db.removeMessage(txn, messageId);
@@ -892,7 +892,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setSubscriptions(txn, contactId, Arrays.asList(group), 1L);
 
 		// The message is not in the database
-		assertNull(db.getMessageIfSendable(txn, contactId, messageId));
+		assertNull(db.getRawMessageIfSendable(txn, contactId, messageId));
 
 		db.commitTransaction(txn);
 		db.close();
@@ -914,7 +914,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setStatus(txn, contactId, messageId, Status.SEEN);
 
 		// The message is not sendable because its status is SEEN
-		assertNull(db.getMessageIfSendable(txn, contactId, messageId));
+		assertNull(db.getRawMessageIfSendable(txn, contactId, messageId));
 
 		db.commitTransaction(txn);
 		db.close();
@@ -937,7 +937,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setStatus(txn, contactId, messageId, Status.NEW);
 
 		// The message is not sendable because its sendability is 0
-		assertNull(db.getMessageIfSendable(txn, contactId, messageId));
+		assertNull(db.getRawMessageIfSendable(txn, contactId, messageId));
 
 		db.commitTransaction(txn);
 		db.close();
@@ -962,7 +962,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setStatus(txn, contactId, messageId, Status.NEW);
 
 		// The message is not sendable because it's too old
-		assertNull(db.getMessageIfSendable(txn, contactId, messageId));
+		assertNull(db.getRawMessageIfSendable(txn, contactId, messageId));
 
 		db.commitTransaction(txn);
 		db.close();
@@ -985,7 +985,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.setStatus(txn, contactId, messageId, Status.NEW);
 
 		// The message is sendable so it should be returned
-		byte[] b = db.getMessageIfSendable(txn, contactId, messageId);
+		byte[] b = db.getRawMessageIfSendable(txn, contactId, messageId);
 		assertArrayEquals(raw, b);
 
 		db.commitTransaction(txn);
@@ -1280,8 +1280,8 @@ public class H2DatabaseTest extends BriarTestCase {
 		System.arraycopy(raw, 10, expectedBody1, 0, bodyLength);
 
 		// Retrieve the raw messages
-		assertArrayEquals(raw, db.getMessage(txn, messageId));
-		assertArrayEquals(raw, db.getMessage(txn, messageId1));
+		assertArrayEquals(raw, db.getRawMessage(txn, messageId));
+		assertArrayEquals(raw, db.getRawMessage(txn, messageId1));
 
 		// Retrieve the message bodies
 		byte[] body = db.getMessageBody(txn, messageId);
@@ -1310,7 +1310,7 @@ public class H2DatabaseTest extends BriarTestCase {
 				authorId, subject, timestamp1, raw);
 		db.addGroupMessage(txn, message1);
 		// Mark one of the messages read
-		assertFalse(db.setRead(txn, messageId, true));
+		assertFalse(db.setReadFlag(txn, messageId, true));
 
 		// Retrieve the message headers
 		Collection<MessageHeader> headers = db.getMessageHeaders(txn, groupId);
@@ -1381,13 +1381,13 @@ public class H2DatabaseTest extends BriarTestCase {
 		// The message should be unread by default
 		assertFalse(db.getReadFlag(txn, messageId));
 		// Marking the message read should return the old value
-		assertFalse(db.setRead(txn, messageId, true));
-		assertTrue(db.setRead(txn, messageId, true));
+		assertFalse(db.setReadFlag(txn, messageId, true));
+		assertTrue(db.setReadFlag(txn, messageId, true));
 		// The message should be read
 		assertTrue(db.getReadFlag(txn, messageId));
 		// Marking the message unread should return the old value
-		assertTrue(db.setRead(txn, messageId, false));
-		assertFalse(db.setRead(txn, messageId, false));
+		assertTrue(db.setReadFlag(txn, messageId, false));
+		assertFalse(db.setReadFlag(txn, messageId, false));
 		// Unsubscribe from the group
 		db.removeSubscription(txn, groupId);
 
@@ -1407,13 +1407,13 @@ public class H2DatabaseTest extends BriarTestCase {
 		// The message should be unstarred by default
 		assertFalse(db.getStarredFlag(txn, messageId));
 		// Starring the message should return the old value
-		assertFalse(db.setStarred(txn, messageId, true));
-		assertTrue(db.setStarred(txn, messageId, true));
+		assertFalse(db.setStarredFlag(txn, messageId, true));
+		assertTrue(db.setStarredFlag(txn, messageId, true));
 		// The message should be starred
 		assertTrue(db.getStarredFlag(txn, messageId));
 		// Unstarring the message should return the old value
-		assertTrue(db.setStarred(txn, messageId, false));
-		assertFalse(db.setStarred(txn, messageId, false));
+		assertTrue(db.setStarredFlag(txn, messageId, false));
+		assertFalse(db.setStarredFlag(txn, messageId, false));
 		// Unsubscribe from the group
 		db.removeSubscription(txn, groupId);
 
@@ -1446,7 +1446,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		db.addGroupMessage(txn, message2);
 
 		// Mark one of the messages in the first group read
-		assertFalse(db.setRead(txn, messageId, true));
+		assertFalse(db.setReadFlag(txn, messageId, true));
 
 		// There should be one unread message in each group
 		Map<GroupId, Integer> counts = db.getUnreadMessageCounts(txn);
@@ -1459,10 +1459,10 @@ public class H2DatabaseTest extends BriarTestCase {
 		assertEquals(1, count.intValue());
 
 		// Mark the read message unread (it will now be false rather than null)
-		assertTrue(db.setRead(txn, messageId, false));
+		assertTrue(db.setReadFlag(txn, messageId, false));
 
 		// Mark the message in the second group read
-		assertFalse(db.setRead(txn, messageId2, true));
+		assertFalse(db.setReadFlag(txn, messageId2, true));
 
 		// There should be two unread messages in the first group, none in
 		// the second group
@@ -1812,7 +1812,7 @@ public class H2DatabaseTest extends BriarTestCase {
 		Connection txn = db.startTransaction();
 		try {
 			// Ask for a nonexistent message - an exception should be thrown
-			db.getMessage(txn, messageId);
+			db.getRawMessage(txn, messageId);
 			fail();
 		} catch(DbException expected) {
 			// It should be possible to abort the transaction without error