Skip to content

Commit

Permalink
Added a base implementation of loading threads.| #74
Browse files Browse the repository at this point in the history
  • Loading branch information
DenBond7 committed Aug 5, 2024
1 parent f336092 commit b2d33fb
Show file tree
Hide file tree
Showing 4 changed files with 184 additions and 22 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import com.flowcrypt.email.api.email.EmailUtil
import com.flowcrypt.email.api.email.JavaEmailConstants
import com.flowcrypt.email.api.email.gmail.api.GMailRawAttachmentFilterInputStream
import com.flowcrypt.email.api.email.gmail.api.GMailRawMIMEMessageFilterInputStream
import com.flowcrypt.email.api.email.gmail.model.GmailThreadInfo
import com.flowcrypt.email.api.email.model.AttachmentInfo
import com.flowcrypt.email.api.email.model.LocalFolder
import com.flowcrypt.email.api.retrofit.response.base.Result
Expand Down Expand Up @@ -52,6 +53,7 @@ import com.google.api.services.gmail.model.Draft
import com.google.api.services.gmail.model.History
import com.google.api.services.gmail.model.Label
import com.google.api.services.gmail.model.ListMessagesResponse
import com.google.api.services.gmail.model.ListThreadsResponse
import com.google.api.services.gmail.model.Message
import com.google.api.services.gmail.model.MessagePart
import jakarta.mail.Flags
Expand Down Expand Up @@ -245,6 +247,38 @@ class GmailApiHelper {
return@withContext Base64InputStream(GMailRawMIMEMessageFilterInputStream(message.executeAsInputStream()))
}

suspend fun loadThreads(
context: Context,
accountEntity: AccountEntity,
localFolder: LocalFolder,
maxResult: Long = COUNT_OF_LOADED_EMAILS_BY_STEP,
fields: List<String>? = null,
nextPageToken: String? = null
): ListThreadsResponse = withContext(Dispatchers.IO) {
val gmailApiService = generateGmailApiService(context, accountEntity)
val request = gmailApiService
.users()
.threads()
.list(DEFAULT_USER_ID)
.setPageToken(nextPageToken)
.setMaxResults(maxResult)

fields?.let { fields ->
request.fields = fields.joinToString(separator = ",")
}

if (!localFolder.isAll) {
request.labelIds = listOf(localFolder.fullName)
}

if (accountEntity.showOnlyEncrypted == true) {
request.q =
(EmailUtil.genPgpThingsSearchTerm(accountEntity) as? GmailRawSearchTerm)?.pattern
}

return@withContext request.execute()
}

suspend fun loadMsgsBaseInfo(
context: Context,
accountEntity: AccountEntity,
Expand Down Expand Up @@ -305,6 +339,83 @@ class GmailApiHelper {
}
}

suspend fun loadGmailThreadInfoInParallel(
context: Context,
accountEntity: AccountEntity,
threads: List<com.google.api.services.gmail.model.Thread>,
localFolder: LocalFolder,
format: String = MESSAGE_RESPONSE_FORMAT_FULL,
stepValue: Int = 10
): List<GmailThreadInfo> = withContext(Dispatchers.IO)
{
return@withContext useParallel(list = threads, stepValue = stepValue) { list ->
loadGmailThreadInfo(context, accountEntity, list, localFolder, format)
}
}

suspend fun loadGmailThreadInfo(
context: Context,
accountEntity: AccountEntity,
threads: Collection<com.google.api.services.gmail.model.Thread>,
localFolder: LocalFolder,
format: String = MESSAGE_RESPONSE_FORMAT_FULL,
metadataHeaders: List<String>? = null,
fields: List<String>? = FULL_INFO_WITHOUT_DATA
): List<GmailThreadInfo> = withContext(Dispatchers.IO)
{
val gmailApiService = generateGmailApiService(context, accountEntity)
val batch = gmailApiService.batch()

val listResult = mutableListOf<GmailThreadInfo>()
val isTrash = localFolder.fullName.equals(LABEL_TRASH, true)

for (thread in threads) {
val request = gmailApiService
.users()
.threads()
.get(DEFAULT_USER_ID, thread.id)
.setFormat(format)

metadataHeaders?.let { metadataHeaders ->
request.metadataHeaders = metadataHeaders
}

request.queue(
batch,
object : JsonBatchCallback<com.google.api.services.gmail.model.Thread>() {
override fun onSuccess(
t: com.google.api.services.gmail.model.Thread?,
responseHeaders: HttpHeaders?
) {
t?.let { thread ->
thread.messages?.lastOrNull()?.let {
if (isTrash || it.labelIds?.contains(LABEL_TRASH) != true) {
listResult.add(
GmailThreadInfo(
id = thread.id,
lastMessage = it,
messagesCount = thread.messages?.size ?: 0,
recipients = emptyList(),
subject = it.snippet,
labels = ""
)
)
}
}
} ?: throw java.lang.NullPointerException()
}

override fun onFailure(e: GoogleJsonError?, responseHeaders: HttpHeaders?) {

}
})
}

batch.execute()

return@withContext listResult
}

