mirror of
https://code.briarproject.org/briar/briar.git
synced 2026-02-12 18:59:06 +01:00
Added message flags: read/unread and starred/unstarred.
This commit is contained in:
@@ -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>
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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());
|
||||
|
||||
Reference in New Issue
Block a user