Skip to content

Commit ee06b00

Browse files
refactor: Extract handleAddedMessage into its refresh strategy
1 parent 45e5401 commit ee06b00

File tree

3 files changed

+164
-147
lines changed

3 files changed

+164
-147
lines changed

app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/CustomRefreshStrategies.kt

+9-2
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import com.infomaniak.mail.data.models.message.Message
2121
import com.infomaniak.mail.data.models.thread.Thread
2222
import io.realm.kotlin.MutableRealm
2323
import io.realm.kotlin.TypedRealm
24+
import kotlinx.coroutines.CoroutineScope
2425

2526
val defaultRefreshStrategy = object : DefaultRefreshStrategy {}
2627

@@ -35,11 +36,17 @@ val snoozeRefreshStrategy = object : DefaultRefreshStrategy {
3536
return ThreadController.getInboxThreadsWithSnoozeFilter(withSnooze = true, realm = realm)
3637
}
3738

38-
override fun updateExistingMessageWhenAdded(remoteMessage: Message, realm: MutableRealm) {
39-
MessageController.updateMessage(remoteMessage.uid, realm = realm) { localMessage ->
39+
override fun handleAddedMessages(
40+
scope: CoroutineScope,
41+
remoteMessage: Message,
42+
isConversationMode: Boolean,
43+
realm: MutableRealm,
44+
): Set<Thread> = buildSet {
45+
MessageController.updateMessage(remoteMessage.uid, realm) { localMessage ->
4046
localMessage?.snoozeState = remoteMessage.snoozeState
4147
localMessage?.snoozeEndDate = remoteMessage.snoozeEndDate
4248
localMessage?.snoozeAction = remoteMessage.snoozeAction
49+
localMessage?.threads?.let(::addAll)
4350
}
4451
}
4552
}

app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshController.kt

+1-143
Original file line numberDiff line numberDiff line change
@@ -46,9 +46,7 @@ import io.realm.kotlin.Realm
4646
import io.realm.kotlin.TypedRealm
4747
import io.realm.kotlin.ext.copyFromRealm
4848
import io.realm.kotlin.ext.toRealmList
49-
import io.realm.kotlin.query.RealmResults
5049
import io.realm.kotlin.types.RealmList
51-
import io.realm.kotlin.types.RealmSet
5250
import io.sentry.Sentry
5351
import kotlinx.coroutines.*
5452
import okhttp3.OkHttpClient
@@ -518,20 +516,8 @@ class RefreshController @Inject constructor(
518516
scope.ensureActive()
519517

520518
initMessageLocalValues(remoteMessage, folder)
521-
522519
addedMessagesUids.add(remoteMessage.shortUid)
523-
524-
refreshStrategy.updateExistingMessageWhenAdded(remoteMessage, realm = this)
525-
526-
val newThread = if (isConversationMode) {
527-
val (thread, impactThreads) = handleAddedMessage(scope, remoteMessage)
528-
impactedThreadsManaged += impactThreads
529-
thread
530-
} else {
531-
remoteMessage.toThread()
532-
}
533-
534-
newThread?.let { impactedThreadsManaged += putNewThreadInRealm(it) }
520+
impactedThreadsManaged += refreshStrategy.handleAddedMessages(scope, remoteMessage, isConversationMode, realm = this)
535521
}
536522

537523
addSentryBreadcrumbForAddedUidsInFolder(addedMessagesUids)
@@ -547,11 +533,6 @@ class RefreshController @Inject constructor(
547533
return impactedThreadsUnmanaged
548534
}
549535

550-
private fun MutableRealm.updateExistingMessage(remoteMessage: Message) {
551-
val isMessageAlreadyInRealm = MessageController.getMessage(remoteMessage.uid, realm = this) != null
552-
if (isMessageAlreadyInRealm) MessageController.upsertMessage(remoteMessage, realm = this)
553-
}
554-
555536
private fun initMessageLocalValues(remoteMessage: Message, folder: Folder) {
556537
remoteMessage.initLocalValues(
557538
MessageInitialState(
@@ -564,129 +545,6 @@ class RefreshController @Inject constructor(
564545
latestCalendarEventResponse = null,
565546
)
566547
}
567-
568-
private fun MutableRealm.handleAddedMessage(scope: CoroutineScope, remoteMessage: Message): Pair<Thread?, Set<Thread>> {
569-
570-
// Other pre-existing Threads that will also require this Message and will provide the prior Messages for this new Thread.
571-
val existingThreads = ThreadController.getThreadsByMessageIds(remoteMessage.messageIds, realm = this)
572-
val existingMessages = getExistingMessages(existingThreads)
573-
574-
val thread = createNewThreadIfRequired(scope, remoteMessage, existingThreads, existingMessages)
575-
val impactedThreads = updateExistingThreads(scope, remoteMessage, existingThreads, existingMessages)
576-
577-
return thread to impactedThreads
578-
}
579-
580-
private fun TypedRealm.createNewThreadIfRequired(
581-
scope: CoroutineScope,
582-
newMessage: Message,
583-
existingThreads: List<Thread>,
584-
existingMessages: Set<Message>,
585-
): Thread? {
586-
var newThread: Thread? = null
587-
588-
if (existingThreads.none { it.folderId == newMessage.folderId }) {
589-
590-
newThread = newMessage.toThread()
591-
592-
addPreviousMessagesToThread(scope, newThread, existingMessages)
593-
}
594-
595-
return newThread
596-
}
597-
598-
private fun MutableRealm.updateExistingThreads(
599-
scope: CoroutineScope,
600-
remoteMessage: Message,
601-
existingThreads: RealmResults<Thread>,
602-
existingMessages: Set<Message>,
603-
): Set<Thread> {
604-
605-
val impactedThreads = mutableSetOf<Thread>()
606-
607-
// Update already existing Threads (i.e. in other Folders, or specific cases like Snoozed)
608-
impactedThreads += addAllMessagesToAllThreads(scope, remoteMessage, existingThreads, existingMessages)
609-
610-
// Some Messages don't have references to all previous Messages of the Thread (ex: these from the iOS Mail app).
611-
// Because we are missing the links between Messages, it will create multiple Threads for the same Folder.
612-
// Hence, we need to find these duplicates, and remove them.
613-
val duplicatedThreads = identifyExtraDuplicatedThreads(remoteMessage.messageIds)
614-
impactedThreads -= duplicatedThreads
615-
duplicatedThreads.forEach(::delete) // Delete the other Threads. Sorry bro, you won't be missed.
616-
617-
return impactedThreads
618-
}
619-
620-
private fun MutableRealm.addAllMessagesToAllThreads(
621-
scope: CoroutineScope,
622-
remoteMessage: Message,
623-
existingThreads: RealmResults<Thread>,
624-
existingMessages: Set<Message>,
625-
): Set<Thread> {
626-
627-
if (existingThreads.isEmpty()) return emptySet()
628-
629-
val allExistingMessages = buildSet {
630-
addAll(existingMessages)
631-
add(remoteMessage)
632-
}
633-
634-
return buildSet {
635-
existingThreads.forEach { thread ->
636-
scope.ensureActive()
637-
638-
allExistingMessages.forEach { existingMessage ->
639-
scope.ensureActive()
640-
641-
if (!thread.messages.contains(existingMessage)) {
642-
thread.messagesIds += existingMessage.messageIds
643-
thread.addMessageWithConditions(existingMessage, realm = this@addAllMessagesToAllThreads)
644-
}
645-
}
646-
647-
add(thread)
648-
}
649-
}
650-
}
651-
652-
private fun MutableRealm.identifyExtraDuplicatedThreads(messageIds: RealmSet<String>): Set<Thread> {
653-
654-
// Create a map with all duplicated Threads of the same Thread in a list.
655-
val map = mutableMapOf<String, MutableList<Thread>>()
656-
ThreadController.getThreadsByMessageIds(messageIds, realm = this).forEach {
657-
map.getOrPut(it.folderId) { mutableListOf() }.add(it)
658-
}
659-
660-
return buildSet {
661-
map.values.forEach { threads ->
662-
// We want to keep only 1 duplicated Thread, so we skip the 1st one. (He's the chosen one!)
663-
addAll(threads.subList(1, threads.count()))
664-
}
665-
}
666-
}
667-
668-
private fun MutableRealm.putNewThreadInRealm(newThread: Thread): Thread {
669-
return ThreadController.upsertThread(newThread, realm = this)
670-
}
671-
672-
private fun getExistingMessages(existingThreads: List<Thread>): Set<Message> {
673-
return existingThreads.flatMapTo(mutableSetOf()) { it.messages }
674-
}
675-
676-
private fun TypedRealm.addPreviousMessagesToThread(
677-
scope: CoroutineScope,
678-
newThread: Thread,
679-
referenceMessages: Set<Message>,
680-
) {
681-
referenceMessages.forEach { message ->
682-
scope.ensureActive()
683-
684-
newThread.apply {
685-
messagesIds += message.computeMessageIds()
686-
addMessageWithConditions(message, realm = this@addPreviousMessagesToThread)
687-
}
688-
}
689-
}
690548
//endregion
691549

692550
//region API calls

app/src/main/java/com/infomaniak/mail/data/cache/mailboxContent/RefreshStrategies.kt

+154-2
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,168 @@ import com.infomaniak.mail.data.models.message.Message
2121
import com.infomaniak.mail.data.models.thread.Thread
2222
import io.realm.kotlin.MutableRealm
2323
import io.realm.kotlin.TypedRealm
24+
import io.realm.kotlin.query.RealmResults
25+
import io.realm.kotlin.types.RealmSet
26+
import kotlinx.coroutines.CoroutineScope
27+
import kotlinx.coroutines.ensureActive
2428

2529
interface RefreshStrategy {
2630
fun queryFolderThreads(folderId: String, realm: TypedRealm): List<Thread>
27-
fun updateExistingMessageWhenAdded(remoteMessage: Message, realm: MutableRealm)
31+
32+
/**
33+
* The returned Threads should be managed by Realm
34+
*/
35+
fun handleAddedMessages(
36+
scope: CoroutineScope,
37+
remoteMessage: Message,
38+
isConversationMode: Boolean,
39+
realm: MutableRealm,
40+
): Set<Thread>
2841
}
2942

3043
interface DefaultRefreshStrategy : RefreshStrategy {
3144
override fun queryFolderThreads(folderId: String, realm: TypedRealm): List<Thread> {
3245
return ThreadController.getThreadsByFolderId(folderId, realm)
3346
}
3447

35-
override fun updateExistingMessageWhenAdded(remoteMessage: Message, realm: MutableRealm) = Unit
48+
override fun handleAddedMessages(
49+
scope: CoroutineScope,
50+
remoteMessage: Message,
51+
isConversationMode: Boolean,
52+
realm: MutableRealm,
53+
): Set<Thread> {
54+
val impactedThreads = mutableSetOf<Thread>()
55+
val newThread = if (isConversationMode) {
56+
val (thread, otherThreads) = realm.handleAddedMessage(scope, remoteMessage)
57+
impactedThreads += otherThreads
58+
thread
59+
} else {
60+
remoteMessage.toThread()
61+
}
62+
newThread?.let { impactedThreads += realm.putNewThreadInRealm(it) }
63+
return impactedThreads
64+
}
65+
66+
private fun MutableRealm.handleAddedMessage(scope: CoroutineScope, remoteMessage: Message): Pair<Thread?, Set<Thread>> {
67+
68+
// Other pre-existing Threads that will also require this Message and will provide the prior Messages for this new Thread.
69+
val existingThreads = ThreadController.getThreadsByMessageIds(remoteMessage.messageIds, realm = this)
70+
val existingMessages = getExistingMessages(existingThreads)
71+
72+
val thread = createNewThreadIfRequired(scope, remoteMessage, existingThreads, existingMessages)
73+
val impactedThreads = updateExistingThreads(scope, remoteMessage, existingThreads, existingMessages)
74+
75+
return thread to impactedThreads
76+
}
77+
78+
private fun TypedRealm.createNewThreadIfRequired(
79+
scope: CoroutineScope,
80+
newMessage: Message,
81+
existingThreads: List<Thread>,
82+
existingMessages: Set<Message>,
83+
): Thread? {
84+
var newThread: Thread? = null
85+
86+
if (existingThreads.none { it.folderId == newMessage.folderId }) {
87+
88+
newThread = newMessage.toThread()
89+
90+
addPreviousMessagesToThread(scope, newThread, existingMessages)
91+
}
92+
93+
return newThread
94+
}
95+
96+
private fun MutableRealm.updateExistingThreads(
97+
scope: CoroutineScope,
98+
remoteMessage: Message,
99+
existingThreads: RealmResults<Thread>,
100+
existingMessages: Set<Message>,
101+
): Set<Thread> {
102+
103+
val impactedThreads = mutableSetOf<Thread>()
104+
105+
// Update already existing Threads (i.e. in other Folders, or specific cases like Snoozed)
106+
impactedThreads += addAllMessagesToAllThreads(scope, remoteMessage, existingThreads, existingMessages)
107+
108+
// Some Messages don't have references to all previous Messages of the Thread (ex: these from the iOS Mail app).
109+
// Because we are missing the links between Messages, it will create multiple Threads for the same Folder.
110+
// Hence, we need to find these duplicates, and remove them.
111+
val duplicatedThreads = identifyExtraDuplicatedThreads(remoteMessage.messageIds)
112+
impactedThreads -= duplicatedThreads
113+
duplicatedThreads.forEach(::delete) // Delete the other Threads. Sorry bro, you won't be missed.
114+
115+
return impactedThreads
116+
}
117+
118+
private fun MutableRealm.addAllMessagesToAllThreads(
119+
scope: CoroutineScope,
120+
remoteMessage: Message,
121+
existingThreads: RealmResults<Thread>,
122+
existingMessages: Set<Message>,
123+
): Set<Thread> {
124+
125+
if (existingThreads.isEmpty()) return emptySet()
126+
127+
val allExistingMessages = buildSet {
128+
addAll(existingMessages)
129+
add(remoteMessage)
130+
}
131+
132+
return buildSet {
133+
existingThreads.forEach { thread ->
134+
scope.ensureActive()
135+
136+
allExistingMessages.forEach { existingMessage ->
137+
scope.ensureActive()
138+
139+
if (!thread.messages.contains(existingMessage)) {
140+
thread.messagesIds += existingMessage.messageIds
141+
thread.addMessageWithConditions(existingMessage, realm = this@addAllMessagesToAllThreads)
142+
}
143+
}
144+
145+
add(thread)
146+
}
147+
}
148+
}
149+
150+
private fun MutableRealm.identifyExtraDuplicatedThreads(messageIds: RealmSet<String>): Set<Thread> {
151+
152+
// Create a map with all duplicated Threads of the same Thread in a list.
153+
val map = mutableMapOf<String, MutableList<Thread>>()
154+
ThreadController.getThreadsByMessageIds(messageIds, realm = this).forEach {
155+
map.getOrPut(it.folderId) { mutableListOf() }.add(it)
156+
}
157+
158+
return buildSet {
159+
map.values.forEach { threads ->
160+
// We want to keep only 1 duplicated Thread, so we skip the 1st one. (He's the chosen one!)
161+
addAll(threads.subList(1, threads.count()))
162+
}
163+
}
164+
}
165+
166+
private fun MutableRealm.putNewThreadInRealm(newThread: Thread): Thread {
167+
return ThreadController.upsertThread(newThread, realm = this)
168+
}
169+
170+
private fun getExistingMessages(existingThreads: List<Thread>): Set<Message> {
171+
return existingThreads.flatMapTo(mutableSetOf()) { it.messages }
172+
}
173+
174+
private fun TypedRealm.addPreviousMessagesToThread(
175+
scope: CoroutineScope,
176+
newThread: Thread,
177+
referenceMessages: Set<Message>,
178+
) {
179+
referenceMessages.forEach { message ->
180+
scope.ensureActive()
181+
182+
newThread.apply {
183+
messagesIds += message.computeMessageIds()
184+
addMessageWithConditions(message, realm = this@addPreviousMessagesToThread)
185+
}
186+
}
187+
}
36188
}

0 commit comments

Comments
 (0)