suspend fun loadMsgs(
context: Context, accountEntity: AccountEntity, messages: Collection<Message>,
localFolder: LocalFolder, format: String = MESSAGE_RESPONSE_FORMAT_FULL,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
/*
* © 2016-present FlowCrypt a.s. Limitations apply. Contact [email protected]
* Contributors: denbond7
*/

package com.flowcrypt.email.api.email.gmail.model

import com.google.api.services.gmail.model.Message
import jakarta.mail.internet.InternetAddress

/**
* @author Denys Bondarenko
*/
data class GmailThreadInfo(
val id: String,
val lastMessage: Message,
val messagesCount: Int,
val recipients: List<InternetAddress>,
val subject: String,
val labels: String
)
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,7 @@ data class AccountEntity(
@ColumnInfo(name = "service_pgp_private_key") val servicePgpPrivateKey: ByteArray,
@ColumnInfo(name = "signature", defaultValue = "NULL") val signature: String? = null,
@ColumnInfo(name = "use_alias_signatures", defaultValue = "0") val useAliasSignatures: Boolean = false,
@ColumnInfo(name = "use_conversation_mode", defaultValue = "0") val useConversationMode: Boolean = false,
) : Parcelable {

@IgnoredOnParcel
Expand Down Expand Up @@ -143,7 +144,8 @@ data class AccountEntity(
useCustomerFesUrl = useCustomerFesUrl,
servicePgpPassphrase = "",
servicePgpPrivateKey = byteArrayOf(),
useAliasSignatures = true
useAliasSignatures = true,
useConversationMode = true
)

constructor(authCredentials: AuthCredentials, clientConfiguration: ClientConfiguration? = null) :
Expand Down Expand Up @@ -305,6 +307,7 @@ data class AccountEntity(
if (!servicePgpPrivateKey.contentEquals(other.servicePgpPrivateKey)) return false
if (signature != other.signature) return false
if (useAliasSignatures != other.useAliasSignatures) return false
if (useConversationMode != other.useConversationMode) return false

return true
}
Expand Down Expand Up @@ -343,6 +346,7 @@ data class AccountEntity(
result = 31 * result + servicePgpPrivateKey.contentHashCode()
result = 31 * result + (signature?.hashCode() ?: 0)
result = 31 * result + useAliasSignatures.hashCode()
result = 31 * result + useConversationMode.hashCode()
return result
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -461,31 +461,57 @@ class MessagesViewModel(application: Application) : AccountViewModel(application
resultCode = R.id.progress_id_gmail_list
)
)
val messagesBaseInfo = GmailApiHelper.loadMsgsBaseInfo(
context = getApplication(),
accountEntity = accountEntity,
localFolder = localFolder,
nextPageToken = if (totalItemsCount > 0) labelEntity?.nextPageToken else null
)

val draftIdsMap = when (messagesBaseInfo) {
is ListDraftsResponse -> messagesBaseInfo.drafts?.associateBy(
{ it.message.id },
{ it.id }) ?: emptyMap()
val messages: List<com.google.api.services.gmail.model.Message>
val nextPageToken: String?
val draftIdsMap: Map<String, String>

else -> emptyMap()
}
if (accountEntity.useConversationMode) {
val threadsResponse = GmailApiHelper.loadThreads(
context = getApplication(),
accountEntity = accountEntity,
localFolder = localFolder,
nextPageToken = if (totalItemsCount > 0) labelEntity?.nextPageToken else null
)

val messages = when (messagesBaseInfo) {
is ListMessagesResponse -> messagesBaseInfo.messages ?: emptyList()
is ListDraftsResponse -> messagesBaseInfo.drafts?.map { it.message } ?: emptyList()
else -> emptyList()
}
val gmailThreadInfoList = GmailApiHelper.loadGmailThreadInfoInParallel(
context = getApplication(),
accountEntity = accountEntity,
threads = threadsResponse.threads,
format = GmailApiHelper.MESSAGE_RESPONSE_FORMAT_MINIMAL,
localFolder = localFolder
)

val nextPageToken = when (messagesBaseInfo) {
is ListMessagesResponse -> messagesBaseInfo.nextPageToken
is ListDraftsResponse -> messagesBaseInfo.nextPageToken
else -> null
messages = gmailThreadInfoList.map { it.lastMessage }
nextPageToken = threadsResponse.nextPageToken
draftIdsMap = emptyMap()//todo-denbond7 fix me
} else{
val messagesBaseInfo = GmailApiHelper.loadMsgsBaseInfo(
context = getApplication(),
accountEntity = accountEntity,
localFolder = localFolder,
nextPageToken = if (totalItemsCount > 0) labelEntity?.nextPageToken else null
)

draftIdsMap = when (messagesBaseInfo) {
is ListDraftsResponse -> messagesBaseInfo.drafts?.associateBy(
{ it.message.id },
{ it.id }) ?: emptyMap()

else -> emptyMap()
}

messages = when (messagesBaseInfo) {
is ListMessagesResponse -> messagesBaseInfo.messages ?: emptyList()
is ListDraftsResponse -> messagesBaseInfo.drafts?.map { it.message } ?: emptyList()
else -> emptyList()
}

nextPageToken = when (messagesBaseInfo) {
is ListMessagesResponse -> messagesBaseInfo.nextPageToken
is ListDraftsResponse -> messagesBaseInfo.nextPageToken
else -> null
}
}

loadMsgsFromRemoteServerLiveData.postValue(
Expand Down

0 comments on commit b2d33fb

Please sign in to comment.