From 00275e260fcb69db3010717a41484a27e190f185 Mon Sep 17 00:00:00 2001
From: akwizgran <akwizgran@users.sourceforge.net>
Date: Tue, 9 Feb 2016 11:14:52 +0000
Subject: [PATCH] Allow messages to be deleted.

---
 .../api/db/DatabaseComponent.java             |  6 +++
 .../src/org/briarproject/db/Database.java     | 10 ++++
 .../db/DatabaseComponentImpl.java             | 17 +++++++
 .../src/org/briarproject/db/JdbcDatabase.java | 26 ++++++++--
 .../org/briarproject/db/H2DatabaseTest.java   | 47 +++++++++++++++++++
 5 files changed, 101 insertions(+), 5 deletions(-)

diff --git a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
index 465a886bd7..80fd14a7e6 100644
--- a/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
+++ b/briar-api/src/org/briarproject/api/db/DatabaseComponent.java
@@ -63,6 +63,12 @@ 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;
+
 	/**
 	 * Returns an acknowledgement for the given contact, or null if there are
 	 * no messages to acknowledge.
diff --git a/briar-core/src/org/briarproject/db/Database.java b/briar-core/src/org/briarproject/db/Database.java
index a5a22b135c..d21e6c3495 100644
--- a/briar-core/src/org/briarproject/db/Database.java
+++ b/briar-core/src/org/briarproject/db/Database.java
@@ -215,6 +215,16 @@ 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;
+
 	/**
 	 * Returns the contact with the given ID.
 	 * <p>
diff --git a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
index 9e929d86c8..d55501a101 100644
--- a/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
+++ b/briar-core/src/org/briarproject/db/DatabaseComponentImpl.java
@@ -293,6 +293,23 @@ 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 Ack generateAck(ContactId c, int maxMessages) throws DbException {
 		Collection<MessageId> ids;
 		lock.writeLock().lock();
diff --git a/briar-core/src/org/briarproject/db/JdbcDatabase.java b/briar-core/src/org/briarproject/db/JdbcDatabase.java
index 79c07da039..2f32e8fd8b 100644
--- a/briar-core/src/org/briarproject/db/JdbcDatabase.java
+++ b/briar-core/src/org/briarproject/db/JdbcDatabase.java
@@ -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,22 @@ 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 Contact getContact(Connection txn, ContactId c) throws DbException {
 		PreparedStatement ps = null;
 		ResultSet rs = null;
@@ -1308,7 +1324,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 +1383,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 +1417,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 +1469,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";
diff --git a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
index 2fe48c6c3e..ec0674e03f 100644
--- a/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
+++ b/briar-tests/src/org/briarproject/db/H2DatabaseTest.java
@@ -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;
 
@@ -1071,6 +1073,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);
-- 
GitLab