Merge branch '238-delete-messages' into 'master'

Allow messages to be deleted.

Fixes #238.

See merge request !98
This commit is contained in:
Torsten Grote
2016-02-10 17:59:45 +00:00
6 changed files with 172 additions and 8 deletions

View File

@@ -63,6 +63,15 @@ public interface DatabaseComponent {
*/
void addTransportKeys(ContactId c, TransportKeys k) throws DbException;
/**
* Deletes the message with the given ID. The message ID and any other
* associated data are not deleted.
*/
void deleteMessage(MessageId m) throws DbException;
/** Deletes any metadata associated with the given message. */
void deleteMessageMetadata(MessageId m) throws DbException;
/**
* Returns an acknowledgement for the given contact, or null if there are
* no messages to acknowledge.

View File

@@ -215,6 +215,23 @@ interface Database<T> {
*/
int countOfferedMessages(T txn, ContactId c) throws DbException;
/**
* Deletes the message with the given ID. Unlike
* {@link #removeMessage(Object, MessageId)}, the message ID and any other
* associated data are not deleted, and
* {@link #containsMessage(Object, MessageId)} will continue to return true.
* <p>
* Locking: write.
*/
void deleteMessage(T txn, MessageId m) throws DbException;
/**
* Deletes any metadata associated with the given message.
* <p>
* Locking: write.
*/
void deleteMessageMetadata(T txn, MessageId m) throws DbException;
/**
* Returns the contact with the given ID.
* <p>

View File

@@ -293,6 +293,40 @@ class DatabaseComponentImpl<T> implements DatabaseComponent {
}
}
public void deleteMessage(MessageId m) throws DbException {
lock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
db.deleteMessage(txn, m);
} catch (DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
lock.writeLock().unlock();
}
}
public void deleteMessageMetadata(MessageId m) throws DbException {
lock.writeLock().lock();
try {
T txn = db.startTransaction();
try {
if (!db.containsMessage(txn, m))
throw new NoSuchMessageException();
db.deleteMessageMetadata(txn, m);
} catch (DbException e) {
db.abortTransaction(txn);
throw e;
}
} finally {
lock.writeLock().unlock();
}
}
public Ack generateAck(ContactId c, int maxMessages) throws DbException {
Collection<MessageId> ids;
lock.writeLock().lock();

View File

@@ -136,7 +136,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " valid INT NOT NULL,"
+ " shared BOOLEAN NOT NULL,"
+ " length INT NOT NULL,"
+ " raw BLOB NOT NULL,"
+ " raw BLOB," // Null if message has been deleted
+ " PRIMARY KEY (messageId),"
+ " FOREIGN KEY (groupId)"
+ " REFERENCES groups (groupId)"
@@ -933,6 +933,38 @@ abstract class JdbcDatabase implements Database<Connection> {
}
}
public void deleteMessage(Connection txn, MessageId m) throws DbException {
PreparedStatement ps = null;
try {
String sql = "UPDATE messages SET raw = NULL WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
int affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
if (affected > 1) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
public void deleteMessageMetadata(Connection txn, MessageId m)
throws DbException {
PreparedStatement ps = null;
try {
String sql = "DELETE FROM messageMetadata WHERE messageId = ?";
ps = txn.prepareStatement(sql);
ps.setBytes(1, m.getBytes());
int affected = ps.executeUpdate();
if (affected < 0) throw new DbStateException();
ps.close();
} catch (SQLException e) {
tryToClose(ps);
throw new DbException(e);
}
}
public Contact getContact(Connection txn, ContactId c) throws DbException {
PreparedStatement ps = null;
ResultSet rs = null;
@@ -1308,7 +1340,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " ON m.messageId = s.messageId"
+ " AND gv.contactId = s.contactId"
+ " WHERE gv.contactId = ?"
+ " AND valid = ? AND shared = TRUE"
+ " AND valid = ? AND shared = TRUE AND raw IS NOT NULL"
+ " AND seen = FALSE AND requested = FALSE"
+ " AND s.expiry < ?"
+ " ORDER BY timestamp DESC LIMIT ?";
@@ -1367,7 +1399,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " ON m.messageId = s.messageId"
+ " AND gv.contactId = s.contactId"
+ " WHERE gv.contactId = ?"
+ " AND valid = ? AND shared = TRUE"
+ " AND valid = ? AND shared = TRUE AND raw IS NOT NULL"
+ " AND seen = FALSE"
+ " AND s.expiry < ?"
+ " ORDER BY timestamp DESC";
@@ -1401,7 +1433,7 @@ abstract class JdbcDatabase implements Database<Connection> {
try {
String sql = "SELECT messageId FROM messages AS m"
+ " JOIN groups AS g ON m.groupId = g.groupId"
+ " WHERE valid = ? AND clientId = ?";
+ " WHERE valid = ? AND clientId = ? AND raw IS NOT NULL";
ps = txn.prepareStatement(sql);
ps.setInt(1, UNKNOWN.getValue());
ps.setBytes(2, c.getBytes());
@@ -1453,7 +1485,7 @@ abstract class JdbcDatabase implements Database<Connection> {
+ " ON m.messageId = s.messageId"
+ " AND gv.contactId = s.contactId"
+ " WHERE gv.contactId = ?"
+ " AND valid = ? AND shared = TRUE"
+ " AND valid = ? AND shared = TRUE AND raw IS NOT NULL"
+ " AND seen = FALSE AND requested = TRUE"
+ " AND s.expiry < ?"
+ " ORDER BY timestamp DESC";

View File

@@ -543,11 +543,11 @@ public class DatabaseComponentImplTest extends BriarTestCase {
final EventBus eventBus = context.mock(EventBus.class);
context.checking(new Expectations() {{
// Check whether the message is in the DB (which it's not)
exactly(6).of(database).startTransaction();
exactly(8).of(database).startTransaction();
will(returnValue(txn));
exactly(6).of(database).containsMessage(txn, messageId);
exactly(8).of(database).containsMessage(txn, messageId);
will(returnValue(false));
exactly(6).of(database).abortTransaction(txn);
exactly(8).of(database).abortTransaction(txn);
// This is needed for getMessageStatus() to proceed
exactly(1).of(database).containsContact(txn, contactId);
will(returnValue(true));
@@ -555,6 +555,20 @@ public class DatabaseComponentImplTest extends BriarTestCase {
DatabaseComponent db = createDatabaseComponent(database, eventBus,
shutdown);
try {
db.deleteMessage(messageId);
fail();
} catch (NoSuchMessageException expected) {
// Expected
}
try {
db.deleteMessageMetadata(messageId);
fail();
} catch (NoSuchMessageException expected) {
// Expected
}
try {
db.getRawMessage(messageId);
fail();

View File

@@ -52,6 +52,8 @@ import static org.junit.Assert.assertArrayEquals;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -937,6 +939,17 @@ public class H2DatabaseTest extends BriarTestCase {
assertTrue(retrieved.containsKey("baz"));
assertArrayEquals(metadata.get("baz"), retrieved.get("baz"));
// Delete the metadata
db.deleteMessageMetadata(txn, messageId);
// Retrieve the metadata again
retrieved = db.getMessageMetadata(txn, messageId);
assertTrue(retrieved.isEmpty());
// Retrieve the metadata for the group again
all = db.getMessageMetadata(txn, groupId);
assertTrue(all.isEmpty());
db.commitTransaction(txn);
db.close();
}
@@ -1071,6 +1084,51 @@ public class H2DatabaseTest extends BriarTestCase {
db.close();
}
@Test
public void testDeleteMessage() throws Exception {
Database<Connection> db = open(false);
Connection txn = db.startTransaction();
// Add a contact, a group and a message
db.addLocalAuthor(txn, localAuthor);
assertEquals(contactId, db.addContact(txn, author, localAuthorId));
db.addGroup(txn, group);
db.addVisibility(txn, contactId, groupId);
db.addMessage(txn, message, VALID, true);
db.addStatus(txn, contactId, messageId, false, false);
// The message should be visible to the contact
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
// The message should be sendable
Collection<MessageId> ids = db.getMessagesToSend(txn, contactId,
ONE_MEGABYTE);
assertEquals(Collections.singletonList(messageId), ids);
ids = db.getMessagesToOffer(txn, contactId, 100);
assertEquals(Collections.singletonList(messageId), ids);
// The raw message should not be null
assertNotNull(db.getRawMessage(txn, messageId));
// Delete the message
db.deleteMessage(txn, messageId);
// The message should be visible to the contact
assertTrue(db.containsVisibleMessage(txn, contactId, messageId));
// The message should not be sendable
ids = db.getMessagesToSend(txn, contactId, ONE_MEGABYTE);
assertTrue(ids.isEmpty());
ids = db.getMessagesToOffer(txn, contactId, 100);
assertTrue(ids.isEmpty());
// The raw message should be null
assertNull(db.getRawMessage(txn, messageId));
db.commitTransaction(txn);
db.close();
}
@Test
public void testExceptionHandling() throws Exception {
Database<Connection> db = open(false);