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:
parent
1a5ecfea1d
commit
34b5d56ab1
@ -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);
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
@ -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,21 +311,16 @@ 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;
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -342,7 +328,7 @@ public class LocalMessage extends MimeMessage {
|
|||||||
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
|
||||||
|
@ -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;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user