FetchContactsDataWorker improved handling of CancelableException.

MAILAND-1537
This commit is contained in:
Tomasz Giszczak 2021-02-17 14:28:54 +01:00
parent 8a101a41fe
commit 3dd516841b
7 changed files with 61 additions and 45 deletions

View File

@ -24,6 +24,7 @@ import androidx.test.InstrumentationRegistry
import ch.protonmail.android.api.models.ContactEncryptedData
import ch.protonmail.android.api.models.room.testValue
import ch.protonmail.android.core.Constants
import kotlinx.coroutines.runBlocking
import org.hamcrest.Matchers.`is`
import org.junit.Assert
import org.junit.Ignore
@ -158,8 +159,10 @@ internal class ContactsDatabaseTest {
)
private fun ContactsDatabase.populate() {
saveAllContactsData(contactData)
saveAllContactsEmailsBlocking(contactEmails)
runBlocking {
saveAllContactsData(contactData)
saveAllContactsEmails(contactEmails)
}
fullContactDetails.forEach(this::insertFullContactDetails)
}
@ -233,12 +236,14 @@ internal class ContactsDatabaseTest {
@Test
fun saveAllContactsData() {
val inserted = listOf(
ContactData("y", "yy"), ContactData("z", "zz")
)
val expected = contactData + inserted
database.saveAllContactsData(inserted)
assertDatabaseState(expectedContactData = expected)
runBlocking {
val inserted = listOf(
ContactData("y", "yy"), ContactData("z", "zz")
)
val expected = contactData + inserted
database.saveAllContactsData(inserted)
assertDatabaseState(expectedContactData = expected)
}
}
@Test

View File

@ -216,7 +216,8 @@ class ProtonMailApiManager @Inject constructor(var api: ProtonMailApi) :
override suspend fun pingAsync(): ResponseBody = api.pingAsync()
override suspend fun fetchContacts(page: Int, pageSize: Int): ContactsDataResponse = api.fetchContacts(page, pageSize)
override suspend fun fetchContacts(page: Int, pageSize: Int): ContactsDataResponse =
api.fetchContacts(page, pageSize)
override suspend fun fetchContactEmails(page: Int, pageSize: Int): ContactEmailsResponseV2 =
api.fetchContactEmails(page, pageSize)

View File

@ -63,7 +63,7 @@ interface ContactsDatabase {
fun saveAllContactsData(vararg contactData: ContactData): List<Long>
@Insert(onConflict = OnConflictStrategy.REPLACE)
fun saveAllContactsData(contactData: Collection<ContactData>): List<Long>
suspend fun saveAllContactsData(contactData: Collection<ContactData>): List<Long>
@Delete
fun deleteContactData(vararg contactData: ContactData)

View File

@ -46,12 +46,16 @@ class ContactEmailsManager @Inject constructor(
currentPage++
}
val allContactEmails = allResults.flatMap { it.contactEmails }
val allJoins = getJoins(allContactEmails)
Timber.v(
"Refresh emails: ${allContactEmails.size}, labels: ${contactLabelList.size}, allJoins: ${allJoins.size}"
)
contactsDao.insertNewContactsAndLabels(allContactEmails, contactLabelList, allJoins)
if (allResults.isNotEmpty()) {
val allContactEmails = allResults.flatMap { it.contactEmails }
val allJoins = getJoins(allContactEmails)
Timber.v(
"Refresh emails: ${allContactEmails.size}, labels: ${contactLabelList.size}, allJoins: ${allJoins.size}"
)
contactsDao.insertNewContactsAndLabels(allContactEmails, contactLabelList, allJoins)
} else {
Timber.v("contactEmails result list is empty")
}
}
private fun getJoins(allContactEmails: List<ContactEmail>): List<ContactEmailContactLabelJoin> {

View File

@ -35,9 +35,8 @@ import ch.protonmail.android.api.ProtonMailApiManager
import ch.protonmail.android.api.models.room.contacts.ContactsDao
import ch.protonmail.android.api.segments.TEN_SECONDS
import ch.protonmail.android.core.Constants.CONTACTS_PAGE_SIZE
import kotlinx.coroutines.withContext
import me.proton.core.util.kotlin.DispatcherProvider
import timber.log.Timber
import java.util.concurrent.CancellationException
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -55,30 +54,29 @@ class FetchContactsDataWorker @WorkerInject constructor(
@Assisted context: Context,
@Assisted params: WorkerParameters,
private val api: ProtonMailApiManager,
private val contactsDao: ContactsDao,
private val dispatchers: DispatcherProvider
private val contactsDao: ContactsDao
) : CoroutineWorker(context, params) {
override suspend fun doWork(): Result =
runCatching {
withContext(dispatchers.Io) {
Timber.v("Fetch Contacts Worker started")
var page = 0
var response = api.fetchContacts(page, CONTACTS_PAGE_SIZE)
response.contacts?.let { contacts ->
val total = response.total
var fetched = contacts.size
while (total > fetched) {
++page
response = api.fetchContacts(page, CONTACTS_PAGE_SIZE)
val contactDataList = response.contacts
if (contactDataList.isNullOrEmpty()) {
break
}
contacts.addAll(contactDataList)
fetched = contacts.size
Timber.v("Fetch Contacts Worker started")
var page = 0
var response = api.fetchContacts(page, CONTACTS_PAGE_SIZE)
response.contacts?.let { contacts ->
val total = response.total
var fetched = contacts.size
while (total > fetched) {
++page
response = api.fetchContacts(page, CONTACTS_PAGE_SIZE)
val contactDataList = response.contacts
if (contactDataList.isNullOrEmpty()) {
break
}
contacts.addAll(contactDataList)
fetched = contacts.size
}
if (contacts.isNotEmpty()) {
contactsDao.saveAllContactsData(contacts)
}
}
@ -91,13 +89,18 @@ class FetchContactsDataWorker @WorkerInject constructor(
}
)
private fun shouldReRunOnThrowable(throwable: Throwable): Result =
if (runAttemptCount < MAX_RETRY_COUNT) {
private fun shouldReRunOnThrowable(throwable: Throwable): Result {
if (throwable is CancellationException) {
throw throwable
}
return if (runAttemptCount < MAX_RETRY_COUNT) {
Timber.d(throwable, "Fetch Contacts Worker failure, retrying count: $runAttemptCount")
Result.retry()
} else {
failure(throwable)
}
}
class Enqueuer @Inject constructor(private val workManager: WorkManager) {
fun enqueue(): LiveData<WorkInfo> {

View File

@ -33,6 +33,7 @@ import androidx.work.WorkManager
import androidx.work.WorkerParameters
import ch.protonmail.android.api.segments.contact.ContactEmailsManager
import timber.log.Timber
import java.util.concurrent.CancellationException
import java.util.concurrent.TimeUnit
import javax.inject.Inject
@ -48,8 +49,11 @@ class FetchContactsEmailsWorker @WorkerInject constructor(
onSuccess = {
success()
},
onFailure = {
failure(it)
onFailure = { throwable ->
if (throwable is CancellationException) {
throw throwable
}
failure(throwable)
}
)
}

View File

@ -29,13 +29,12 @@ import ch.protonmail.android.api.models.room.contacts.ContactsDao
import ch.protonmail.android.core.Constants
import io.mockk.MockKAnnotations
import io.mockk.coEvery
import io.mockk.coVerify
import io.mockk.every
import io.mockk.impl.annotations.MockK
import io.mockk.impl.annotations.RelaxedMockK
import io.mockk.mockk
import io.mockk.verify
import kotlinx.coroutines.test.runBlockingTest
import me.proton.core.test.kotlin.TestDispatcherProvider
import me.proton.core.util.android.workmanager.toWorkData
import kotlin.test.BeforeTest
import kotlin.test.Test
@ -60,7 +59,7 @@ class FetchContactsDataWorkerTest {
@BeforeTest
fun setUp() {
MockKAnnotations.init(this)
worker = FetchContactsDataWorker(context, parameters, api, contactsDao, TestDispatcherProvider)
worker = FetchContactsDataWorker(context, parameters, api, contactsDao)
}
@Test
@ -74,14 +73,14 @@ class FetchContactsDataWorkerTest {
every { total } returns contactsList.size
}
coEvery { api.fetchContacts(0, Constants.CONTACTS_PAGE_SIZE) } returns response
every { contactsDao.saveAllContactsData(contactsList) } returns listOf(1)
coEvery { contactsDao.saveAllContactsData(contactsList) } returns listOf(1)
val expected = ListenableWorker.Result.success()
// when
val operationResult = worker.doWork()
// then
verify { contactsDao.saveAllContactsData(contactsList) }
coVerify { contactsDao.saveAllContactsData(contactsList) }
assertEquals(expected, operationResult)
}