1
0
mirror of https://github.com/moparisthebest/k-9 synced 2024-11-23 18:02:15 -05:00

Get rid of 'attachments' table

This commit is contained in:
cketti 2015-01-06 23:59:58 +01:00
parent 1a5ecfea1d
commit 34b5d56ab1
5 changed files with 122 additions and 456 deletions

View File

@ -3194,7 +3194,7 @@ public class MessagingController implements Runnable {
MimeMessageHelper.setBody(remoteMessage, message.getBody()); MimeMessageHelper.setBody(remoteMessage, message.getBody());
remoteFolder.fetchPart(remoteMessage, part, null); remoteFolder.fetchPart(remoteMessage, part, null);
localFolder.updateMessage((LocalMessage)message); localFolder.addPartToMessage((LocalMessage) message, part);
for (MessagingListener l : getListeners(listener)) { for (MessagingListener l : getListeners(listener)) {
l.loadAttachmentFinished(account, message, part, tag); l.loadAttachmentFinished(account, message, part, tag);
} }

View File

@ -4,7 +4,6 @@ package com.fsck.k9.mailstore;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
import java.io.File; import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.Serializable; import java.io.Serializable;
@ -19,20 +18,17 @@ import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.Stack; import java.util.Stack;
import java.util.UUID; import java.util.UUID;
import java.util.regex.Pattern;
import android.content.ContentValues; import android.content.ContentValues;
import android.content.Context; import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.database.Cursor; import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.util.Log; import android.util.Log;
import com.fsck.k9.Account; import com.fsck.k9.Account;
import com.fsck.k9.K9; import com.fsck.k9.K9;
import com.fsck.k9.activity.Search; import com.fsck.k9.activity.Search;
import com.fsck.k9.helper.HtmlConverter;
import com.fsck.k9.helper.Utility; import com.fsck.k9.helper.Utility;
import com.fsck.k9.mail.Address; import com.fsck.k9.mail.Address;
import com.fsck.k9.mail.Body; import com.fsck.k9.mail.Body;
@ -49,13 +45,10 @@ import com.fsck.k9.mail.Part;
import com.fsck.k9.mail.internet.MimeBodyPart; import com.fsck.k9.mail.internet.MimeBodyPart;
import com.fsck.k9.mail.internet.MimeHeader; import com.fsck.k9.mail.internet.MimeHeader;
import com.fsck.k9.mail.internet.MimeMessage; import com.fsck.k9.mail.internet.MimeMessage;
import com.fsck.k9.mail.internet.MimeMessageHelper;
import com.fsck.k9.mail.internet.MimeMultipart; import com.fsck.k9.mail.internet.MimeMultipart;
import com.fsck.k9.mail.internet.MimeUtility;
import com.fsck.k9.mailstore.LockableDatabase.DbCallback; import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
import com.fsck.k9.mailstore.LockableDatabase.WrappedException; import com.fsck.k9.mailstore.LockableDatabase.WrappedException;
import com.fsck.k9.provider.AttachmentProvider; import com.fsck.k9.provider.AttachmentProvider;
import org.apache.commons.io.IOUtils;
import org.apache.james.mime4j.MimeException; import org.apache.james.mime4j.MimeException;
import org.apache.james.mime4j.parser.ContentHandler; import org.apache.james.mime4j.parser.ContentHandler;
import org.apache.james.mime4j.parser.MimeStreamParser; import org.apache.james.mime4j.parser.MimeStreamParser;
@ -1230,10 +1223,10 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
if (oldMessage != null) { if (oldMessage != null) {
oldMessageId = oldMessage.getId(); oldMessageId = oldMessage.getId();
}
//FIXME long oldRootMessagePartId = oldMessage.getMessagePartId();
deleteAttachments(message.getUid()); deleteMessagePartsAndDataFromDisk(oldRootMessagePartId);
}
} }
long rootId = -1; long rootId = -1;
@ -1408,274 +1401,11 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
} }
} }
/** public void addPartToMessage(final LocalMessage message, final Part part) throws MessagingException {
* Update the given message in the LocalStore without first deleting the existing
* message (contrast with appendMessages). This method is used to store changes
* to the given message while updating attachments and not removing existing
* attachment data.
* TODO In the future this method should be combined with appendMessages since the Message
* contains enough data to decide what to do.
* @param message
* @throws MessagingException
*/
public void updateMessage(final LocalMessage message) throws MessagingException {
open(OPEN_MODE_RW); open(OPEN_MODE_RW);
try { throw new RuntimeException("Not implemented yet");
this.localStore.database.execute(false, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
try {
ViewableContainer container =
LocalMessageExtractor.extractTextAndAttachments(LocalFolder.this.localStore.context, message);
List<Part> attachments = container.attachments; // localStore.notifyChange();
String text = container.text;
String html = HtmlConverter.convertEmoji2Img(container.html);
String preview = Message.calculateContentPreview(text);
try {
db.execSQL("UPDATE messages SET "
+ "uid = ?, subject = ?, sender_list = ?, date = ?, flags = ?, "
+ "folder_id = ?, to_list = ?, cc_list = ?, bcc_list = ?, "
+ "html_content = ?, text_content = ?, preview = ?, reply_to_list = ?, "
+ "attachment_count = ?, read = ?, flagged = ?, answered = ?, forwarded = ? "
+ "WHERE id = ?",
new Object[] {
message.getUid(),
message.getSubject(),
Address.pack(message.getFrom()),
message.getSentDate() == null ? System
.currentTimeMillis() : message.getSentDate()
.getTime(),
LocalFolder.this.localStore.serializeFlags(message.getFlags()),
mFolderId,
Address.pack(message
.getRecipients(RecipientType.TO)),
Address.pack(message
.getRecipients(RecipientType.CC)),
Address.pack(message
.getRecipients(RecipientType.BCC)),
html.length() > 0 ? html : null,
text.length() > 0 ? text : null,
preview.length() > 0 ? preview : null,
Address.pack(message.getReplyTo()),
attachments.size(),
message.isSet(Flag.SEEN) ? 1 : 0,
message.isSet(Flag.FLAGGED) ? 1 : 0,
message.isSet(Flag.ANSWERED) ? 1 : 0,
message.isSet(Flag.FORWARDED) ? 1 : 0,
message.getId()
});
for (int i = 0, count = attachments.size(); i < count; i++) {
Part attachment = attachments.get(i);
saveAttachment(message.getId(), attachment, false);
}
//FIXME
//saveHeaders(message.getId(), message);
} catch (Exception e) {
throw new MessagingException("Error appending message", e);
}
} catch (MessagingException e) {
throw new WrappedException(e);
}
return null;
}
});
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
}
this.localStore.notifyChange();
}
/**
* @param messageId
* @param attachment
* @param saveAsNew
* @throws IOException
* @throws MessagingException
*/
private void saveAttachment(final long messageId, final Part attachment, final boolean saveAsNew)
throws IOException, MessagingException {
try {
this.localStore.database.execute(true, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
try {
long attachmentId = -1;
Uri contentUri = null;
int size = -1;
File tempAttachmentFile = null;
if ((!saveAsNew) && (attachment instanceof LocalAttachmentBodyPart)) {
attachmentId = ((LocalAttachmentBodyPart) attachment).getAttachmentId();
}
final File attachmentDirectory = StorageManager.getInstance(LocalFolder.this.localStore.context).getAttachmentDirectory(LocalFolder.this.localStore.uUid, LocalFolder.this.localStore.database.getStorageProviderId());
if (attachment.getBody() != null) {
Body body = attachment.getBody();
if (body instanceof LocalAttachmentBody) {
contentUri = ((LocalAttachmentBody) body).getContentUri();
} else if (body instanceof Message) {
// It's a message, so use Message.writeTo() to output the
// message including all children.
Message message = (Message) body;
tempAttachmentFile = File.createTempFile("att", null, attachmentDirectory);
FileOutputStream out = new FileOutputStream(tempAttachmentFile);
try {
message.writeTo(out);
} finally {
out.close();
}
size = (int) (tempAttachmentFile.length() & 0x7FFFFFFFL);
} else {
/*
* If the attachment has a body we're expected to save it into the local store
* so we copy the data into a cached attachment file.
*/
InputStream in = MimeUtility.decodeBody(attachment.getBody());
try {
tempAttachmentFile = File.createTempFile("att", null, attachmentDirectory);
FileOutputStream out = new FileOutputStream(tempAttachmentFile);
try {
size = IOUtils.copy(in, out);
} finally {
out.close();
}
} finally {
try { in.close(); } catch (Throwable ignore) {}
}
}
}
if (size == -1) {
/*
* If the attachment is not yet downloaded see if we can pull a size
* off the Content-Disposition.
*/
String disposition = attachment.getDisposition();
if (disposition != null) {
String sizeParam = MimeUtility.getHeaderParameter(disposition, "size");
if (sizeParam != null) {
try {
size = Integer.parseInt(sizeParam);
} catch (NumberFormatException e) { /* Ignore */ }
}
}
}
if (size == -1) {
size = 0;
}
String storeData =
Utility.combine(attachment.getHeader(
MimeHeader.HEADER_ANDROID_ATTACHMENT_STORE_DATA), ',');
String name = MimeUtility.getHeaderParameter(attachment.getContentType(), "name");
String contentId = MimeUtility.getHeaderParameter(attachment.getContentId(), null);
String contentDisposition = MimeUtility.unfoldAndDecode(attachment.getDisposition());
String dispositionType = contentDisposition;
if (dispositionType != null) {
int pos = dispositionType.indexOf(';');
if (pos != -1) {
// extract the disposition-type, "attachment", "inline" or extension-token (see the RFC 2183)
dispositionType = dispositionType.substring(0, pos);
}
}
if (name == null && contentDisposition != null) {
name = MimeUtility.getHeaderParameter(contentDisposition, "filename");
}
if (attachmentId == -1) {
ContentValues cv = new ContentValues();
cv.put("message_id", messageId);
cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
cv.put("store_data", storeData);
cv.put("size", size);
cv.put("name", name);
cv.put("mime_type", attachment.getMimeType());
cv.put("content_id", contentId);
cv.put("content_disposition", dispositionType);
attachmentId = db.insert("attachments", "message_id", cv);
} else {
ContentValues cv = new ContentValues();
cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
cv.put("size", size);
db.update("attachments", cv, "id = ?", new String[]
{ Long.toString(attachmentId) });
}
if (attachmentId != -1 && tempAttachmentFile != null) {
File attachmentFile = new File(attachmentDirectory, Long.toString(attachmentId));
tempAttachmentFile.renameTo(attachmentFile);
contentUri = AttachmentProvider.getAttachmentUri(
getAccount(),
attachmentId);
if (MimeUtil.isMessage(attachment.getMimeType())) {
LocalAttachmentMessageBody body = new LocalAttachmentMessageBody(
contentUri, LocalFolder.this.localStore.context);
MimeMessageHelper.setBody(attachment, body);
} else {
LocalAttachmentBody body = new LocalAttachmentBody(
contentUri, LocalFolder.this.localStore.context);
MimeMessageHelper.setBody(attachment, body);
}
ContentValues cv = new ContentValues();
cv.put("content_uri", contentUri != null ? contentUri.toString() : null);
db.update("attachments", cv, "id = ?", new String[]
{ Long.toString(attachmentId) });
}
/* The message has attachment with Content-ID */
if (contentId != null && contentUri != null) {
Cursor cursor = db.query("messages", new String[]
{ "html_content" }, "id = ?", new String[]
{ Long.toString(messageId) }, null, null, null);
try {
if (cursor.moveToNext()) {
String htmlContent = cursor.getString(0);
if (htmlContent != null) {
String newHtmlContent = htmlContent.replaceAll(
Pattern.quote("cid:" + contentId),
contentUri.toString());
ContentValues cv = new ContentValues();
cv.put("html_content", newHtmlContent);
db.update("messages", cv, "id = ?", new String[]
{ Long.toString(messageId) });
}
}
} finally {
Utility.closeQuietly(cursor);
}
}
if (attachmentId != -1 && attachment instanceof LocalAttachmentBodyPart) {
((LocalAttachmentBodyPart) attachment).setAttachmentId(attachmentId);
}
return null;
} catch (MessagingException e) {
throw new WrappedException(e);
} catch (IOException e) {
throw new WrappedException(e);
}
}
});
} catch (WrappedException e) {
final Throwable cause = e.getCause();
if (cause instanceof IOException) {
throw (IOException) cause;
}
throw (MessagingException) cause;
}
} }
/** /**
@ -1770,21 +1500,18 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
@Override @Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException { public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
try { try {
// Get UIDs for all messages to delete Cursor cursor = db.query("messages", new String[] { "message_part_id" },
Cursor cursor = db.query("messages", new String[] { "uid" },
"folder_id = ? AND (empty IS NULL OR empty != 1)", "folder_id = ? AND (empty IS NULL OR empty != 1)",
folderIdArg, null, null, null); folderIdArg, null, null, null);
try { try {
// Delete attachments of these messages
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
deleteAttachments(cursor.getString(0)); long messagePartId = cursor.getLong(0);
deleteMessageDataFromDisk(messagePartId);
} }
} finally { } finally {
cursor.close(); cursor.close();
} }
// Delete entries in 'threads' and 'messages'
db.execSQL("DELETE FROM threads WHERE message_id IN " + db.execSQL("DELETE FROM threads WHERE message_id IN " +
"(SELECT id FROM messages WHERE folder_id = ?)", folderIdArg); "(SELECT id FROM messages WHERE folder_id = ?)", folderIdArg);
db.execSQL("DELETE FROM messages WHERE folder_id = ?", folderIdArg); db.execSQL("DELETE FROM messages WHERE folder_id = ?", folderIdArg);
@ -1816,9 +1543,9 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
try { try {
// We need to open the folder first to make sure we've got it's id // We need to open the folder first to make sure we've got it's id
open(OPEN_MODE_RO); open(OPEN_MODE_RO);
List<? extends Message> messages = getMessages(null); List<LocalMessage> messages = getMessages(null);
for (Message message : messages) { for (LocalMessage message : messages) {
deleteAttachments(message.getUid()); deleteMessageDataFromDisk(message.getMessagePartId());
} }
} catch (MessagingException e) { } catch (MessagingException e) {
throw new WrappedException(e); throw new WrappedException(e);
@ -1846,75 +1573,68 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
return mName.hashCode(); return mName.hashCode();
} }
void deleteAttachments(final long messageId) throws MessagingException { void deleteMessagePartsAndDataFromDisk(final long rootMessagePartId) throws MessagingException {
open(OPEN_MODE_RW); deleteMessageDataFromDisk(rootMessagePartId);
this.localStore.database.execute(false, new DbCallback<Void>() { deleteMessageParts(rootMessagePartId);
}
private void deleteMessageParts(final long rootMessagePartId) throws MessagingException {
localStore.database.execute(false, new DbCallback<Void>() {
@Override @Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException { public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
Cursor attachmentsCursor = null; db.delete("message_parts", "root = ?", new String[] { Long.toString(rootMessagePartId) });
return null;
}
});
}
private void deleteMessageDataFromDisk(final long rootMessagePartId) throws MessagingException {
localStore.database.execute(false, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
deleteMessagePartsFromDisk(db, rootMessagePartId);
deleteAttachmentThumbnailsFromDisk(db, rootMessagePartId);
return null;
}
});
}
private void deleteMessagePartsFromDisk(SQLiteDatabase db, long rootMessagePartId) {
File attachmentDirectory = StorageManager.getInstance(localStore.context)
.getAttachmentDirectory(getAccountUuid(), localStore.database.getStorageProviderId());
Cursor cursor = db.query("message_parts", new String[] { "id" },
"root = ? AND data_location = " + DataLocation.ON_DISK,
new String[] { Long.toString(rootMessagePartId) }, null, null, null);
try { try {
String accountUuid = getAccountUuid(); while (cursor.moveToNext()) {
Context context = LocalFolder.this.localStore.context; String messagePartId = cursor.getString(0);
File file = new File(attachmentDirectory, messagePartId);
// Get attachment IDs
String[] whereArgs = new String[] { Long.toString(messageId) };
attachmentsCursor = db.query("attachments", new String[] { "id" },
"message_id = ?", whereArgs, null, null, null);
final File attachmentDirectory = StorageManager.getInstance(LocalFolder.this.localStore.context)
.getAttachmentDirectory(LocalFolder.this.localStore.uUid, LocalFolder.this.localStore.database.getStorageProviderId());
while (attachmentsCursor.moveToNext()) {
String attachmentId = Long.toString(attachmentsCursor.getLong(0));
try {
// Delete stored attachment
File file = new File(attachmentDirectory, attachmentId);
if (file.exists()) { if (file.exists()) {
file.delete(); if (!file.delete() && K9.DEBUG) {
Log.d(K9.LOG_TAG, "Couldn't delete message part file: " + file.getAbsolutePath());
}
} }
// Delete thumbnail file
AttachmentProvider.deleteThumbnail(context, accountUuid,
attachmentId);
} catch (Exception e) { /* ignore */ }
} }
// Delete attachment metadata from the database
db.delete("attachments", "message_id = ?", whereArgs);
} finally { } finally {
Utility.closeQuietly(attachmentsCursor); cursor.close();
} }
return null;
}
});
} }
private void deleteAttachments(final String uid) throws MessagingException { private void deleteAttachmentThumbnailsFromDisk(SQLiteDatabase db, long rootMessagePartId) {
open(OPEN_MODE_RW); Context context = localStore.context;
try { String accountUuid = getAccountUuid();
this.localStore.database.execute(false, new DbCallback<Void>() {
@Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
Cursor messagesCursor = null;
try {
messagesCursor = db.query("messages", new String[]
{ "id" }, "folder_id = ? AND uid = ?", new String[]
{ Long.toString(mFolderId), uid }, null, null, null);
while (messagesCursor.moveToNext()) {
long messageId = messagesCursor.getLong(0);
deleteAttachments(messageId);
Cursor cursor = db.query("message_parts", new String[] { "id" },
"root = ? AND type = " + MessagePartType.ATTACHMENT,
new String[] { Long.toString(rootMessagePartId) }, null, null, null);
try {
while (cursor.moveToNext()) {
String messagePartId = cursor.getString(0);
AttachmentProvider.deleteThumbnail(context, accountUuid, messagePartId);
} }
} catch (MessagingException e) {
throw new WrappedException(e);
} finally { } finally {
Utility.closeQuietly(messagesCursor); cursor.close();
}
return null;
}
});
} catch (WrappedException e) {
throw(MessagingException) e.getCause();
} }
} }
@ -2185,7 +1905,7 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
} }
// Note: The contents of the 'message_parts' table depend on these values. // Note: The contents of the 'message_parts' table depend on these values.
private static class DataLocation { static class DataLocation {
static final int MISSING = 0; static final int MISSING = 0;
static final int IN_DATABASE = 1; static final int IN_DATABASE = 1;
static final int ON_DISK = 2; static final int ON_DISK = 2;

View File

@ -290,23 +290,14 @@ public class LocalMessage extends MimeMessage {
} }
/* /*
* If a message is being marked as deleted we want to clear out it's content * If a message is being marked as deleted we want to clear out its content. Delete will not actually remove the
* and attachments as well. Delete will not actually remove the row since we need * row since we need to retain the UID for synchronization purposes.
* to retain the uid for synchronization purposes.
*/
private void delete() throws MessagingException
{
/*
* Delete all of the message's content to save space.
*/ */
private void delete() throws MessagingException {
try { try {
this.localStore.database.execute(true, new DbCallback<Void>() { localStore.database.execute(true, new DbCallback<Void>() {
@Override @Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
UnavailableStorageException {
String[] idArg = new String[] { Long.toString(mId) };
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put("deleted", 1); cv.put("deleted", 1);
cv.put("empty", 1); cv.put("empty", 1);
@ -320,29 +311,24 @@ public class LocalMessage extends MimeMessage {
cv.putNull("html_content"); cv.putNull("html_content");
cv.putNull("text_content"); cv.putNull("text_content");
cv.putNull("reply_to_list"); cv.putNull("reply_to_list");
cv.putNull("message_part_id");
db.update("messages", cv, "id = ?", idArg); db.update("messages", cv, "id = ?", new String[] { Long.toString(mId) });
/*
* Delete all of the message's attachments to save space.
* We do this explicit deletion here because we're not deleting the record
* in messages, which means our ON DELETE trigger for messages won't cascade
*/
try { try {
((LocalFolder) mFolder).deleteAttachments(mId); ((LocalFolder) mFolder).deleteMessagePartsAndDataFromDisk(messagePartId);
} catch (MessagingException e) { } catch (MessagingException e) {
throw new WrappedException(e); throw new WrappedException(e);
} }
db.delete("attachments", "message_id = ?", idArg);
return null; return null;
} }
}); });
} catch (WrappedException e) { } catch (WrappedException e) {
throw(MessagingException) e.getCause(); throw (MessagingException) e.getCause();
} }
this.localStore.notifyChange(); localStore.notifyChange();
} }
/* /*
@ -360,7 +346,7 @@ public class LocalMessage extends MimeMessage {
try { try {
LocalFolder localFolder = (LocalFolder) mFolder; LocalFolder localFolder = (LocalFolder) mFolder;
localFolder.deleteAttachments(mId); localFolder.deleteMessagePartsAndDataFromDisk(messagePartId);
if (hasThreadChildren(db, mId)) { if (hasThreadChildren(db, mId)) {
// This message has children in the thread structure so we need to // This message has children in the thread structure so we need to

View File

@ -21,8 +21,8 @@ import com.fsck.k9.mail.Folder;
import com.fsck.k9.mail.MessageRetrievalListener; import com.fsck.k9.mail.MessageRetrievalListener;
import com.fsck.k9.mail.MessagingException; import com.fsck.k9.mail.MessagingException;
import com.fsck.k9.mail.Store; import com.fsck.k9.mail.Store;
import com.fsck.k9.mailstore.LocalFolder.DataLocation;
import com.fsck.k9.mailstore.StorageManager.StorageProvider; import com.fsck.k9.mailstore.StorageManager.StorageProvider;
import com.fsck.k9.mail.store.StoreConfig;
import com.fsck.k9.mailstore.LockableDatabase.DbCallback; import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
import com.fsck.k9.mailstore.LockableDatabase.WrappedException; import com.fsck.k9.mailstore.LockableDatabase.WrappedException;
import com.fsck.k9.provider.EmailProvider; import com.fsck.k9.provider.EmailProvider;
@ -276,7 +276,7 @@ public class LocalStore extends Store implements Serializable {
if (K9.DEBUG) if (K9.DEBUG)
Log.i(K9.LOG_TAG, "Before prune size = " + getSize()); Log.i(K9.LOG_TAG, "Before prune size = " + getSize());
pruneCachedAttachments(true); deleteAllMessageDataFromDisk();
if (K9.DEBUG) { if (K9.DEBUG) {
Log.i(K9.LOG_TAG, "After prune / before compaction size = " + getSize()); Log.i(K9.LOG_TAG, "After prune / before compaction size = " + getSize());
@ -398,73 +398,39 @@ public class LocalStore extends Store implements Serializable {
database.recreate(); database.recreate();
} }
/** private void deleteAllMessageDataFromDisk() throws MessagingException {
* Deletes all cached attachments for the entire store. markAllMessagePartsDataAsMissing();
* @param force deleteAllMessagePartsDataFromDisk();
* @throws com.fsck.k9.mail.MessagingException }
*/
//TODO this method seems to be only called with force=true, simplify accordingly private void markAllMessagePartsDataAsMissing() throws MessagingException {
private void pruneCachedAttachments(final boolean force) throws MessagingException {
database.execute(false, new DbCallback<Void>() { database.execute(false, new DbCallback<Void>() {
@Override @Override
public Void doDbWork(final SQLiteDatabase db) throws WrappedException { public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
if (force) {
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.putNull("content_uri"); cv.put("data_location", DataLocation.MISSING);
db.update("attachments", cv, null, null); db.update("message_parts", cv, null, null);
}
final StorageManager storageManager = StorageManager.getInstance(context);
File[] files = storageManager.getAttachmentDirectory(uUid, database.getStorageProviderId()).listFiles();
for (File file : files) {
if (file.exists()) {
if (!force) {
Cursor cursor = null;
try {
cursor = db.query(
"attachments",
new String[] { "store_data" },
"id = ?",
new String[] { file.getName() },
null,
null,
null);
if (cursor.moveToNext()) {
if (cursor.getString(0) == null) {
if (K9.DEBUG)
Log.d(K9.LOG_TAG, "Attachment " + file.getAbsolutePath() + " has no store data, not deleting");
/*
* If the attachment has no store data it is not recoverable, so
* we won't delete it.
*/
continue;
}
}
} finally {
Utility.closeQuietly(cursor);
}
}
if (!force) {
try {
ContentValues cv = new ContentValues();
cv.putNull("content_uri");
db.update("attachments", cv, "id = ?", new String[] { file.getName() });
} catch (Exception e) {
/*
* If the row has gone away before we got to mark it not-downloaded that's
* okay.
*/
}
}
if (!file.delete()) {
file.deleteOnExit();
}
}
}
return null; return null;
} }
}); });
} }
private void deleteAllMessagePartsDataFromDisk() {
final StorageManager storageManager = StorageManager.getInstance(context);
File attachmentDirectory = storageManager.getAttachmentDirectory(uUid, database.getStorageProviderId());
File[] files = attachmentDirectory.listFiles();
if (files == null) {
return;
}
for (File file : files) {
if (file.exists() && !file.delete()) {
file.deleteOnExit();
}
}
}
public void resetVisibleLimits(int visibleLimit) throws MessagingException { public void resetVisibleLimits(int visibleLimit) throws MessagingException {
final ContentValues cv = new ContentValues(); final ContentValues cv = new ContentValues();
cv.put("visible_limit", Integer.toString(visibleLimit)); cv.put("visible_limit", Integer.toString(visibleLimit));
@ -672,32 +638,27 @@ public class LocalStore extends Store implements Serializable {
return database.execute(false, new DbCallback<AttachmentInfo>() { return database.execute(false, new DbCallback<AttachmentInfo>() {
@Override @Override
public AttachmentInfo doDbWork(final SQLiteDatabase db) throws WrappedException { public AttachmentInfo doDbWork(final SQLiteDatabase db) throws WrappedException {
String name; Cursor cursor = db.query("message_parts",
String type; new String[] { "display_name", "decoded_body_size", "mime_type" },
int size;
Cursor cursor = null;
try {
cursor = db.query(
"attachments",
new String[] { "name", "size", "mime_type" },
"id = ?", "id = ?",
new String[] { attachmentId }, new String[] { attachmentId },
null, null, null, null);
null, try {
null);
if (!cursor.moveToFirst()) { if (!cursor.moveToFirst()) {
return null; return null;
} }
name = cursor.getString(0); String name = cursor.getString(0);
size = cursor.getInt(1); int size = cursor.getInt(1);
type = cursor.getString(2); String mimeType = cursor.getString(2);
final AttachmentInfo attachmentInfo = new AttachmentInfo(); final AttachmentInfo attachmentInfo = new AttachmentInfo();
attachmentInfo.name = name; attachmentInfo.name = name;
attachmentInfo.size = size; attachmentInfo.size = size;
attachmentInfo.type = type; attachmentInfo.type = mimeType;
return attachmentInfo; return attachmentInfo;
} finally { } finally {
Utility.closeQuietly(cursor); cursor.close();
} }
} }
}); });
@ -705,7 +666,7 @@ public class LocalStore extends Store implements Serializable {
public static class AttachmentInfo { public static class AttachmentInfo {
public String name; public String name;
public int size; public int size; //FIXME: use long
public String type; public String type;
} }

View File

@ -167,11 +167,6 @@ class StoreSchemaDefinition implements LockableDatabase.SchemaDefinition {
"UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " + "UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
"END"); "END");
db.execSQL("DROP TABLE IF EXISTS attachments");
db.execSQL("CREATE TABLE attachments (id INTEGER PRIMARY KEY, message_id INTEGER,"
+ "store_data TEXT, content_uri TEXT, size INTEGER, name TEXT,"
+ "mime_type TEXT, content_id TEXT, content_disposition TEXT)");
db.execSQL("DROP TABLE IF EXISTS pending_commands"); db.execSQL("DROP TABLE IF EXISTS pending_commands");
db.execSQL("CREATE TABLE pending_commands " + db.execSQL("CREATE TABLE pending_commands " +
"(id INTEGER PRIMARY KEY, command TEXT, arguments TEXT)"); "(id INTEGER PRIMARY KEY, command TEXT, arguments TEXT)");
@ -180,7 +175,11 @@ class StoreSchemaDefinition implements LockableDatabase.SchemaDefinition {
db.execSQL("CREATE TRIGGER delete_folder BEFORE DELETE ON folders BEGIN DELETE FROM messages WHERE old.id = folder_id; END;"); db.execSQL("CREATE TRIGGER delete_folder BEFORE DELETE ON folders BEGIN DELETE FROM messages WHERE old.id = folder_id; END;");
db.execSQL("DROP TRIGGER IF EXISTS delete_message"); db.execSQL("DROP TRIGGER IF EXISTS delete_message");
db.execSQL("CREATE TRIGGER delete_message BEFORE DELETE ON messages BEGIN DELETE FROM attachments WHERE old.id = message_id; END;"); db.execSQL("CREATE TRIGGER delete_message " +
"BEFORE DELETE ON messages " +
"BEGIN " +
"DELETE FROM message_parts WHERE root = OLD.message_part_id;" +
"END");
} else { } else {
// in the case that we're starting out at 29 or newer, run all the needed updates // in the case that we're starting out at 29 or newer, run all the needed updates