@@ -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.architecture.model.LegacyEntityIdFactory
1314import net.thunderbird.core.logging.Logger
1415import net.thunderbird.feature.account.AccountId
@@ -18,8 +19,10 @@ import net.thunderbird.feature.mail.message.Message
1819import net.thunderbird.feature.mail.message.MessageDownloadState
1920import net.thunderbird.feature.mail.message.MessageId
2021import net.thunderbird.feature.mail.message.MessageServerId
22+ import net.thunderbird.feature.mail.message.domain.GetMessageIdCriteria
2123import net.thunderbird.feature.mail.message.domain.MessageLifecycleError
2224import net.thunderbird.feature.mail.message.domain.MessageLifecycleRepository
25+ import net.thunderbird.feature.mail.message.domain.MessageQueryRepository
2326import net.thunderbird.feature.mail.message.mapper.MessageDataMapper
2427import com.fsck.k9.mail.Message as LegacyMessageDto
2528import com.fsck.k9.mail.MessageDownloadState as LegacyMessageDownloadState
@@ -28,6 +31,7 @@ private const val LOG_ID = "[repository][message-lifecycle]"
2831
2932class DefaultMessageLifecycleRepository (
3033 private val logger : Logger ,
34+ private val messageQueryRepository : MessageQueryRepository ,
3135 private val messageStoreManager : MessageStoreManager ,
3236 private val saveMessageDataCreator : SaveMessageDataCreator ,
3337 private val messageMapper : MessageDataMapper <LegacyMessageDto >,
@@ -41,6 +45,12 @@ class DefaultMessageLifecycleRepository(
4145 accountId : AccountId ,
4246 folderId : FolderId ? ,
4347 ): Outcome <MessageId , MessageLifecycleError > = withContext(ioDispatcher) {
48+ val messageServerId = message.serverId
49+ val existingMessageId = verifyMessageExists(folderId, messageServerId, accountId)
50+ if (existingMessageId != null ) {
51+ logger.warn { " $LOG_ID message already exists with id '$existingMessageId '. Use update instead." }
52+ return @withContext Outcome .failure(MessageLifecycleError .MessageAlreadyExists (existingMessageId))
53+ }
4454 logger.debug {
4555 " $LOG_ID creating new message in folder '${folderId ? : " <outbox-id>" } ' for account '$accountId '"
4656 }
@@ -54,11 +64,10 @@ class DefaultMessageLifecycleRepository(
5464 }
5565 logger.verbose { " $LOG_ID assigning message to folder id = $legacyFolderId " }
5666
57- val messageServerId = message.serverId
5867 val messageId = if (messageServerId == null ) {
59- createLocalMessage (messageStore, legacyFolderId, messageData)
68+ upsertLocalMessage (messageStore, legacyFolderId, messageData)
6069 } else {
61- createRemoteMessage (messageStore, legacyFolderId, messageServerId, messageData)
70+ upsertRemoteMessage (messageStore, legacyFolderId, messageServerId, messageData)
6271 }
6372 logger.verbose { " $LOG_ID message created successfully. MessageId = $messageId " }
6473 Outcome .success(messageId)
@@ -67,8 +76,32 @@ class DefaultMessageLifecycleRepository(
6776 override suspend fun update (
6877 message : Message ,
6978 accountId : AccountId ,
70- ): Outcome <MessageId , MessageLifecycleError > {
71- TODO (" Not yet implemented" )
79+ folderId : FolderId ,
80+ ): Outcome <MessageId , MessageLifecycleError > = withContext(ioDispatcher) {
81+ // NOTE: The legacy store resolves remote messages by folder id, server id (messages.uid), so
82+ // message.id is not strictly needed for that branch today. We still require it so the
83+ // update() contract is the same for local and remote messages and stays valid when
84+ // the data source is replaced.
85+ // Revisit upsertRemoteMessage once the new db can update by message id directly.
86+ val messageId = requireNotNull(message.id) { " $LOG_ID The message.id is required to update a message." }
87+ logger.debug {
88+ " $LOG_ID updating message '$messageId ' in folder '$folderId ' for account '$accountId '"
89+ }
90+ logger.verbose { " $LOG_ID message = $message " }
91+ val legacyMessage = messageMapper.toDto(message)
92+ val downloadState = message.downloadState.toLegacyDownloadState()
93+ val messageData = saveMessageDataCreator.createSaveMessageData(legacyMessage, downloadState)
94+ val messageStore = messageStoreManager.getMessageStore(accountId.value.toString())
95+ val legacyFolderId = folderIdLegacyEntityIdFactory.toLegacyId(folderId)
96+ val messageServerId = message.serverId
97+ val updatedMessageId = if (messageServerId == null ) {
98+ val legacyExistingMessageId = messageIdLegacyEntityIdFactory.toLegacyId(messageId)
99+ upsertLocalMessage(messageStore, legacyFolderId, messageData, legacyExistingMessageId)
100+ } else {
101+ upsertRemoteMessage(messageStore, legacyFolderId, messageServerId, messageData)
102+ }
103+ logger.verbose { " $LOG_ID message updated successfully. MessageId = $updatedMessageId " }
104+ Outcome .success(updatedMessageId)
72105 }
73106
74107 override suspend fun move (
@@ -93,21 +126,36 @@ class DefaultMessageLifecycleRepository(
93126 TODO (" Not yet implemented" )
94127 }
95128
96- private fun createLocalMessage (
129+ private suspend fun verifyMessageExists (
130+ folderId : FolderId ? ,
131+ messageServerId : MessageServerId ? ,
132+ accountId : AccountId ,
133+ ): MessageId ? {
134+ return if (folderId != null && messageServerId != null ) {
135+ messageQueryRepository
136+ .findIdByCriteria(accountId, GetMessageIdCriteria (folderId, messageServerId))
137+ .fold(onSuccess = { it }, onFailure = { throw it.throwable })
138+ } else {
139+ null
140+ }
141+ }
142+
143+ private fun upsertLocalMessage (
97144 messageStore : ListenableMessageStore ,
98145 legacyFolderId : Long ,
99146 messageData : SaveMessageData ,
147+ existingLegacyMessageId : Long? = null,
100148 ): MessageId {
101149 logger.verbose { " $LOG_ID creating local message in folder id '$legacyFolderId '" }
102150 val legacyMessageId = messageStore.saveLocalMessage(
103151 folderId = legacyFolderId,
104152 messageData = messageData,
105- existingMessageId = null ,
153+ existingMessageId = existingLegacyMessageId ,
106154 )
107155 return messageIdLegacyEntityIdFactory.of(legacyMessageId)
108156 }
109157
110- private fun createRemoteMessage (
158+ private fun upsertRemoteMessage (
111159 messageStore : ListenableMessageStore ,
112160 legacyFolderId : Long ,
113161 messageServerId : MessageServerId ,
0 commit comments