Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: Tidy up createNewThread() #2228

Merged
merged 2 commits into from
Mar 6, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -430,7 +430,7 @@ class RefreshController @Inject constructor(
val upToDateFolder = getUpToDateFolder(folder.id)
val isConversationMode = localSettings.threadMode == ThreadMode.CONVERSATION

return@write createThreads(scope, upToDateFolder, messages, isConversationMode).also {
return@write handleAddedMessages(scope, upToDateFolder, messages, isConversationMode).also {

// TODO: This count will be false for INBOX & SNOOZED when the snooze feature will be implemented
val messagesCount = MessageController.getMessagesCountByFolderId(upToDateFolder.id, realm = this)
Expand Down Expand Up @@ -503,7 +503,7 @@ class RefreshController @Inject constructor(
//endregion

//region Create Threads
private fun MutableRealm.createThreads(
private fun MutableRealm.handleAddedMessages(
scope: CoroutineScope,
folder: Folder,
remoteMessages: List<Message>,
Expand All @@ -521,7 +521,9 @@ class RefreshController @Inject constructor(
addedMessagesUids.add(remoteMessage.shortUid)

val newThread = if (isConversationMode) {
createNewThread(scope, remoteMessage, impactedThreadsManaged)
val (thread, impactThreads) = handleAddedMessage(scope, remoteMessage)
impactedThreadsManaged += impactThreads
thread
} else {
remoteMessage.toThread()
}
Expand All @@ -542,31 +544,6 @@ class RefreshController @Inject constructor(
return impactedThreadsUnmanaged
}

private fun MutableRealm.createNewThread(
scope: CoroutineScope,
remoteMessage: Message,
impactedThreadsManaged: MutableSet<Thread>,
): Thread? {
// Other pre-existing Threads that will also require this Message and will provide the prior Messages for this new Thread.
val existingThreads = ThreadController.getThreadsByMessageIds(remoteMessage.messageIds, realm = this)
val existingMessages = getExistingMessages(existingThreads)

// Some Messages don't have references to all previous Messages of the Thread (ex: these from the iOS Mail app).
// Because we are missing the links between Messages, it will create multiple Threads for the same Folder.
// Hence, we need to find these duplicates.
val isThereDuplicatedThreads = isThereDuplicatedThreads(remoteMessage.messageIds, existingThreads.count())

// Create Thread in this Folder
val thread = createNewThreadIfRequired(scope, remoteMessage, existingThreads, existingMessages)
// Update Threads in other Folders
updateOtherExistingThreads(scope, remoteMessage, existingThreads, existingMessages, impactedThreadsManaged)

// Now that all other existing Threads are updated, we need to remove the duplicated Threads.
if (isThereDuplicatedThreads) removeDuplicatedThreads(remoteMessage.messageIds, impactedThreadsManaged)

return thread
}

private fun initMessageLocalValues(remoteMessage: Message, folder: Folder) {
remoteMessage.initLocalValues(
MessageInitialState(
Expand All @@ -580,27 +557,16 @@ class RefreshController @Inject constructor(
)
}

private fun MutableRealm.isThereDuplicatedThreads(messageIds: RealmSet<String>, threadsCount: Int): Boolean {
val foldersCount = ThreadController.getExistingThreadsFoldersCount(messageIds, realm = this)
return foldersCount != threadsCount.toLong()
}
private fun MutableRealm.handleAddedMessage(scope: CoroutineScope, remoteMessage: Message): Pair<Thread?, Set<Thread>> {

private fun MutableRealm.removeDuplicatedThreads(messageIds: RealmSet<String>, impactedThreadsManaged: MutableSet<Thread>) {
// Other pre-existing Threads that will also require this Message and will provide the prior Messages for this new Thread.
val existingThreads = ThreadController.getThreadsByMessageIds(remoteMessage.messageIds, realm = this)
val existingMessages = getExistingMessages(existingThreads)

// Create a map with all duplicated Threads of the same Thread in a list.
val map = mutableMapOf<String, MutableList<Thread>>()
ThreadController.getThreadsByMessageIds(messageIds, realm = this).forEach {
map.getOrPut(it.folderId) { mutableListOf() }.add(it)
}
val thread = createNewThreadIfRequired(scope, remoteMessage, existingThreads, existingMessages)
val impactedThreads = updateExistingThreads(scope, remoteMessage, existingThreads, existingMessages)

map.values.forEach { threads ->
threads.forEachIndexed { index, thread ->
if (index > 0) { // We want to keep only 1 duplicated Thread, so we skip the 1st one. (He's the chosen one!)
impactedThreadsManaged.remove(thread)
delete(thread) // Delete the other Threads. Sorry bro, you won't be missed.
}
}
}
return thread to impactedThreads
}

private fun TypedRealm.createNewThreadIfRequired(
Expand All @@ -621,33 +587,73 @@ class RefreshController @Inject constructor(
return newThread
}

private fun MutableRealm.updateOtherExistingThreads(
private fun MutableRealm.updateExistingThreads(
scope: CoroutineScope,
remoteMessage: Message,
existingThreads: RealmResults<Thread>,
existingMessages: Set<Message>,
impactedThreadsManaged: MutableSet<Thread>,
) {
if (existingThreads.isEmpty()) return
): Set<Thread> {

val allExistingMessages = mutableSetOf<Message>().apply {
val impactedThreads = mutableSetOf<Thread>()

// Update already existing Threads (i.e. in other Folders, or specific cases like Snoozed)
impactedThreads += addAllMessagesToAllThreads(scope, remoteMessage, existingThreads, existingMessages)

// Some Messages don't have references to all previous Messages of the Thread (ex: these from the iOS Mail app).
// Because we are missing the links between Messages, it will create multiple Threads for the same Folder.
// Hence, we need to find these duplicates, and remove them.
val duplicatedThreads = identifyExtraDuplicatedThreads(remoteMessage.messageIds)
impactedThreads -= duplicatedThreads
duplicatedThreads.forEach(::delete) // Delete the other Threads. Sorry bro, you won't be missed.

return impactedThreads
}

private fun MutableRealm.addAllMessagesToAllThreads(
scope: CoroutineScope,
remoteMessage: Message,
existingThreads: RealmResults<Thread>,
existingMessages: Set<Message>,
): Set<Thread> {

if (existingThreads.isEmpty()) return emptySet()

val allExistingMessages = buildSet {
addAll(existingMessages)
add(remoteMessage)
}

existingThreads.forEach { thread ->
scope.ensureActive()

allExistingMessages.forEach { existingMessage ->
return buildSet {
existingThreads.forEach { thread ->
scope.ensureActive()

if (!thread.messages.contains(existingMessage)) {
thread.messagesIds += existingMessage.messageIds
thread.addMessageWithConditions(existingMessage, realm = this)
allExistingMessages.forEach { existingMessage ->
scope.ensureActive()

if (!thread.messages.contains(existingMessage)) {
thread.messagesIds += existingMessage.messageIds
thread.addMessageWithConditions(existingMessage, realm = this@addAllMessagesToAllThreads)
}
}

add(thread)
}
}
}

private fun MutableRealm.identifyExtraDuplicatedThreads(messageIds: RealmSet<String>): Set<Thread> {

impactedThreadsManaged += thread
// Create a map with all duplicated Threads of the same Thread in a list.
val map = mutableMapOf<String, MutableList<Thread>>()
ThreadController.getThreadsByMessageIds(messageIds, realm = this).forEach {
map.getOrPut(it.folderId) { mutableListOf() }.add(it)
}

return buildSet {
map.values.forEach { threads ->
// We want to keep only 1 duplicated Thread, so we skip the 1st one. (He's the chosen one!)
addAll(threads.subList(1, threads.count()))
}
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,10 +200,6 @@ class ThreadController @Inject constructor(
return realm.query("ANY ${Thread::messagesIds.name} IN $0", messageIds)
}

private fun getExistingThreadsFoldersCountQuery(messageIds: Set<String>, realm: TypedRealm): RealmScalarQuery<Long> {
return getThreadsByMessageIdsQuery(messageIds, realm).distinct(Thread::folderId.name).count()
}

private fun getSearchThreadsQuery(realm: TypedRealm): RealmQuery<Thread> {
return realm.query<Thread>("${Thread::isFromSearch.name} == true").sort(Thread::date.name, Sort.DESCENDING)
}
Expand Down Expand Up @@ -266,10 +262,6 @@ class ThreadController @Inject constructor(
return getThreadsByMessageIdsQuery(messageIds, realm).find()
}

fun getExistingThreadsFoldersCount(messageIds: Set<String>, realm: TypedRealm): Long {
return getExistingThreadsFoldersCountQuery(messageIds, realm).find()
}

fun getUnreadThreadsCount(folder: Folder): Int {
return getUnreadThreadsCountQuery(folder).find().toInt()
}
Expand Down