Get rid of 'attachments' table
This commit is contained in:
parent
1a5ecfea1d
commit
34b5d56ab1
5 changed files with 122 additions and 456 deletions
|
@ -3194,7 +3194,7 @@ public class MessagingController implements Runnable {
|
|||
MimeMessageHelper.setBody(remoteMessage, message.getBody());
|
||||
remoteFolder.fetchPart(remoteMessage, part, null);
|
||||
|
||||
localFolder.updateMessage((LocalMessage)message);
|
||||
localFolder.addPartToMessage((LocalMessage) message, part);
|
||||
for (MessagingListener l : getListeners(listener)) {
|
||||
l.loadAttachmentFinished(account, message, part, tag);
|
||||
}
|
||||
|
|
|
@ -4,7 +4,6 @@ package com.fsck.k9.mailstore;
|
|||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.Serializable;
|
||||
|
@ -19,20 +18,17 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
import java.util.Stack;
|
||||
import java.util.UUID;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import android.content.ContentValues;
|
||||
import android.content.Context;
|
||||
import android.content.SharedPreferences;
|
||||
import android.database.Cursor;
|
||||
import android.database.sqlite.SQLiteDatabase;
|
||||
import android.net.Uri;
|
||||
import android.util.Log;
|
||||
|
||||
import com.fsck.k9.Account;
|
||||
import com.fsck.k9.K9;
|
||||
import com.fsck.k9.activity.Search;
|
||||
import com.fsck.k9.helper.HtmlConverter;
|
||||
import com.fsck.k9.helper.Utility;
|
||||
import com.fsck.k9.mail.Address;
|
||||
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.MimeHeader;
|
||||
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.MimeUtility;
|
||||
import com.fsck.k9.mailstore.LockableDatabase.DbCallback;
|
||||
import com.fsck.k9.mailstore.LockableDatabase.WrappedException;
|
||||
import com.fsck.k9.provider.AttachmentProvider;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.apache.james.mime4j.MimeException;
|
||||
import org.apache.james.mime4j.parser.ContentHandler;
|
||||
import org.apache.james.mime4j.parser.MimeStreamParser;
|
||||
|
@ -1230,10 +1223,10 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
|
|||
|
||||
if (oldMessage != null) {
|
||||
oldMessageId = oldMessage.getId();
|
||||
}
|
||||
|
||||
//FIXME
|
||||
deleteAttachments(message.getUid());
|
||||
long oldRootMessagePartId = oldMessage.getMessagePartId();
|
||||
deleteMessagePartsAndDataFromDisk(oldRootMessagePartId);
|
||||
}
|
||||
}
|
||||
|
||||
long rootId = -1;
|
||||
|
@ -1408,274 +1401,11 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 {
|
||||
public void addPartToMessage(final LocalMessage message, final Part part) throws MessagingException {
|
||||
open(OPEN_MODE_RW);
|
||||
try {
|
||||
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);
|
||||
throw new RuntimeException("Not implemented yet");
|
||||
|
||||
List<Part> attachments = container.attachments;
|
||||
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;
|
||||
}
|
||||
// localStore.notifyChange();
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1770,21 +1500,18 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
|
|||
@Override
|
||||
public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
|
||||
try {
|
||||
// Get UIDs for all messages to delete
|
||||
Cursor cursor = db.query("messages", new String[] { "uid" },
|
||||
Cursor cursor = db.query("messages", new String[] { "message_part_id" },
|
||||
"folder_id = ? AND (empty IS NULL OR empty != 1)",
|
||||
folderIdArg, null, null, null);
|
||||
|
||||
try {
|
||||
// Delete attachments of these messages
|
||||
while (cursor.moveToNext()) {
|
||||
deleteAttachments(cursor.getString(0));
|
||||
long messagePartId = cursor.getLong(0);
|
||||
deleteMessageDataFromDisk(messagePartId);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
|
||||
// Delete entries in 'threads' and 'messages'
|
||||
db.execSQL("DELETE FROM threads WHERE message_id IN " +
|
||||
"(SELECT id 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 {
|
||||
// We need to open the folder first to make sure we've got it's id
|
||||
open(OPEN_MODE_RO);
|
||||
List<? extends Message> messages = getMessages(null);
|
||||
for (Message message : messages) {
|
||||
deleteAttachments(message.getUid());
|
||||
List<LocalMessage> messages = getMessages(null);
|
||||
for (LocalMessage message : messages) {
|
||||
deleteMessageDataFromDisk(message.getMessagePartId());
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
throw new WrappedException(e);
|
||||
|
@ -1846,75 +1573,68 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
|
|||
return mName.hashCode();
|
||||
}
|
||||
|
||||
void deleteAttachments(final long messageId) throws MessagingException {
|
||||
open(OPEN_MODE_RW);
|
||||
this.localStore.database.execute(false, new DbCallback<Void>() {
|
||||
void deleteMessagePartsAndDataFromDisk(final long rootMessagePartId) throws MessagingException {
|
||||
deleteMessageDataFromDisk(rootMessagePartId);
|
||||
deleteMessageParts(rootMessagePartId);
|
||||
}
|
||||
|
||||
private void deleteMessageParts(final long rootMessagePartId) throws MessagingException {
|
||||
localStore.database.execute(false, new DbCallback<Void>() {
|
||||
@Override
|
||||
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
|
||||
Cursor attachmentsCursor = null;
|
||||
try {
|
||||
String accountUuid = getAccountUuid();
|
||||
Context context = LocalFolder.this.localStore.context;
|
||||
|
||||
// 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()) {
|
||||
file.delete();
|
||||
}
|
||||
|
||||
// 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 {
|
||||
Utility.closeQuietly(attachmentsCursor);
|
||||
}
|
||||
db.delete("message_parts", "root = ?", new String[] { Long.toString(rootMessagePartId) });
|
||||
return null;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void deleteAttachments(final String uid) throws MessagingException {
|
||||
open(OPEN_MODE_RW);
|
||||
try {
|
||||
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);
|
||||
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;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
} catch (MessagingException e) {
|
||||
throw new WrappedException(e);
|
||||
} finally {
|
||||
Utility.closeQuietly(messagesCursor);
|
||||
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 {
|
||||
while (cursor.moveToNext()) {
|
||||
String messagePartId = cursor.getString(0);
|
||||
File file = new File(attachmentDirectory, messagePartId);
|
||||
if (file.exists()) {
|
||||
if (!file.delete() && K9.DEBUG) {
|
||||
Log.d(K9.LOG_TAG, "Couldn't delete message part file: " + file.getAbsolutePath());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} catch (WrappedException e) {
|
||||
throw(MessagingException) e.getCause();
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
private void deleteAttachmentThumbnailsFromDisk(SQLiteDatabase db, long rootMessagePartId) {
|
||||
Context context = localStore.context;
|
||||
String accountUuid = getAccountUuid();
|
||||
|
||||
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);
|
||||
}
|
||||
} finally {
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -2185,7 +1905,7 @@ public class LocalFolder extends Folder<LocalMessage> implements Serializable {
|
|||
}
|
||||
|
||||
// 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 IN_DATABASE = 1;
|
||||
static final int ON_DISK = 2;
|
||||
|
|
|
@ -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
|
||||
* and attachments as well. Delete will not actually remove the row since we need
|
||||
* to retain the uid for synchronization purposes.
|
||||
* If a message is being marked as deleted we want to clear out its content. Delete will not actually remove the
|
||||
* row since we need 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 {
|
||||
this.localStore.database.execute(true, new DbCallback<Void>() {
|
||||
localStore.database.execute(true, new DbCallback<Void>() {
|
||||
@Override
|
||||
public Void doDbWork(final SQLiteDatabase db) throws WrappedException,
|
||||
UnavailableStorageException {
|
||||
String[] idArg = new String[] { Long.toString(mId) };
|
||||
|
||||
public Void doDbWork(final SQLiteDatabase db) throws WrappedException, UnavailableStorageException {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put("deleted", 1);
|
||||
cv.put("empty", 1);
|
||||
|
@ -320,29 +311,24 @@ public class LocalMessage extends MimeMessage {
|
|||
cv.putNull("html_content");
|
||||
cv.putNull("text_content");
|
||||
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 {
|
||||
((LocalFolder) mFolder).deleteAttachments(mId);
|
||||
((LocalFolder) mFolder).deleteMessagePartsAndDataFromDisk(messagePartId);
|
||||
} catch (MessagingException e) {
|
||||
throw new WrappedException(e);
|
||||
}
|
||||
|
||||
db.delete("attachments", "message_id = ?", idArg);
|
||||
return null;
|
||||
}
|
||||
});
|
||||
} 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 {
|
||||
LocalFolder localFolder = (LocalFolder) mFolder;
|
||||
|
||||
localFolder.deleteAttachments(mId);
|
||||
localFolder.deleteMessagePartsAndDataFromDisk(messagePartId);
|
||||
|
||||
if (hasThreadChildren(db, mId)) {
|
||||
// This message has children in the thread structure so we need to
|
||||
|
|
|
@ -21,8 +21,8 @@ import com.fsck.k9.mail.Folder;
|
|||
import com.fsck.k9.mail.MessageRetrievalListener;
|
||||
import com.fsck.k9.mail.MessagingException;
|
||||
import com.fsck.k9.mail.Store;
|
||||
import com.fsck.k9.mailstore.LocalFolder.DataLocation;
|
||||
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.WrappedException;
|
||||
import com.fsck.k9.provider.EmailProvider;
|
||||
|
@ -276,7 +276,7 @@ public class LocalStore extends Store implements Serializable {
|
|||
if (K9.DEBUG)
|
||||
Log.i(K9.LOG_TAG, "Before prune size = " + getSize());
|
||||
|
||||
pruneCachedAttachments(true);
|
||||
deleteAllMessageDataFromDisk();
|
||||
if (K9.DEBUG) {
|
||||
Log.i(K9.LOG_TAG, "After prune / before compaction size = " + getSize());
|
||||
|
||||
|
@ -398,73 +398,39 @@ public class LocalStore extends Store implements Serializable {
|
|||
database.recreate();
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes all cached attachments for the entire store.
|
||||
* @param force
|
||||
* @throws com.fsck.k9.mail.MessagingException
|
||||
*/
|
||||
//TODO this method seems to be only called with force=true, simplify accordingly
|
||||
private void pruneCachedAttachments(final boolean force) throws MessagingException {
|
||||
private void deleteAllMessageDataFromDisk() throws MessagingException {
|
||||
markAllMessagePartsDataAsMissing();
|
||||
deleteAllMessagePartsDataFromDisk();
|
||||
}
|
||||
|
||||
private void markAllMessagePartsDataAsMissing() throws MessagingException {
|
||||
database.execute(false, new DbCallback<Void>() {
|
||||
@Override
|
||||
public Void doDbWork(final SQLiteDatabase db) throws WrappedException {
|
||||
if (force) {
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.putNull("content_uri");
|
||||
db.update("attachments", 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
ContentValues cv = new ContentValues();
|
||||
cv.put("data_location", DataLocation.MISSING);
|
||||
db.update("message_parts", cv, null, 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 {
|
||||
final ContentValues cv = new ContentValues();
|
||||
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>() {
|
||||
@Override
|
||||
public AttachmentInfo doDbWork(final SQLiteDatabase db) throws WrappedException {
|
||||
String name;
|
||||
String type;
|
||||
int size;
|
||||
Cursor cursor = null;
|
||||
Cursor cursor = db.query("message_parts",
|
||||
new String[] { "display_name", "decoded_body_size", "mime_type" },
|
||||
"id = ?",
|
||||
new String[] { attachmentId },
|
||||
null, null, null);
|
||||
try {
|
||||
cursor = db.query(
|
||||
"attachments",
|
||||
new String[] { "name", "size", "mime_type" },
|
||||
"id = ?",
|
||||
new String[] { attachmentId },
|
||||
null,
|
||||
null,
|
||||
null);
|
||||
if (!cursor.moveToFirst()) {
|
||||
return null;
|
||||
}
|
||||
name = cursor.getString(0);
|
||||
size = cursor.getInt(1);
|
||||
type = cursor.getString(2);
|
||||
String name = cursor.getString(0);
|
||||
int size = cursor.getInt(1);
|
||||
String mimeType = cursor.getString(2);
|
||||
|
||||
final AttachmentInfo attachmentInfo = new AttachmentInfo();
|
||||
attachmentInfo.name = name;
|
||||
attachmentInfo.size = size;
|
||||
attachmentInfo.type = type;
|
||||
attachmentInfo.type = mimeType;
|
||||
|
||||
return attachmentInfo;
|
||||
} finally {
|
||||
Utility.closeQuietly(cursor);
|
||||
cursor.close();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
@ -705,7 +666,7 @@ public class LocalStore extends Store implements Serializable {
|
|||
|
||||
public static class AttachmentInfo {
|
||||
public String name;
|
||||
public int size;
|
||||
public int size; //FIXME: use long
|
||||
public String type;
|
||||
}
|
||||
|
||||
|
|
|
@ -167,11 +167,6 @@ class StoreSchemaDefinition implements LockableDatabase.SchemaDefinition {
|
|||
"UPDATE threads SET root=id WHERE root IS NULL AND ROWID = NEW.ROWID; " +
|
||||
"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("CREATE TABLE pending_commands " +
|
||||
"(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("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 {
|
||||
// in the case that we're starting out at 29 or newer, run all the needed updates
|
||||
|
||||
|
|
Loading…
Reference in a new issue