@@ -9,6 +9,7 @@ import kotlinx.coroutines.Dispatchers
99import kotlinx.coroutines.withContext
1010import net.thunderbird.app.common.feature.mail.message.domain.model.LegacyMessageSource
1111import net.thunderbird.components.core.outcome.Outcome
12+ import net.thunderbird.components.core.outcome.fold
1213import net.thunderbird.core.logging.Logger
1314import net.thunderbird.feature.account.AccountId
1415import net.thunderbird.feature.mail.folder.FolderId
@@ -19,8 +20,10 @@ import net.thunderbird.feature.mail.message.Message
1920import net.thunderbird.feature.mail.message.MessageDownloadState
2021import net.thunderbird.feature.mail.message.MessageId
2122import net.thunderbird.feature.mail.message.MessageServerId
23+ import net.thunderbird.feature.mail.message.domain.GetMessageIdCriteria
2224import net.thunderbird.feature.mail.message.domain.MessageLifecycleError
2325import net.thunderbird.feature.mail.message.domain.MessageLifecycleRepository
26+ import net.thunderbird.feature.mail.message.domain.MessageQueryRepository
2427import net.thunderbird.feature.mail.message.mapper.MessageDataMapper
2528import com.fsck.k9.mail.Message as LegacyMessageDto
2629import com.fsck.k9.mail.MessageDownloadState as LegacyMessageDownloadState
@@ -29,6 +32,7 @@ private const val LOG_ID = "[repository][message-lifecycle]"
2932
3033class DefaultMessageLifecycleRepository (
3134 private val logger : Logger ,
35+ private val messageQueryRepository : MessageQueryRepository ,
3236 private val messageStoreManager : MessageStoreManager ,
3337 private val saveMessageDataCreator : SaveMessageDataCreator ,
3438 private val messageMapper : MessageDataMapper <LegacyMessageDto >,
@@ -40,6 +44,12 @@ class DefaultMessageLifecycleRepository(
4044 accountId : AccountId ,
4145 folderId : FolderId ? ,
4246 ): Outcome <MessageId , MessageLifecycleError > = withContext(ioDispatcher) {
47+ val messageServerId = message.serverId
48+ val existingMessageId = verifyMessageExists(folderId, messageServerId, accountId)
49+ if (existingMessageId != null ) {
50+ logger.warn { " $LOG_ID message already exists with id '$existingMessageId '. Use update instead." }
51+ return @withContext Outcome .failure(MessageLifecycleError .MessageAlreadyExists (existingMessageId))
52+ }
4353 logger.debug {
4454 " $LOG_ID creating new message in folder '${folderId ? : " <outbox-id>" } ' for account '$accountId '"
4555 }
@@ -53,11 +63,10 @@ class DefaultMessageLifecycleRepository(
5363 }
5464 logger.verbose { " $LOG_ID assigning message to folder id = $legacyFolderId " }
5565
56- val messageServerId = message.serverId
5766 val messageId = if (messageServerId == null ) {
58- createLocalMessage (messageStore, legacyFolderId, messageData)
67+ upsertLocalMessage (messageStore, legacyFolderId, messageData)
5968 } else {
60- createRemoteMessage (messageStore, legacyFolderId, messageServerId, messageData)
69+ upsertRemoteMessage (messageStore, legacyFolderId, messageServerId, messageData)
6170 }
6271 logger.verbose { " $LOG_ID message created successfully. MessageId = $messageId " }
6372 Outcome .success(messageId)
@@ -66,8 +75,32 @@ class DefaultMessageLifecycleRepository(
6675 override suspend fun update (
6776 message : Message ,
6877 accountId : AccountId ,
69- ): Outcome <MessageId , MessageLifecycleError > {
70- TODO (" Not yet implemented" )
78+ folderId : FolderId ,
79+ ): Outcome <MessageId , MessageLifecycleError > = withContext(ioDispatcher) {
80+ // NOTE: The legacy store resolves remote messages by folder id, server id (messages.uid), so
81+ // message.id is not strictly needed for that branch today. We still require it so the
82+ // update() contract is the same for local and remote messages and stays valid when
83+ // the data source is replaced.
84+ // Revisit upsertRemoteMessage once the new db can update by message id directly.
85+ val messageId = requireNotNull(message.id) { " $LOG_ID The message.id is required to update a message." }
86+ logger.debug {
87+ " $LOG_ID updating message '$messageId ' in folder '$folderId ' for account '$accountId '"
88+ }
89+ logger.verbose { " $LOG_ID message = $message " }
90+ val legacyMessage = messageMapper.toDto(message)
91+ val downloadState = message.downloadState.toLegacyDownloadState()
92+ val messageData = saveMessageDataCreator.createSaveMessageData(legacyMessage, downloadState)
93+ val messageStore = messageStoreManager.getMessageStore(accountId)
94+ val legacyFolderId = LegacyFolderIdFactory .toLegacyId(folderId)
95+ val messageServerId = message.serverId
96+ val updatedMessageId = if (messageServerId == null ) {
97+ val legacyExistingMessageId = LegacyMessageIdFactory .toLegacyId(messageId)
98+ upsertLocalMessage(messageStore, legacyFolderId, messageData, legacyExistingMessageId)
99+ } else {
100+ upsertRemoteMessage(messageStore, legacyFolderId, messageServerId, messageData)
101+ }
102+ logger.verbose { " $LOG_ID message updated successfully. MessageId = $updatedMessageId " }
103+ Outcome .success(updatedMessageId)
71104 }
72105
73106 override suspend fun move (
@@ -92,21 +125,36 @@ class DefaultMessageLifecycleRepository(
92125 TODO (" Not yet implemented" )
93126 }
94127
95- private fun createLocalMessage (
128+ private suspend fun verifyMessageExists (
129+ folderId : FolderId ? ,
130+ messageServerId : MessageServerId ? ,
131+ accountId : AccountId ,
132+ ): MessageId ? {
133+ return if (folderId != null && messageServerId != null ) {
134+ messageQueryRepository
135+ .findIdByCriteria(accountId, GetMessageIdCriteria (folderId, messageServerId))
136+ .fold(onSuccess = { it }, onFailure = { throw it.throwable })
137+ } else {
138+ null
139+ }
140+ }
141+
142+ private fun upsertLocalMessage (
96143 messageStore : ListenableMessageStore ,
97144 legacyFolderId : Long ,
98145 messageData : SaveMessageData ,
146+ existingLegacyMessageId : Long? = null,
99147 ): MessageId {
100148 logger.verbose { " $LOG_ID creating local message in folder id '$legacyFolderId '" }
101149 val legacyMessageId = messageStore.saveLocalMessage(
102150 folderId = legacyFolderId,
103151 messageData = messageData,
104- existingMessageId = null ,
152+ existingMessageId = existingLegacyMessageId ,
105153 )
106154 return LegacyMessageIdFactory .of(legacyMessageId)
107155 }
108156
109- private fun createRemoteMessage (
157+ private fun upsertRemoteMessage (
110158 messageStore : ListenableMessageStore ,
111159 legacyFolderId : Long ,
112160 messageServerId : MessageServerId ,
0 commit comments