Merge pull request #5259 from k9mail/save_messages
This commit is contained in:
commit
c43f27a9ff
10 changed files with 316 additions and 99 deletions
|
@ -6,14 +6,21 @@ import com.fsck.k9.backend.api.Backend
|
|||
import com.fsck.k9.controller.MessagingControllerCommands.PendingAppend
|
||||
import com.fsck.k9.controller.MessagingControllerCommands.PendingReplace
|
||||
import com.fsck.k9.mail.FetchProfile
|
||||
import com.fsck.k9.mail.Flag
|
||||
import com.fsck.k9.mail.Message
|
||||
import com.fsck.k9.mail.MessagingException
|
||||
import com.fsck.k9.mailstore.LocalFolder
|
||||
import com.fsck.k9.mailstore.LocalMessage
|
||||
import com.fsck.k9.mailstore.MessageStoreManager
|
||||
import com.fsck.k9.mailstore.SaveMessageData
|
||||
import com.fsck.k9.mailstore.SaveMessageDataCreator
|
||||
import org.jetbrains.annotations.NotNull
|
||||
import timber.log.Timber
|
||||
|
||||
internal class DraftOperations(private val messagingController: MessagingController) {
|
||||
internal class DraftOperations(
|
||||
private val messagingController: @NotNull MessagingController,
|
||||
private val messageStoreManager: @NotNull MessageStoreManager,
|
||||
private val saveMessageDataCreator: SaveMessageDataCreator
|
||||
) {
|
||||
|
||||
fun saveDraft(
|
||||
account: Account,
|
||||
|
@ -24,22 +31,13 @@ internal class DraftOperations(private val messagingController: MessagingControl
|
|||
return try {
|
||||
val draftsFolderId = account.draftsFolderId ?: error("No Drafts folder configured")
|
||||
|
||||
val localStore = messagingController.getLocalStoreOrThrow(account)
|
||||
val localFolder = localStore.getFolder(draftsFolderId)
|
||||
localFolder.open()
|
||||
|
||||
val localMessage = if (messagingController.supportsUpload(account)) {
|
||||
saveAndUploadDraft(account, message, localFolder, existingDraftId)
|
||||
val messageId = if (messagingController.supportsUpload(account)) {
|
||||
saveAndUploadDraft(account, message, draftsFolderId, existingDraftId, plaintextSubject)
|
||||
} else {
|
||||
saveDraftLocally(message, localFolder, existingDraftId)
|
||||
saveDraftLocally(account, message, draftsFolderId, existingDraftId, plaintextSubject)
|
||||
}
|
||||
|
||||
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true)
|
||||
if (plaintextSubject != null) {
|
||||
localMessage.setCachedDecryptedSubject(plaintextSubject)
|
||||
}
|
||||
|
||||
localMessage.databaseId
|
||||
messageId
|
||||
} catch (e: MessagingException) {
|
||||
Timber.e(e, "Unable to save message as draft.")
|
||||
null
|
||||
|
@ -49,41 +47,52 @@ internal class DraftOperations(private val messagingController: MessagingControl
|
|||
private fun saveAndUploadDraft(
|
||||
account: Account,
|
||||
message: Message,
|
||||
localFolder: LocalFolder,
|
||||
existingDraftId: Long?
|
||||
): LocalMessage {
|
||||
localFolder.appendMessages(listOf(message))
|
||||
folderId: Long,
|
||||
existingDraftId: Long?,
|
||||
subject: String?
|
||||
): Long {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
|
||||
val localMessage = localFolder.getMessage(message.uid)
|
||||
val previousDraftMessage = if (existingDraftId != null) localFolder.getMessage(existingDraftId) else null
|
||||
val messageId = messageStore.saveLocalMessage(folderId, message.toSaveMessageData(subject))
|
||||
|
||||
val previousDraftMessage = if (existingDraftId != null) {
|
||||
val localStore = messagingController.getLocalStoreOrThrow(account)
|
||||
val localFolder = localStore.getFolder(folderId)
|
||||
localFolder.open()
|
||||
|
||||
localFolder.getMessage(existingDraftId)
|
||||
} else {
|
||||
null
|
||||
}
|
||||
|
||||
val folderId = localFolder.databaseId
|
||||
if (previousDraftMessage != null) {
|
||||
previousDraftMessage.delete()
|
||||
|
||||
val uploadMessageId = localMessage.databaseId
|
||||
val deleteMessageId = previousDraftMessage.databaseId
|
||||
val command = PendingReplace.create(folderId, uploadMessageId, deleteMessageId)
|
||||
val command = PendingReplace.create(folderId, messageId, deleteMessageId)
|
||||
messagingController.queuePendingCommand(account, command)
|
||||
} else {
|
||||
val command = PendingAppend.create(folderId, localMessage.uid)
|
||||
val fakeMessageServerId = messageStore.getMessageServerId(messageId)
|
||||
val command = PendingAppend.create(folderId, fakeMessageServerId)
|
||||
messagingController.queuePendingCommand(account, command)
|
||||
}
|
||||
|
||||
messagingController.processPendingCommands(account)
|
||||
|
||||
return localMessage
|
||||
return messageId
|
||||
}
|
||||
|
||||
private fun saveDraftLocally(message: Message, localFolder: LocalFolder, existingDraftId: Long?): LocalMessage {
|
||||
if (existingDraftId != null) {
|
||||
// Setting the UID will cause LocalFolder.appendMessages() to replace the existing draft.
|
||||
message.uid = localFolder.getMessageUidById(existingDraftId)
|
||||
}
|
||||
private fun saveDraftLocally(
|
||||
account: Account,
|
||||
message: Message,
|
||||
folderId: Long,
|
||||
existingDraftId: Long?,
|
||||
plaintextSubject: String?
|
||||
): Long {
|
||||
val messageStore = messageStoreManager.getMessageStore(account)
|
||||
val messageData = message.toSaveMessageData(plaintextSubject)
|
||||
|
||||
localFolder.appendMessages(listOf(message))
|
||||
|
||||
return localFolder.getMessage(message.uid)
|
||||
return messageStore.saveLocalMessage(folderId, messageData, existingDraftId)
|
||||
}
|
||||
|
||||
fun processPendingReplace(command: PendingReplace, account: Account) {
|
||||
|
@ -153,4 +162,8 @@ internal class DraftOperations(private val messagingController: MessagingControl
|
|||
|
||||
messagingController.destroyPlaceholderMessages(localFolder, messageServerIds)
|
||||
}
|
||||
|
||||
private fun Message.toSaveMessageData(subject: String?): SaveMessageData {
|
||||
return saveMessageDataCreator.createSaveMessageData(this, partialMessage = false, subject)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,6 +5,7 @@ import com.fsck.k9.Preferences
|
|||
import com.fsck.k9.backend.BackendManager
|
||||
import com.fsck.k9.mailstore.LocalStoreProvider
|
||||
import com.fsck.k9.mailstore.MessageStoreManager
|
||||
import com.fsck.k9.mailstore.SaveMessageDataCreator
|
||||
import com.fsck.k9.notification.NotificationController
|
||||
import com.fsck.k9.notification.NotificationStrategy
|
||||
import org.koin.core.qualifier.named
|
||||
|
@ -21,6 +22,7 @@ val controllerModule = module {
|
|||
get<BackendManager>(),
|
||||
get<Preferences>(),
|
||||
get<MessageStoreManager>(),
|
||||
get<SaveMessageDataCreator>(),
|
||||
get(named("controllerExtensions"))
|
||||
)
|
||||
}
|
||||
|
|
|
@ -72,6 +72,8 @@ import com.fsck.k9.mailstore.MessageStore;
|
|||
import com.fsck.k9.mailstore.MessageStoreManager;
|
||||
import com.fsck.k9.mailstore.OutboxState;
|
||||
import com.fsck.k9.mailstore.OutboxStateRepository;
|
||||
import com.fsck.k9.mailstore.SaveMessageData;
|
||||
import com.fsck.k9.mailstore.SaveMessageDataCreator;
|
||||
import com.fsck.k9.mailstore.SendState;
|
||||
import com.fsck.k9.mailstore.UnavailableStorageException;
|
||||
import com.fsck.k9.notification.NotificationController;
|
||||
|
@ -116,6 +118,7 @@ public class MessagingController {
|
|||
private final BackendManager backendManager;
|
||||
private final Preferences preferences;
|
||||
private final MessageStoreManager messageStoreManager;
|
||||
private final SaveMessageDataCreator saveMessageDataCreator;
|
||||
|
||||
private final Thread controllerThread;
|
||||
|
||||
|
@ -140,7 +143,7 @@ public class MessagingController {
|
|||
NotificationStrategy notificationStrategy, LocalStoreProvider localStoreProvider,
|
||||
UnreadMessageCountProvider unreadMessageCountProvider, BackendManager backendManager,
|
||||
Preferences preferences, MessageStoreManager messageStoreManager,
|
||||
List<ControllerExtension> controllerExtensions) {
|
||||
SaveMessageDataCreator saveMessageDataCreator, List<ControllerExtension> controllerExtensions) {
|
||||
this.context = context;
|
||||
this.notificationController = notificationController;
|
||||
this.notificationStrategy = notificationStrategy;
|
||||
|
@ -149,6 +152,7 @@ public class MessagingController {
|
|||
this.backendManager = backendManager;
|
||||
this.preferences = preferences;
|
||||
this.messageStoreManager = messageStoreManager;
|
||||
this.saveMessageDataCreator = saveMessageDataCreator;
|
||||
|
||||
controllerThread = new Thread(new Runnable() {
|
||||
@Override
|
||||
|
@ -162,7 +166,7 @@ public class MessagingController {
|
|||
|
||||
initializeControllerExtensions(controllerExtensions);
|
||||
|
||||
draftOperations = new DraftOperations(this);
|
||||
draftOperations = new DraftOperations(this, messageStoreManager, saveMessageDataCreator);
|
||||
}
|
||||
|
||||
private void initializeControllerExtensions(List<ControllerExtension> controllerExtensions) {
|
||||
|
@ -1360,39 +1364,29 @@ public class MessagingController {
|
|||
}
|
||||
|
||||
/**
|
||||
* Stores the given message in the Outbox and starts a sendPendingMessages command to
|
||||
* attempt to send the message.
|
||||
* Stores the given message in the Outbox and starts a sendPendingMessages command to attempt to send the message.
|
||||
*/
|
||||
public void sendMessage(final Account account,
|
||||
final Message message,
|
||||
String plaintextSubject,
|
||||
MessagingListener listener) {
|
||||
public void sendMessage(Account account, Message message, String plaintextSubject, MessagingListener listener) {
|
||||
try {
|
||||
LocalStore localStore = localStoreProvider.getInstance(account);
|
||||
LocalFolder localFolder = localStore.getFolder(account.getOutboxFolderId());
|
||||
localFolder.open();
|
||||
message.setFlag(Flag.SEEN, true);
|
||||
localFolder.appendMessages(Collections.singletonList(message));
|
||||
LocalMessage localMessage = localFolder.getMessage(message.getUid());
|
||||
long messageId = localMessage.getDatabaseId();
|
||||
localMessage.setFlag(Flag.X_DOWNLOADED_FULL, true);
|
||||
if (plaintextSubject != null) {
|
||||
localMessage.setCachedDecryptedSubject(plaintextSubject);
|
||||
Long outboxFolderId = account.getOutboxFolderId();
|
||||
if (outboxFolderId == null) {
|
||||
Timber.e("Error sending message. No Outbox folder configured.");
|
||||
return;
|
||||
}
|
||||
|
||||
message.setFlag(Flag.SEEN, true);
|
||||
|
||||
MessageStore messageStore = messageStoreManager.getMessageStore(account);
|
||||
SaveMessageData messageData = saveMessageDataCreator.createSaveMessageData(message, false, plaintextSubject);
|
||||
long messageId = messageStore.saveLocalMessage(outboxFolderId, messageData, null);
|
||||
|
||||
LocalStore localStore = localStoreProvider.getInstance(account);
|
||||
OutboxStateRepository outboxStateRepository = localStore.getOutboxStateRepository();
|
||||
outboxStateRepository.initializeOutboxState(messageId);
|
||||
|
||||
sendPendingMessages(account, listener);
|
||||
} catch (Exception e) {
|
||||
/*
|
||||
for (MessagingListener l : getListeners())
|
||||
{
|
||||
// TODO general failed
|
||||
}
|
||||
*/
|
||||
Timber.e(e, "Error sending message");
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -795,21 +795,6 @@ public class LocalFolder {
|
|||
return folder.appendMessages(msgs, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* The method differs slightly from the contract; If an incoming message already has a uid
|
||||
* assigned and it matches the uid of an existing message then this message will replace the
|
||||
* old message. It is implemented as a delete/insert. This functionality is used in saving
|
||||
* of drafts and re-synchronization of updated server messages.
|
||||
*
|
||||
* NOTE that although this method is located in the LocalStore class, it is not guaranteed
|
||||
* that the messages supplied as parameters are actually {@link LocalMessage} instances (in
|
||||
* fact, in most cases, they are not). Therefore, if you want to make local changes only to a
|
||||
* message, retrieve the appropriate local message instance first (if it already exists).
|
||||
*/
|
||||
public Map<String, String> appendMessages(List<Message> messages) throws MessagingException {
|
||||
return appendMessages(messages, false);
|
||||
}
|
||||
|
||||
public void destroyMessages(final List<LocalMessage> messages) {
|
||||
try {
|
||||
this.localStore.getDatabase().execute(true, new DbCallback<Void>() {
|
||||
|
|
|
@ -19,6 +19,14 @@ interface MessageStore {
|
|||
*/
|
||||
fun saveRemoteMessage(folderId: Long, messageServerId: String, messageData: SaveMessageData)
|
||||
|
||||
/**
|
||||
* Save a local message in this store.
|
||||
*
|
||||
* @param existingMessageId The message with this ID is replaced if not `null`.
|
||||
* @return The message ID of the saved message.
|
||||
*/
|
||||
fun saveLocalMessage(folderId: Long, messageData: SaveMessageData, existingMessageId: Long? = null): Long
|
||||
|
||||
/**
|
||||
* Move a message to another folder.
|
||||
*
|
||||
|
|
|
@ -12,16 +12,17 @@ class SaveMessageDataCreator(
|
|||
private val messageFulltextCreator: MessageFulltextCreator,
|
||||
private val attachmentCounter: AttachmentCounter
|
||||
) {
|
||||
fun createSaveMessageData(message: Message, partialMessage: Boolean): SaveMessageData {
|
||||
fun createSaveMessageData(message: Message, partialMessage: Boolean, subject: String? = null): SaveMessageData {
|
||||
val now = System.currentTimeMillis()
|
||||
val date = message.sentDate?.time ?: now
|
||||
val internalDate = message.internalDate?.time ?: now
|
||||
val displaySubject = subject ?: message.subject
|
||||
|
||||
val encryptionResult = encryptionExtractor.extractEncryption(message)
|
||||
return if (encryptionResult != null) {
|
||||
SaveMessageData(
|
||||
message = message,
|
||||
subject = message.subject,
|
||||
subject = displaySubject,
|
||||
date = date,
|
||||
internalDate = internalDate,
|
||||
partialMessage = partialMessage,
|
||||
|
@ -33,7 +34,7 @@ class SaveMessageDataCreator(
|
|||
} else {
|
||||
SaveMessageData(
|
||||
message = message,
|
||||
subject = message.subject,
|
||||
subject = displaySubject,
|
||||
date = date,
|
||||
internalDate = internalDate,
|
||||
partialMessage = partialMessage,
|
||||
|
|
|
@ -26,6 +26,7 @@ import com.fsck.k9.mailstore.LocalStoreProvider;
|
|||
import com.fsck.k9.mailstore.MessageStoreManager;
|
||||
import com.fsck.k9.mailstore.OutboxState;
|
||||
import com.fsck.k9.mailstore.OutboxStateRepository;
|
||||
import com.fsck.k9.mailstore.SaveMessageDataCreator;
|
||||
import com.fsck.k9.mailstore.SendState;
|
||||
import com.fsck.k9.mailstore.UnavailableStorageException;
|
||||
import com.fsck.k9.notification.NotificationController;
|
||||
|
@ -78,6 +79,8 @@ public class MessagingControllerTest extends K9RobolectricTest {
|
|||
@Mock
|
||||
private MessageStoreManager messageStoreManager;
|
||||
@Mock
|
||||
private SaveMessageDataCreator saveMessageDataCreator;
|
||||
@Mock
|
||||
private SimpleMessagingListener listener;
|
||||
@Mock
|
||||
private LocalSearch search;
|
||||
|
@ -133,7 +136,7 @@ public class MessagingControllerTest extends K9RobolectricTest {
|
|||
|
||||
controller = new MessagingController(appContext, notificationController, notificationStrategy,
|
||||
localStoreProvider, unreadMessageCountProvider, backendManager, preferences, messageStoreManager,
|
||||
Collections.<ControllerExtension>emptyList());
|
||||
saveMessageDataCreator, Collections.<ControllerExtension>emptyList());
|
||||
|
||||
configureAccount();
|
||||
configureBackendManager();
|
||||
|
|
|
@ -48,6 +48,10 @@ class K9MessageStore(
|
|||
localStore.notifyChange()
|
||||
}
|
||||
|
||||
override fun saveLocalMessage(folderId: Long, messageData: SaveMessageData, existingMessageId: Long?): Long {
|
||||
return saveMessageOperations.saveLocalMessage(folderId, messageData, existingMessageId)
|
||||
}
|
||||
|
||||
override fun moveMessage(messageId: Long, destinationFolderId: Long): Long {
|
||||
return moveMessageOperations.moveMessage(messageId, destinationFolderId).also {
|
||||
localStore.notifyChange()
|
||||
|
|
|
@ -2,6 +2,7 @@ package com.fsck.k9.storage.messages
|
|||
|
||||
import android.content.ContentValues
|
||||
import android.database.sqlite.SQLiteDatabase
|
||||
import com.fsck.k9.K9
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.fsck.k9.mail.Body
|
||||
import com.fsck.k9.mail.BoundaryGenerator
|
||||
|
@ -28,6 +29,7 @@ import java.io.IOException
|
|||
import java.io.InputStream
|
||||
import java.util.Locale
|
||||
import java.util.Stack
|
||||
import java.util.UUID
|
||||
import org.apache.commons.io.IOUtils
|
||||
import org.apache.james.mime4j.codec.Base64InputStream
|
||||
import org.apache.james.mime4j.codec.QuotedPrintableInputStream
|
||||
|
@ -42,11 +44,45 @@ internal class SaveMessageOperations(
|
|||
private val threadMessageOperations: ThreadMessageOperations
|
||||
) {
|
||||
fun saveRemoteMessage(folderId: Long, messageServerId: String, messageData: SaveMessageData) {
|
||||
lockableDatabase.execute(true) { database ->
|
||||
saveMessage(folderId, messageServerId, messageData)
|
||||
}
|
||||
|
||||
fun saveLocalMessage(folderId: Long, messageData: SaveMessageData, existingMessageId: Long?): Long {
|
||||
return if (existingMessageId == null) {
|
||||
saveLocalMessage(folderId, messageData)
|
||||
} else {
|
||||
replaceLocalMessage(folderId, existingMessageId, messageData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveLocalMessage(folderId: Long, messageData: SaveMessageData): Long {
|
||||
val fakeServerId = K9.LOCAL_UID_PREFIX + UUID.randomUUID().toString()
|
||||
return saveMessage(folderId, fakeServerId, messageData)
|
||||
}
|
||||
|
||||
private fun replaceLocalMessage(folderId: Long, messageId: Long, messageData: SaveMessageData): Long {
|
||||
return lockableDatabase.execute(true) { database ->
|
||||
val (messageServerId, rootMessagePartId) = getLocalMessageInfo(folderId, messageId)
|
||||
|
||||
replaceMessage(
|
||||
database,
|
||||
folderId,
|
||||
messageServerId,
|
||||
existingMessageId = messageId,
|
||||
existingRootMessagePartId = rootMessagePartId,
|
||||
messageData
|
||||
)
|
||||
|
||||
messageId
|
||||
}
|
||||
}
|
||||
|
||||
private fun saveMessage(folderId: Long, messageServerId: String, messageData: SaveMessageData): Long {
|
||||
return lockableDatabase.execute(true) { database ->
|
||||
val message = messageData.message
|
||||
|
||||
val existingMessageInfo = getMessage(folderId, messageServerId)
|
||||
if (existingMessageInfo != null) {
|
||||
return@execute if (existingMessageInfo != null) {
|
||||
val (existingMessageId, existingRootMessagePartId) = existingMessageInfo
|
||||
replaceMessage(
|
||||
database,
|
||||
|
@ -56,29 +92,42 @@ internal class SaveMessageOperations(
|
|||
existingRootMessagePartId,
|
||||
messageData
|
||||
)
|
||||
return@execute
|
||||
|
||||
existingMessageId
|
||||
} else {
|
||||
insertMessage(database, folderId, messageServerId, message, messageData)
|
||||
}
|
||||
|
||||
val threadInfo = threadMessageOperations.doMessageThreading(database, folderId, message.toThreadHeaders())
|
||||
|
||||
val rootMessagePartId = saveMessageParts(database, message)
|
||||
val messageId = saveMessage(
|
||||
database,
|
||||
folderId,
|
||||
messageServerId,
|
||||
rootMessagePartId,
|
||||
messageData,
|
||||
replaceMessageId = threadInfo.messageId
|
||||
)
|
||||
|
||||
if (threadInfo.threadId == null) {
|
||||
threadMessageOperations.createThreadEntry(database, messageId, threadInfo.rootId, threadInfo.parentId)
|
||||
}
|
||||
|
||||
createOrReplaceFulltextEntry(database, messageId, messageData)
|
||||
}
|
||||
}
|
||||
|
||||
private fun insertMessage(
|
||||
database: SQLiteDatabase,
|
||||
folderId: Long,
|
||||
messageServerId: String,
|
||||
message: Message,
|
||||
messageData: SaveMessageData
|
||||
): Long {
|
||||
val threadInfo = threadMessageOperations.doMessageThreading(database, folderId, message.toThreadHeaders())
|
||||
|
||||
val rootMessagePartId = saveMessageParts(database, message)
|
||||
val messageId = saveMessage(
|
||||
database,
|
||||
folderId,
|
||||
messageServerId,
|
||||
rootMessagePartId,
|
||||
messageData,
|
||||
replaceMessageId = threadInfo.messageId
|
||||
)
|
||||
|
||||
if (threadInfo.threadId == null) {
|
||||
threadMessageOperations.createThreadEntry(database, messageId, threadInfo.rootId, threadInfo.parentId)
|
||||
}
|
||||
|
||||
createOrReplaceFulltextEntry(database, messageId, messageData)
|
||||
|
||||
return messageId
|
||||
}
|
||||
|
||||
private fun replaceMessage(
|
||||
database: SQLiteDatabase,
|
||||
folderId: Long,
|
||||
|
@ -413,6 +462,26 @@ internal class SaveMessageOperations(
|
|||
}
|
||||
}
|
||||
|
||||
private fun getLocalMessageInfo(folderId: Long, messageId: Long): Pair<String, Long?> {
|
||||
return lockableDatabase.execute(false) { db ->
|
||||
db.query(
|
||||
"messages",
|
||||
arrayOf("uid", "message_part_id"),
|
||||
"folder_id = ? AND id = ?",
|
||||
arrayOf(folderId.toString(), messageId.toString()),
|
||||
null,
|
||||
null,
|
||||
null
|
||||
).use { cursor ->
|
||||
if (!cursor.moveToFirst()) error("Local message not found $folderId:$messageId")
|
||||
|
||||
val messageServerId = cursor.getString(0)!!
|
||||
val messagePartId = cursor.getLong(1)
|
||||
messageServerId to messagePartId
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun deleteMessagePartsAndDataFromDisk(database: SQLiteDatabase, rootMessagePartId: Long) {
|
||||
deleteMessageDataFromDisk(database, rootMessagePartId)
|
||||
deleteMessageParts(database, rootMessagePartId)
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.fsck.k9.storage.messages
|
||||
|
||||
import com.fsck.k9.K9
|
||||
import com.fsck.k9.mail.Address
|
||||
import com.fsck.k9.mail.Flag
|
||||
import com.fsck.k9.mail.Message
|
||||
|
@ -358,6 +359,143 @@ class SaveMessageOperationsTest : RobolectricTest() {
|
|||
assertThat(thread.messageId).isEqualTo(message.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `save local message`() {
|
||||
val messageData = buildMessage {
|
||||
textBody("local")
|
||||
}.toSaveMessageData(
|
||||
subject = "Provided subject",
|
||||
date = 1618191720000L,
|
||||
internalDate = 1618191720000L,
|
||||
previewResult = PreviewResult.text("Preview")
|
||||
)
|
||||
|
||||
val newMessageId = saveMessageOperations.saveLocalMessage(folderId = 1, messageData, existingMessageId = null)
|
||||
|
||||
val messages = sqliteDatabase.readMessages()
|
||||
assertThat(messages).hasSize(1)
|
||||
|
||||
val message = messages.first()
|
||||
with(message) {
|
||||
assertThat(id).isEqualTo(newMessageId)
|
||||
assertThat(deleted).isEqualTo(0)
|
||||
assertThat(folderId).isEqualTo(1)
|
||||
assertThat(uid).startsWith(K9.LOCAL_UID_PREFIX)
|
||||
assertThat(subject).isEqualTo("Provided subject")
|
||||
assertThat(date).isEqualTo(1618191720000L)
|
||||
assertThat(internalDate).isEqualTo(1618191720000L)
|
||||
assertThat(flags).isEqualTo("X_DOWNLOADED_FULL")
|
||||
assertThat(senderList).isEqualTo("")
|
||||
assertThat(toList).isEqualTo("")
|
||||
assertThat(ccList).isEqualTo("")
|
||||
assertThat(bccList).isEqualTo("")
|
||||
assertThat(replyToList).isEqualTo("")
|
||||
assertThat(attachmentCount).isEqualTo(0)
|
||||
assertThat(messageId).isNull()
|
||||
assertThat(previewType).isEqualTo("text")
|
||||
assertThat(preview).isEqualTo("Preview")
|
||||
assertThat(mimeType).isEqualTo("text/plain")
|
||||
assertThat(empty).isEqualTo(0)
|
||||
assertThat(read).isEqualTo(0)
|
||||
assertThat(flagged).isEqualTo(0)
|
||||
assertThat(answered).isEqualTo(0)
|
||||
assertThat(forwarded).isEqualTo(0)
|
||||
assertThat(encryptionType).isNull()
|
||||
}
|
||||
|
||||
val messageParts = sqliteDatabase.readMessageParts()
|
||||
assertThat(messageParts).hasSize(1)
|
||||
|
||||
val messagePart = messageParts.first()
|
||||
with(messagePart) {
|
||||
assertThat(type).isEqualTo(MessagePartType.UNKNOWN)
|
||||
assertThat(root).isEqualTo(messagePart.id)
|
||||
assertThat(parent).isEqualTo(-1)
|
||||
assertThat(mimeType).isEqualTo("text/plain")
|
||||
assertThat(displayName).isEqualTo("noname.txt")
|
||||
assertThat(header).isNotNull()
|
||||
assertThat(encoding).isEqualTo("quoted-printable")
|
||||
assertThat(charset).isNull()
|
||||
assertThat(dataLocation).isEqualTo(DataLocation.IN_DATABASE)
|
||||
assertThat(decodedBodySize).isEqualTo(5)
|
||||
assertThat(data?.toString(Charsets.UTF_8)).isEqualTo("local")
|
||||
assertThat(preamble).isNull()
|
||||
assertThat(epilogue).isNull()
|
||||
assertThat(boundary).isNull()
|
||||
assertThat(contentId).isNull()
|
||||
assertThat(serverExtra).isNull()
|
||||
}
|
||||
|
||||
val threads = sqliteDatabase.readThreads()
|
||||
assertThat(threads).hasSize(1)
|
||||
|
||||
val thread = threads.first()
|
||||
assertThat(thread.root).isEqualTo(thread.id)
|
||||
assertThat(thread.parent).isNull()
|
||||
assertThat(thread.messageId).isEqualTo(message.id)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `replace local message`() {
|
||||
val existingMessageData = buildMessage {
|
||||
multipart("alternative") {
|
||||
bodyPart("text/plain") {
|
||||
textBody("plain")
|
||||
}
|
||||
bodyPart("text/html") {
|
||||
textBody("html")
|
||||
}
|
||||
}
|
||||
}.toSaveMessageData()
|
||||
val existingMessageId = saveMessageOperations.saveLocalMessage(
|
||||
folderId = 1,
|
||||
existingMessageData,
|
||||
existingMessageId = null
|
||||
)
|
||||
val messageData = buildMessage {
|
||||
textBody("new")
|
||||
}.toSaveMessageData()
|
||||
|
||||
val newMessageId = saveMessageOperations.saveLocalMessage(folderId = 1, messageData, existingMessageId)
|
||||
|
||||
val messages = sqliteDatabase.readMessages()
|
||||
assertThat(messages).hasSize(1)
|
||||
|
||||
assertThat(messages.first().id).isEqualTo(newMessageId)
|
||||
|
||||
val messageParts = sqliteDatabase.readMessageParts()
|
||||
assertThat(messageParts).hasSize(1)
|
||||
|
||||
val messagePart = messageParts.first()
|
||||
with(messagePart) {
|
||||
assertThat(type).isEqualTo(MessagePartType.UNKNOWN)
|
||||
assertThat(root).isEqualTo(messagePart.id)
|
||||
assertThat(parent).isEqualTo(-1)
|
||||
assertThat(mimeType).isEqualTo("text/plain")
|
||||
assertThat(displayName).isEqualTo("noname.txt")
|
||||
assertThat(header).isNotNull()
|
||||
assertThat(encoding).isEqualTo("quoted-printable")
|
||||
assertThat(charset).isNull()
|
||||
assertThat(dataLocation).isEqualTo(DataLocation.IN_DATABASE)
|
||||
assertThat(decodedBodySize).isEqualTo(3)
|
||||
assertThat(data?.toString(Charsets.UTF_8)).isEqualTo("new")
|
||||
assertThat(preamble).isNull()
|
||||
assertThat(epilogue).isNull()
|
||||
assertThat(boundary).isNull()
|
||||
assertThat(contentId).isNull()
|
||||
assertThat(serverExtra).isNull()
|
||||
}
|
||||
|
||||
val threads = sqliteDatabase.readThreads()
|
||||
assertThat(threads).hasSize(1)
|
||||
|
||||
val thread = threads.first()
|
||||
val message = messages.first()
|
||||
assertThat(thread.root).isEqualTo(thread.id)
|
||||
assertThat(thread.parent).isNull()
|
||||
assertThat(thread.messageId).isEqualTo(message.id)
|
||||
}
|
||||
|
||||
private fun Message.toSaveMessageData(
|
||||
subject: String? = getSubject(),
|
||||
date: Long = sentDate?.time ?: System.currentTimeMillis(),
|
||||
|
|
Loading…
Reference in a new issue