@@ -21,16 +21,162 @@ import com.infomaniak.mail.data.models.message.Message
21
21
import com.infomaniak.mail.data.models.thread.Thread
22
22
import io.realm.kotlin.MutableRealm
23
23
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
24
28
25
29
interface RefreshStrategy {
26
30
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
+ )
28
42
}
29
43
30
44
interface DefaultRefreshStrategy : RefreshStrategy {
31
45
override fun queryFolderThreads (folderId : String , realm : TypedRealm ): List <Thread > {
32
46
return ThreadController .getThreadsByFolderId(folderId, realm)
33
47
}
34
48
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
+ }
36
182
}
0 commit comments