Skip to content

Commit 3764d9a

Browse files
refactor: Extract handleAddedMessage into its refresh strategy
1 parent b0fc395 commit 3764d9a

File tree

3 files changed

+164
-139
lines changed

3 files changed

+164
-139
lines changed

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

+15-5
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,20 @@ 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 ->
40-
localMessage?.snoozeState = remoteMessage.snoozeState
41-
localMessage?.snoozeEndDate = remoteMessage.snoozeEndDate
42-
localMessage?.snoozeAction = remoteMessage.snoozeAction
39+
override fun handleAddedMessages(
40+
scope: CoroutineScope,
41+
remoteMessage: Message,
42+
isConversationMode: Boolean,
43+
impactedThreadsManaged: MutableSet<Thread>,
44+
realm: MutableRealm,
45+
) {
46+
impactedThreadsManaged += buildSet {
47+
MessageController.updateMessage(remoteMessage.uid, realm) { localMessage ->
48+
localMessage?.snoozeState = remoteMessage.snoozeState
49+
localMessage?.snoozeEndDate = remoteMessage.snoozeEndDate
50+
localMessage?.snoozeAction = remoteMessage.snoozeAction
51+
localMessage?.threads?.let(::addAll)
52+
}
4353
}
4454
}
4555
}

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

+1-132
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,18 +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-
handleAddedMessage(scope, remoteMessage, impactedThreadsManaged)
528-
} else {
529-
remoteMessage.toThread()
530-
}
531-
532-
newThread?.let { impactedThreadsManaged += putNewThreadInRealm(it) }
520+
refreshStrategy.handleAddedMessages(scope, remoteMessage, isConversationMode, impactedThreadsManaged, realm = this)
533521
}
534522

535523
addSentryBreadcrumbForAddedUidsInFolder(addedMessagesUids)
@@ -557,125 +545,6 @@ class RefreshController @Inject constructor(
557545
latestCalendarEventResponse = null,
558546
)
559547
}
560-
561-
private fun MutableRealm.handleAddedMessage(
562-
scope: CoroutineScope,
563-
remoteMessage: Message,
564-
impactedThreadsManaged: MutableSet<Thread>,
565-
): Thread? {
566-
// Other pre-existing Threads that will also require this Message and will provide the prior Messages for this new Thread.
567-
val existingThreads = ThreadController.getThreadsByMessageIds(remoteMessage.messageIds, realm = this)
568-
val existingMessages = getExistingMessages(existingThreads)
569-
570-
// Some Messages don't have references to all previous Messages of the Thread (ex: these from the iOS Mail app).
571-
// Because we are missing the links between Messages, it will create multiple Threads for the same Folder.
572-
// Hence, we need to find these duplicates.
573-
val isThereDuplicatedThreads = isThereDuplicatedThreads(remoteMessage.messageIds, existingThreads.count())
574-
575-
// Create Thread in this Folder
576-
val thread = createNewThreadIfRequired(scope, remoteMessage, existingThreads, existingMessages)
577-
// Update Threads in other Folders
578-
addAllMessagesToAllThreads(scope, remoteMessage, existingThreads, existingMessages, impactedThreadsManaged)
579-
580-
// Now that all other existing Threads are updated, we need to remove the duplicated Threads.
581-
if (isThereDuplicatedThreads) removeDuplicatedThreads(remoteMessage.messageIds, impactedThreadsManaged)
582-
583-
return thread
584-
}
585-
586-
private fun MutableRealm.isThereDuplicatedThreads(messageIds: RealmSet<String>, threadsCount: Int): Boolean {
587-
val foldersCount = ThreadController.getExistingThreadsFoldersCount(messageIds, realm = this)
588-
return foldersCount != threadsCount.toLong()
589-
}
590-
591-
private fun TypedRealm.createNewThreadIfRequired(
592-
scope: CoroutineScope,
593-
newMessage: Message,
594-
existingThreads: List<Thread>,
595-
existingMessages: Set<Message>,
596-
): Thread? {
597-
var newThread: Thread? = null
598-
599-
if (existingThreads.none { it.folderId == newMessage.folderId }) {
600-
601-
newThread = newMessage.toThread()
602-
603-
addPreviousMessagesToThread(scope, newThread, existingMessages)
604-
}
605-
606-
return newThread
607-
}
608-
609-
private fun MutableRealm.addAllMessagesToAllThreads(
610-
scope: CoroutineScope,
611-
remoteMessage: Message,
612-
existingThreads: RealmResults<Thread>,
613-
existingMessages: Set<Message>,
614-
impactedThreadsManaged: MutableSet<Thread>,
615-
) {
616-
if (existingThreads.isEmpty()) return
617-
618-
val allExistingMessages = mutableSetOf<Message>().apply {
619-
addAll(existingMessages)
620-
add(remoteMessage)
621-
}
622-
623-
existingThreads.forEach { thread ->
624-
scope.ensureActive()
625-
626-
allExistingMessages.forEach { existingMessage ->
627-
scope.ensureActive()
628-
629-
if (!thread.messages.contains(existingMessage)) {
630-
thread.messagesIds += existingMessage.messageIds
631-
thread.addMessageWithConditions(existingMessage, realm = this)
632-
}
633-
}
634-
635-
impactedThreadsManaged += thread
636-
}
637-
}
638-
639-
private fun MutableRealm.removeDuplicatedThreads(messageIds: RealmSet<String>, impactedThreadsManaged: MutableSet<Thread>) {
640-
641-
// Create a map with all duplicated Threads of the same Thread in a list.
642-
val map = mutableMapOf<String, MutableList<Thread>>()
643-
ThreadController.getThreadsByMessageIds(messageIds, realm = this).forEach {
644-
map.getOrPut(it.folderId) { mutableListOf() }.add(it)
645-
}
646-
647-
map.values.forEach { threads ->
648-
threads.forEachIndexed { index, thread ->
649-
if (index > 0) { // We want to keep only 1 duplicated Thread, so we skip the 1st one. (He's the chosen one!)
650-
impactedThreadsManaged.remove(thread)
651-
delete(thread) // Delete the other Threads. Sorry bro, you won't be missed.
652-
}
653-
}
654-
}
655-
}
656-
657-
private fun MutableRealm.putNewThreadInRealm(newThread: Thread): Thread {
658-
return ThreadController.upsertThread(newThread, realm = this)
659-
}
660-
661-
private fun getExistingMessages(existingThreads: List<Thread>): Set<Message> {
662-
return existingThreads.flatMapTo(mutableSetOf()) { it.messages }
663-
}
664-
665-
private fun TypedRealm.addPreviousMessagesToThread(
666-
scope: CoroutineScope,
667-
newThread: Thread,
668-
referenceMessages: Set<Message>,
669-
) {
670-
referenceMessages.forEach { message ->
671-
scope.ensureActive()
672-
673-
newThread.apply {
674-
messagesIds += message.computeMessageIds()
675-
addMessageWithConditions(message, realm = this@addPreviousMessagesToThread)
676-
}
677-
}
678-
}
679548
//endregion
680549

681550
//region API calls

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

+148-2
Original file line numberDiff line numberDiff line change
@@ -21,16 +21,162 @@ 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+
impactedThreadsManaged: MutableSet<Thread>,
40+
realm: MutableRealm,
41+
)
2842
}
2943

3044
interface DefaultRefreshStrategy : RefreshStrategy {
3145
override fun queryFolderThreads(folderId: String, realm: TypedRealm): List<Thread> {
3246
return ThreadController.getThreadsByFolderId(folderId, realm)
3347
}
3448

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

0 commit comments

Comments
 (0)