move responsibility for destroying messages from LocalMessage to LocalFolder

This commit is contained in:
Vincent Breitmoser 2017-08-22 15:20:28 +02:00
parent 74b5b01b3d
commit 3f21549664
2 changed files with 150 additions and 144 deletions

View file

@ -26,7 +26,6 @@ import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import timber.log.Timber;
import com.fsck.k9.Account;
import com.fsck.k9.K9;
@ -67,6 +66,7 @@ import com.fsck.k9.preferences.Storage;
import com.fsck.k9.preferences.StorageEditor;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.util.MimeUtil;
import timber.log.Timber;
public class LocalFolder extends Folder<LocalMessage> implements Serializable {
@ -1839,6 +1839,152 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
return mName.hashCode();
}
void destroyMessage(LocalMessage localMessage) throws MessagingException {
destroyMessage(localMessage.getId(), localMessage.getMessagePartId(), localMessage.getMessageId());
}
private void destroyMessage(final long messageId, final long messagePartId, final String messageIdHeader)
throws MessagingException {
try {
localStore.database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
UnavailableStorageException {
try {
deleteMessagePartsAndDataFromDisk(messagePartId);
deleteFulltextIndexEntry(db, messageId);
if (hasThreadChildren(db, messageId)) {
// This message has children in the thread structure so we need to
// make it an empty message.
ContentValues cv = new ContentValues();
cv.put("id", messageId);
cv.put("folder_id", getId());
cv.put("deleted", 0);
cv.put("message_id", messageIdHeader);
cv.put("empty", 1);
db.replace("messages", null, cv);
// Nothing else to do
return null;
}
// Get the message ID of the parent message if it's empty
long currentId = getEmptyThreadParent(db, messageId);
// Delete the placeholder message
deleteMessageRow(db, messageId);
/*
* Walk the thread tree to delete all empty parents without children
*/
while (currentId != -1) {
if (hasThreadChildren(db, currentId)) {
// We made sure there are no empty leaf nodes and can stop now.
break;
}
// Get ID of the (empty) parent for the next iteration
long newId = getEmptyThreadParent(db, currentId);
// Delete the empty message
deleteMessageRow(db, currentId);
currentId = newId;
}
} catch (MessagingException e) {
throw new WrappedException(e);
}
return null;
}
});
} catch (WrappedException e) {
throw (MessagingException) e.getCause();
}
localStore.notifyChange();
}
/**
* Check whether or not a message has child messages in the thread structure.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to get the children for.
*
* @return {@code true} if the message has children. {@code false} otherwise.
*/
private boolean hasThreadChildren(SQLiteDatabase db, long messageId) {
Cursor cursor = db.rawQuery(
"SELECT COUNT(t2.id) " +
"FROM threads t1 " +
"JOIN threads t2 ON (t2.parent = t1.id) " +
"WHERE t1.message_id = ?",
new String[] { Long.toString(messageId) });
try {
return (cursor.moveToFirst() && !cursor.isNull(0) && cursor.getLong(0) > 0L);
} finally {
cursor.close();
}
}
/**
* Get ID of the the given message's parent if the parent is an empty message.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to get the parent for.
*
* @return Message ID of the parent message if there exists a parent and it is empty.
* Otherwise {@code -1}.
*/
private long getEmptyThreadParent(SQLiteDatabase db, long messageId) {
Cursor cursor = db.rawQuery(
"SELECT m.id " +
"FROM threads t1 " +
"JOIN threads t2 ON (t1.parent = t2.id) " +
"LEFT JOIN messages m ON (t2.message_id = m.id) " +
"WHERE t1.message_id = ? AND m.empty = 1",
new String[] { Long.toString(messageId) });
try {
return (cursor.moveToFirst() && !cursor.isNull(0)) ? cursor.getLong(0) : -1;
} finally {
cursor.close();
}
}
/**
* Delete a message from the 'messages' and 'threads' tables.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to delete.
*/
private void deleteMessageRow(SQLiteDatabase db, long messageId) {
String[] idArg = { Long.toString(messageId) };
// Delete the message
db.delete("messages", "id = ?", idArg);
// Delete row in 'threads' table
// TODO: create trigger for 'messages' table to get rid of the row in 'threads' table
db.delete("threads", "message_id = ?", idArg);
}
void deleteFulltextIndexEntry(SQLiteDatabase db, long messageId) {
String[] idArg = { Long.toString(messageId) };
db.delete("messages_fulltext", "docid = ?", idArg);
}
void deleteMessagePartsAndDataFromDisk(final long rootMessagePartId) throws MessagingException {
deleteMessageDataFromDisk(rootMessagePartId);
deleteMessageParts(rootMessagePartId);

View file

@ -10,11 +10,9 @@ import android.content.ContentValues;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.VisibleForTesting;
import timber.log.Timber;
import com.fsck.k9.Account;
import com.fsck.k9.BuildConfig;
import com.fsck.k9.K9;
import com.fsck.k9.activity.MessageReference;
import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Flag;
@ -25,6 +23,7 @@ import com.fsck.k9.mail.message.MessageHeaderParser;
import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
import com.fsck.k9.mailstore.LockableDatabase.WrappedException;
import com.fsck.k9.message.extractors.PreviewResult.PreviewType;
import timber.log.Timber;
public class LocalMessage extends MimeMessage {
@ -322,7 +321,7 @@ public class LocalMessage extends MimeMessage {
throw new WrappedException(e);
}
deleteFulltextIndexEntry(db, mId);
getFolder().deleteFulltextIndexEntry(db, mId);
return null;
}
@ -374,146 +373,7 @@ public class LocalMessage extends MimeMessage {
*/
@Override
public void destroy() throws MessagingException {
try {
this.localStore.database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
UnavailableStorageException {
try {
LocalFolder localFolder = (LocalFolder) mFolder;
localFolder.deleteMessagePartsAndDataFromDisk(messagePartId);
deleteFulltextIndexEntry(db, mId);
if (hasThreadChildren(db, mId)) {
// This message has children in the thread structure so we need to
// make it an empty message.
ContentValues cv = new ContentValues();
cv.put("id", mId);
cv.put("folder_id", localFolder.getId());
cv.put("deleted", 0);
cv.put("message_id", getMessageId());
cv.put("empty", 1);
db.replace("messages", null, cv);
// Nothing else to do
return null;
}
// Get the message ID of the parent message if it's empty
long currentId = getEmptyThreadParent(db, mId);
// Delete the placeholder message
deleteMessageRow(db, mId);
/*
* Walk the thread tree to delete all empty parents without children
*/
while (currentId != -1) {
if (hasThreadChildren(db, currentId)) {
// We made sure there are no empty leaf nodes and can stop now.
break;
}
// Get ID of the (empty) parent for the next iteration
long newId = getEmptyThreadParent(db, currentId);
// Delete the empty message
deleteMessageRow(db, currentId);
currentId = newId;
}
} catch (MessagingException e) {
throw new WrappedException(e);
}
return null;
}
});
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
this.localStore.notifyChange();
}
/**
* Get ID of the the given message's parent if the parent is an empty message.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to get the parent for.
*
* @return Message ID of the parent message if there exists a parent and it is empty.
* Otherwise {@code -1}.
*/
private long getEmptyThreadParent(SQLiteDatabase db, long messageId) {
Cursor cursor = db.rawQuery(
"SELECT m.id " +
"FROM threads t1 " +
"JOIN threads t2 ON (t1.parent = t2.id) " +
"LEFT JOIN messages m ON (t2.message_id = m.id) " +
"WHERE t1.message_id = ? AND m.empty = 1",
new String[] { Long.toString(messageId) });
try {
return (cursor.moveToFirst() && !cursor.isNull(0)) ? cursor.getLong(0) : -1;
} finally {
cursor.close();
}
}
/**
* Check whether or not a message has child messages in the thread structure.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to get the children for.
*
* @return {@code true} if the message has children. {@code false} otherwise.
*/
private boolean hasThreadChildren(SQLiteDatabase db, long messageId) {
Cursor cursor = db.rawQuery(
"SELECT COUNT(t2.id) " +
"FROM threads t1 " +
"JOIN threads t2 ON (t2.parent = t1.id) " +
"WHERE t1.message_id = ?",
new String[] { Long.toString(messageId) });
try {
return (cursor.moveToFirst() && !cursor.isNull(0) && cursor.getLong(0) > 0L);
} finally {
cursor.close();
}
}
private void deleteFulltextIndexEntry(SQLiteDatabase db, long messageId) {
String[] idArg = { Long.toString(messageId) };
db.delete("messages_fulltext", "docid = ?", idArg);
}
/**
* Delete a message from the 'messages' and 'threads' tables.
*
* @param db
* {@link SQLiteDatabase} instance to access the database.
* @param messageId
* The database ID of the message to delete.
*/
private void deleteMessageRow(SQLiteDatabase db, long messageId) {
String[] idArg = { Long.toString(messageId) };
// Delete the message
db.delete("messages", "id = ?", idArg);
// Delete row in 'threads' table
// TODO: create trigger for 'messages' table to get rid of the row in 'threads' table
db.delete("threads", "message_id = ?", idArg);
getFolder().destroyMessage(this);
}
@Override