Added message flags: read/unread and starred/unstarred.

This commit is contained in:
akwizgran
2011-10-26 16:32:30 +01:00
parent b70b579fd6
commit 0f6b0e88c1
4 changed files with 304 additions and 47 deletions

View File

@@ -32,6 +32,7 @@ import net.sf.briar.api.transport.ConnectionWindow;
* <ul>
* <li> contact
* <li> message
* <li> messageFlag
* <li> messageStatus
* <li> rating
* <li> subscription
@@ -304,6 +305,13 @@ interface Database<T> {
*/
Rating getRating(T txn, AuthorId a) throws DbException;
/**
* Returns true if the given message has been read.
* <p>
* Locking: message read, messageFlag read.
*/
boolean getRead(T txn, MessageId m) throws DbException;
/**
* Returns all remote properties for the given transport.
* <p>
@@ -346,6 +354,13 @@ interface Database<T> {
*/
byte[] getSharedSecret(T txn, ContactId c) throws DbException;
/**
* Returns true if the given message has been starred.
* <p>
* Locking: message read, messageFlag read.
*/
boolean getStarred(T txn, MessageId m) throws DbException;
/**
* Returns the groups to which the user subscribes.
* <p>
@@ -433,8 +448,8 @@ interface Database<T> {
/**
* Removes a contact (and all associated state) from the database.
* <p>
* Locking: contact write, message write, messageStatus write,
* subscription write, transport write.
* Locking: contact write, message write, messageFlag write,
* messageStatus write, subscription write, transport write.
*/
void removeContact(T txn, ContactId c) throws DbException;
@@ -450,7 +465,8 @@ interface Database<T> {
/**
* Removes a message (and all associated state) from the database.
* <p>
* Locking: contact read, message write, messageStatus write.
* Locking: contact read, message write, messageFlag write,
* messageStatus write.
*/
void removeMessage(T txn, MessageId m) throws DbException;
@@ -458,8 +474,8 @@ interface Database<T> {
* Unsubscribes from the given group. Any messages belonging to the group
* are deleted from the database.
* <p>
* Locking: contact read, message write, messageStatus write,
* subscription write.
* Locking: contact read, message write, messageFlag write,
* messageStatus write, subscription write.
*/
void removeSubscription(T txn, GroupId g) throws DbException;
@@ -497,6 +513,14 @@ interface Database<T> {
*/
Rating setRating(T txn, AuthorId a, Rating r) throws DbException;
/**
* Marks the given message read or unread and returns true if it was
* previously read.
* <p>
* Locking: message read, messageFlag write.
*/
boolean setRead(T txn, MessageId m, boolean read) throws DbException;
/**
* Sets the sendability score of the given message.
* <p>
@@ -504,6 +528,14 @@ interface Database<T> {
*/
void setSendability(T txn, MessageId m, int sendability) throws DbException;
/**
* Marks the given message starred or unstarred and returns true if it was
* previously starred.
* <p>
* Locking: message read, messageFlag write.
*/
boolean setStarred(T txn, MessageId m, boolean starred) throws DbException;
/**
* Sets the status of the given message with respect to the given contact.
* <p>

View File

@@ -81,6 +81,8 @@ DatabaseCleaner.Callback {
new ReentrantReadWriteLock(true);
private final ReentrantReadWriteLock messageLock =
new ReentrantReadWriteLock(true);
private final ReentrantReadWriteLock messageFlagLock =
new ReentrantReadWriteLock(true);
private final ReentrantReadWriteLock messageStatusLock =
new ReentrantReadWriteLock(true);
private final ReentrantReadWriteLock ratingLock =
@@ -1137,28 +1139,33 @@ DatabaseCleaner.Callback {
try {
messageLock.writeLock().lock();
try {
messageStatusLock.writeLock().lock();
messageFlagLock.writeLock().lock();
try {
subscriptionLock.writeLock().lock();
messageStatusLock.writeLock().lock();
try {
transportLock.writeLock().lock();
subscriptionLock.writeLock().lock();
try {
T txn = db.startTransaction();
transportLock.writeLock().lock();
try {
db.removeContact(txn, c);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
T txn = db.startTransaction();
try {
db.removeContact(txn, c);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
transportLock.writeLock().unlock();
}
} finally {
transportLock.writeLock().unlock();
subscriptionLock.writeLock().unlock();
}
} finally {
subscriptionLock.writeLock().unlock();
messageStatusLock.writeLock().unlock();
}
} finally {
messageStatusLock.writeLock().unlock();
messageFlagLock.writeLock().unlock();
}
} finally {
messageLock.writeLock().unlock();
@@ -1399,26 +1406,31 @@ DatabaseCleaner.Callback {
try {
messageLock.writeLock().lock();
try {
messageStatusLock.writeLock().lock();
messageFlagLock.writeLock().lock();
try {
subscriptionLock.writeLock().lock();
messageStatusLock.writeLock().lock();
try {
T txn = db.startTransaction();
subscriptionLock.writeLock().lock();
try {
if(db.containsSubscription(txn, g)) {
affected = db.getVisibility(txn, g);
db.removeSubscription(txn, g);
T txn = db.startTransaction();
try {
if(db.containsSubscription(txn, g)) {
affected = db.getVisibility(txn, g);
db.removeSubscription(txn, g);
}
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
} finally {
subscriptionLock.writeLock().unlock();
}
} finally {
subscriptionLock.writeLock().unlock();
messageStatusLock.writeLock().unlock();
}
} finally {
messageStatusLock.writeLock().unlock();
messageFlagLock.writeLock().unlock();
}
} finally {
messageLock.writeLock().unlock();
@@ -1457,19 +1469,24 @@ DatabaseCleaner.Callback {
try {
messageLock.writeLock().lock();
try {
messageStatusLock.writeLock().lock();
messageFlagLock.writeLock().lock();
try {
T txn = db.startTransaction();
messageStatusLock.writeLock().lock();
try {
old = db.getOldMessages(txn, size);
for(MessageId m : old) removeMessage(txn, m);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
T txn = db.startTransaction();
try {
old = db.getOldMessages(txn, size);
for(MessageId m : old) removeMessage(txn, m);
db.commitTransaction(txn);
} catch(DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
messageStatusLock.writeLock().unlock();
}
} finally {
messageStatusLock.writeLock().unlock();
messageFlagLock.writeLock().unlock();
}
} finally {
messageLock.writeLock().unlock();

View File

@@ -225,6 +225,15 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " FOREIGN KEY (contactId) REFERENCES contacts (contactId)"
+ " ON DELETE CASCADE)";
private static final String CREATE_FLAGS =
"CREATE TABLE flags"
+ " (messageId HASH NOT NULL,"
+ " read BOOLEAN NOT NULL,"
+ " starred BOOLEAN NOT NULL,"
+ " PRIMARY KEY (messageId),"
+ " FOREIGN KEY (messageId) REFERENCES messages (messageId)"
+ " ON DELETE CASCADE)";
private static final Logger LOG =
Logger.getLogger(JdbcDatabase.class.getName());
@@ -233,6 +242,7 @@ abstract class JdbcDatabase implements Database<Connection> {
private final ConnectionWindowFactory connectionWindowFactory;
private final GroupFactory groupFactory;
private final MessageHeaderFactory messageHeaderFactory;
private final LinkedList<Connection> connections =
new LinkedList<Connection>(); // Locking: self
@@ -316,6 +326,7 @@ abstract class JdbcDatabase implements Database<Connection> {
s.executeUpdate(insertTypeNames(CREATE_CONNECTION_WINDOWS));
s.executeUpdate(insertTypeNames(CREATE_SUBSCRIPTION_TIMESTAMPS));
s.executeUpdate(insertTypeNames(CREATE_TRANSPORT_TIMESTAMPS));
s.executeUpdate(insertTypeNames(CREATE_FLAGS));
s.close();
} catch(SQLException e) {
tryToClose(s);
@@ -1319,6 +1330,27 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public boolean getRead(Connection txn, MessageId m) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT read FROM flags WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
rs = ps.executeQuery();
boolean read = false;
if(rs.next()) read = rs.getBoolean(1);
if(rs.next()) throw new DbStateException();
rs.close();
ps.close();
return read;
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Map<ContactId, TransportProperties> getRemoteProperties(
Connection txn, TransportId t) throws DbException {
PreparedStatement ps = null;
@@ -1513,6 +1545,27 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public boolean getStarred(Connection txn, MessageId m) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT starred FROM flags WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
rs = ps.executeQuery();
boolean starred = false;
if(rs.next()) starred = rs.getBoolean(1);
if(rs.next()) throw new DbStateException();
rs.close();
ps.close();
return starred;
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public Collection<Group> getSubscriptions(Connection txn)
throws DbException {
PreparedStatement ps = null;
@@ -2068,7 +2121,7 @@ abstract class JdbcDatabase implements Database<Connection> {
rs = ps.executeQuery();
Rating old;
if(rs.next()) {
// A rating row exists - update it
// A rating row exists - update it if necessary
old = Rating.values()[rs.getByte(1)];
if(rs.next()) throw new DbStateException();
rs.close();
@@ -2083,17 +2136,70 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.close();
}
} else {
// No rating row exists - create one
// No rating row exists - create one if necessary
rs.close();
ps.close();
old = Rating.UNRATED;
sql = "INSERT INTO ratings (authorId, rating) VALUES (?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getBytes());
ps.setShort(2, (short) r.ordinal());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
if(!old.equals(r)) {
sql = "INSERT INTO ratings (authorId, rating)"
+ " VALUES (?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, a.getBytes());
ps.setShort(2, (short) r.ordinal());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
}
}
return old;
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public boolean setRead(Connection txn, MessageId m, boolean read)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT read FROM flags WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
rs = ps.executeQuery();
boolean old;
if(rs.next()) {
// A flag row exists - update it if necessary
old = rs.getBoolean(1);
if(rs.next()) throw new DbStateException();
rs.close();
ps.close();
if(old != read) {
sql = "UPDATE flags SET read = ? WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBoolean(1, read);
ps.setBytes(2, m.getBytes());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
}
} else {
// No flag row exists - create one if necessary
ps.close();
rs.close();
old = false;
if(old != read) {
sql = "INSERT INTO flags (messageId, read, starred)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
ps.setBoolean(2, read);
ps.setBoolean(3, false);
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
}
}
return old;
} catch(SQLException e) {
@@ -2121,6 +2227,56 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public boolean setStarred(Connection txn, MessageId m, boolean starred)
throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
try {
String sql = "SELECT starred FROM flags WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
rs = ps.executeQuery();
boolean old;
if(rs.next()) {
// A flag row exists - update it if necessary
old = rs.getBoolean(1);
if(rs.next()) throw new DbStateException();
rs.close();
ps.close();
if(old != starred) {
sql = "UPDATE flags SET starred = ? WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBoolean(1, starred);
ps.setBytes(2, m.getBytes());
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
}
} else {
// No flag row exists - create one if necessary
ps.close();
rs.close();
old = false;
if(old != starred) {
sql = "INSERT INTO flags (messageId, read, starred)"
+ " VALUES (?, ?, ?)";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
ps.setBoolean(2, false);
ps.setBoolean(3, starred);
int affected = ps.executeUpdate();
if(affected != 1) throw new DbStateException();
ps.close();
}
}
return old;
} catch(SQLException e) {
tryToClose(rs);
tryToClose(ps);
throw new DbException(e);
}
}
public void setStatus(Connection txn, ContactId c, MessageId m, Status s)
throws DbException {
PreparedStatement ps = null;
@@ -2133,7 +2289,7 @@ abstract class JdbcDatabase implements Database<Connection> {
ps.setInt(2, c.getInt());
rs = ps.executeQuery();
if(rs.next()) {
// A status row exists - update it
// A status row exists - update it if necessary
Status old = Status.values()[rs.getByte(1)];
if(rs.next()) throw new DbStateException();
rs.close();

View File

@@ -1705,6 +1705,58 @@ public class H2DatabaseTest extends TestCase {
db.close();
}
@Test
public void testReadFlag() throws Exception {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Subscribe to a group and store a message
db.addSubscription(txn, group);
db.addGroupMessage(txn, message);
// The message should be unread by default
assertFalse(db.getRead(txn, messageId));
// Marking the message read should return the old value
assertFalse(db.setRead(txn, messageId, true));
assertTrue(db.setRead(txn, messageId, true));
// The message should be read
assertTrue(db.getRead(txn, messageId));
// Marking the message unread should return the old value
assertTrue(db.setRead(txn, messageId, false));
assertFalse(db.setRead(txn, messageId, false));
// Unsubscribe from the group
db.removeSubscription(txn, groupId);
db.commitTransaction(txn);
db.close();
}
@Test
public void testStarredFlag() throws Exception {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Subscribe to a group and store a message
db.addSubscription(txn, group);
db.addGroupMessage(txn, message);
// The message should be unstarred by default
assertFalse(db.getStarred(txn, messageId));
// Starring the message should return the old value
assertFalse(db.setStarred(txn, messageId, true));
assertTrue(db.setStarred(txn, messageId, true));
// The message should be starred
assertTrue(db.getStarred(txn, messageId));
// Unstarring the message should return the old value
assertTrue(db.setStarred(txn, messageId, false));
assertFalse(db.setStarred(txn, messageId, false));
// Unsubscribe from the group
db.removeSubscription(txn, groupId);
db.commitTransaction(txn);
db.close();
}
private void assertHeadersAreEqual(MessageHeader h1, MessageHeader h2) {
assertEquals(h1.getId(), h2.getId());
if(h1.getParent() == null) assertNull(h2.getParent());