Converted PostDeleteJob into DeleteMessageWorker.

MAILAND-953
This commit is contained in:
Tomasz Giszczak 2020-09-16 15:45:12 +02:00
parent 82cd0cc296
commit 12a77416b6
16 changed files with 300 additions and 41 deletions

View File

@ -0,0 +1,55 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.worker
import android.content.Context
import androidx.test.platform.app.InstrumentationRegistry
import androidx.work.ListenableWorker
import androidx.work.testing.TestListenableWorkerBuilder
import androidx.work.workDataOf
import org.junit.Before
import org.junit.Test
import kotlin.test.assertEquals
class DeleteMessageWorkerTest {
private lateinit var context: Context
@Before
fun setUp() {
context = InstrumentationRegistry.getInstrumentation().targetContext
}
@Test
fun verifyWorkerFailsWithNoMessageIdsProvided() {
// given
val worker =
TestListenableWorkerBuilder<DeleteMessageWorker>(context).build()
val expected = ListenableWorker.Result.failure(
workDataOf(KEY_WORKER_ERROR_DESCRIPTION to "Cannot proceed with empty messages list")
)
// when
val result = worker.startWork().get()
// then
assertEquals(expected, result)
}
}

View File

@ -47,12 +47,6 @@ import com.birbit.android.jobqueue.JobManager;
import com.google.android.material.snackbar.Snackbar;
import com.squareup.otto.Subscribe;
import ch.protonmail.android.activities.mailbox.MailboxActivity;
import ch.protonmail.android.activities.multiuser.AccountManagerActivity;
import ch.protonmail.android.events.ForceSwitchedAccountNotifier;
import ch.protonmail.android.worker.FetchUserInfoWorker;
import timber.log.Timber;
import javax.inject.Inject;
import butterknife.BindView;
@ -76,6 +70,7 @@ import ch.protonmail.android.core.NetworkResults;
import ch.protonmail.android.core.ProtonMailApplication;
import ch.protonmail.android.core.QueueNetworkUtil;
import ch.protonmail.android.core.UserManager;
import ch.protonmail.android.events.ForceSwitchedAccountNotifier;
import ch.protonmail.android.events.LogoutEvent;
import ch.protonmail.android.events.MessageSentEvent;
import ch.protonmail.android.events.Status;
@ -88,7 +83,9 @@ import ch.protonmail.android.utils.CustomLocale;
import ch.protonmail.android.utils.INetworkConfiguratorCallback;
import ch.protonmail.android.utils.UiUtil;
import ch.protonmail.android.utils.extensions.TextExtensions;
import ch.protonmail.android.worker.FetchUserInfoWorker;
import dagger.hilt.android.AndroidEntryPoint;
import timber.log.Timber;
import static ch.protonmail.android.receivers.VerificationOnSendReceiver.EXTRA_MESSAGE_ADDRESS_ID;
import static ch.protonmail.android.receivers.VerificationOnSendReceiver.EXTRA_MESSAGE_ID;

View File

@ -153,7 +153,6 @@ import ch.protonmail.android.jobs.FetchByLocationJob
import ch.protonmail.android.jobs.FetchLabelsJob
import ch.protonmail.android.jobs.PingJob
import ch.protonmail.android.jobs.PostArchiveJob
import ch.protonmail.android.jobs.PostDeleteJob
import ch.protonmail.android.jobs.PostInboxJob
import ch.protonmail.android.jobs.PostLabelJob
import ch.protonmail.android.jobs.PostReadJob
@ -178,6 +177,7 @@ import ch.protonmail.android.utils.ui.dialogs.DialogUtils.Companion.showInfoDial
import ch.protonmail.android.utils.ui.dialogs.DialogUtils.Companion.showSignedInSnack
import ch.protonmail.android.utils.ui.dialogs.DialogUtils.Companion.showUndoSnackbar
import ch.protonmail.android.utils.ui.selection.SelectionModeEnum
import ch.protonmail.android.worker.DeleteMessageWorker
import com.birbit.android.jobqueue.Job
import com.google.android.gms.common.ConnectionResult
import com.google.android.gms.common.GoogleApiAvailability
@ -1344,7 +1344,7 @@ class MailboxActivity : NavigationActivity(),
getString(R.string.delete_messages),
getString(R.string.confirm_destructive_action)
) {
mJobManager.addJobInBackground(PostDeleteJob(messageIds))
DeleteMessageWorker.Enqueuer(workManager).enqueue(messageIds)
mode.finish()
}
R.id.mark_read -> job = PostReadJob(messageIds)

View File

@ -20,17 +20,19 @@ package ch.protonmail.android.adapters.swipe
import ch.protonmail.android.api.models.SimpleMessage
import ch.protonmail.android.core.Constants
import ch.protonmail.android.jobs.*
import ch.protonmail.android.jobs.MoveToFolderJob
import ch.protonmail.android.jobs.PostArchiveJob
import ch.protonmail.android.jobs.PostDraftJob
import ch.protonmail.android.jobs.PostInboxJob
import ch.protonmail.android.jobs.PostTrashJob
import com.birbit.android.jobqueue.Job
/**
* Created by dkadrikj on 9.7.15.
*/
class TrashSwipeHandler : ISwipeHandler {
override fun handleSwipe(message: SimpleMessage, currentLocation: String?): Job {
return if (Constants.MessageLocationType.fromInt(message.location) == Constants.MessageLocationType.DRAFT) {
PostDeleteJob(listOf(message.messageId))
throw IllegalStateException("Migrate to PostDeleteWorker")
//PostDeleteJob(listOf(message.messageId))
} else {
PostTrashJob(listOf(message.messageId), currentLocation)
}

View File

@ -267,7 +267,7 @@ class ProtonMailApiManager @Inject constructor(var api: ProtonMailApi)
override fun markMessageAsUnRead(messageIds: IDList) = api.markMessageAsUnRead(messageIds)
override fun deleteMessage(messageIds: IDList) = api.deleteMessage(messageIds)
override suspend fun deleteMessage(messageIds: IDList) = api.deleteMessage(messageIds)
override fun emptyDrafts() = api.emptyDrafts()

View File

@ -152,4 +152,4 @@ class ProxyOkHttpClient(
override fun getAcceptedIssuers(): Array<X509Certificate?>? = arrayOfNulls(0)
}
}
}

View File

@ -24,10 +24,6 @@ import java.util.List;
import ch.protonmail.android.api.utils.Fields;
/**
* Created by dkadrikj on 8/28/16.
*/
public class DeleteContactResponse {
@SerializedName(Fields.Response.CODE)

View File

@ -0,0 +1,30 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.api.models
import ch.protonmail.android.api.utils.Fields
import com.google.gson.annotations.SerializedName
data class DeleteMessageResponse(
@SerializedName(Fields.Response.CODE)
val code: Int,
@SerializedName(Fields.Response.RESPONSES)
val responses: List<DeleteContactResponse.Response>
)

View File

@ -21,9 +21,6 @@ package ch.protonmail.android.api.segments.key
import androidx.annotation.WorkerThread
import ch.protonmail.android.api.models.*
import ch.protonmail.android.api.models.address.KeyActivationBody
import retrofit2.Call
import retrofit2.http.Body
import retrofit2.http.Path
import java.io.IOException
@ -44,4 +41,4 @@ interface KeyApiSpec {
@Throws(IOException::class)
fun setupKeys(keysSetupBody: KeysSetupBody): UserInfo
}
}

View File

@ -20,21 +20,21 @@ package ch.protonmail.android.api.segments.message
import androidx.annotation.WorkerThread
import ch.protonmail.android.api.interceptors.RetrofitTag
import ch.protonmail.android.api.segments.BaseApi
import ch.protonmail.android.api.models.IDList
import ch.protonmail.android.api.models.MoveToFolderResponse
import ch.protonmail.android.api.models.NewMessage
import ch.protonmail.android.api.models.UnreadTotalMessagesResponse
import ch.protonmail.android.api.models.messages.receive.MessageResponse
import ch.protonmail.android.api.models.messages.receive.MessagesResponse
import ch.protonmail.android.api.models.messages.send.MessageSendBody
import ch.protonmail.android.api.models.messages.send.MessageSendResponse
import ch.protonmail.android.core.Constants
import retrofit2.Call
import java.io.IOException
import ch.protonmail.android.api.models.messages.receive.MessageResponse
import ch.protonmail.android.api.segments.BaseApi
import ch.protonmail.android.api.utils.ParseUtils
import ch.protonmail.android.core.Constants
import io.reactivex.Observable
import retrofit2.Call
import timber.log.Timber
import java.io.IOException
class MessageApi(private val service: MessageService) : BaseApi(), MessageApiSpec {
@ -70,10 +70,7 @@ class MessageApi(private val service: MessageService) : BaseApi(), MessageApiSpe
service.unRead(messageIds).execute()
}
@Throws(IOException::class)
override fun deleteMessage(messageIds: IDList) {
service.delete(messageIds).execute()
}
override suspend fun deleteMessage(messageIds: IDList) = service.delete(messageIds)
@Throws(IOException::class)
override fun emptyDrafts() {

View File

@ -20,6 +20,7 @@ package ch.protonmail.android.api.segments.message
import androidx.annotation.WorkerThread
import ch.protonmail.android.api.interceptors.RetrofitTag
import ch.protonmail.android.api.models.DeleteContactResponse
import ch.protonmail.android.api.models.IDList
import ch.protonmail.android.api.models.MoveToFolderResponse
import ch.protonmail.android.api.models.NewMessage
@ -55,8 +56,7 @@ interface MessageApiSpec {
@Throws(IOException::class)
fun markMessageAsUnRead(messageIds: IDList)
@Throws(IOException::class)
fun deleteMessage(messageIds: IDList)
suspend fun deleteMessage(messageIds: IDList): DeleteContactResponse
@Throws(IOException::class)
fun emptyDrafts()

View File

@ -19,6 +19,7 @@
package ch.protonmail.android.api.segments.message
import ch.protonmail.android.api.interceptors.RetrofitTag
import ch.protonmail.android.api.models.DeleteContactResponse
import ch.protonmail.android.api.models.IDList
import ch.protonmail.android.api.models.MoveToFolderResponse
import ch.protonmail.android.api.models.NewMessage
@ -32,7 +33,15 @@ import ch.protonmail.android.api.segments.RetrofitConstants.CONTENT_TYPE
import io.reactivex.Observable
import okhttp3.ResponseBody
import retrofit2.Call
import retrofit2.http.*
import retrofit2.http.Body
import retrofit2.http.DELETE
import retrofit2.http.GET
import retrofit2.http.Headers
import retrofit2.http.POST
import retrofit2.http.PUT
import retrofit2.http.Path
import retrofit2.http.Query
import retrofit2.http.Tag
interface MessageService {
@ -42,7 +51,7 @@ interface MessageService {
@PUT("mail/v4/messages/delete")
@Headers(CONTENT_TYPE, ACCEPT_HEADER_V1)
fun delete(@Body messageIds: IDList): Call<ResponseBody>
suspend fun delete(@Body messageIds: IDList): DeleteContactResponse
@PUT("mail/v4/messages/read")
@Headers(CONTENT_TYPE, ACCEPT_HEADER_V1)

View File

@ -58,8 +58,6 @@ import kotlinx.coroutines.withContext
import javax.inject.Inject
import javax.inject.Named
/**
* Created by kadrikj on 9/17/18. */
class ComposeMessageRepository @Inject constructor(
val jobManager: JobManager,
val api: ProtonMailApiManager,
@ -184,7 +182,8 @@ class ComposeMessageRepository @Inject constructor(
}
fun startPostDelete(messageId: String) {
jobManager.addJobInBackground(PostDeleteJob(listOf(messageId)))
throw IllegalStateException("Migrate to PostDeleteWorker")
///jobManager.addJobInBackground(PostDeleteJob(listOf(messageId)))
}
fun startPostHumanVerification(tokenType: Constants.TokenType, token: String) {

View File

@ -21,4 +21,5 @@ package ch.protonmail.android.events
/**
* Created by sunny on 7/20/15.
*/
@Deprecated("Not in use with new Workes")
class MessageDeletedEvent(val notDeletedMessages: List<String>)

View File

@ -77,8 +77,6 @@ import static ch.protonmail.android.servers.notification.NotificationServerKt.NO
public class AppUtil {
private static final String TAG_APP_UTIL = "AppUtil";
private static final int BUFFER_SIZE = 4096;
private AppUtil() {

View File

@ -0,0 +1,178 @@
/*
* Copyright (c) 2020 Proton Technologies AG
*
* This file is part of ProtonMail.
*
* ProtonMail is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* ProtonMail is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with ProtonMail. If not, see https://www.gnu.org/licenses/.
*/
package ch.protonmail.android.worker
import android.content.Context
import androidx.work.Constraints
import androidx.work.CoroutineWorker
import androidx.work.NetworkType
import androidx.work.OneTimeWorkRequestBuilder
import androidx.work.Operation
import androidx.work.WorkManager
import androidx.work.WorkerParameters
import androidx.work.workDataOf
import ch.protonmail.android.activities.messageDetails.repository.MessageDetailsRepository
import ch.protonmail.android.api.ProtonMailApiManager
import ch.protonmail.android.api.models.IDList
import ch.protonmail.android.api.models.room.pendingActions.PendingActionsDao
import ch.protonmail.android.core.Constants
import ch.protonmail.android.utils.extensions.app
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import timber.log.Timber
import javax.inject.Inject
internal const val KEY_WORKER_ERROR_DESCRIPTION = "KeyWorkerErrorDescription"
internal const val KEY_INPUT_DATA_MESSAGE_IDS = "KeyInputDataMessageIds"
internal const val KEY_INVALID_MESSAGE_IDS_RESULT = "KeyInvalidMessageIdsResult"
private const val WORKER_TAG = "PostDeleteWorkerTag"
/**
* Work Manager Worker responsible for deleting messages.
*
* InputData has to contain non-null values for:
* labelId
*
* @see androidx.work.WorkManager
*/
class DeleteMessageWorker(
context: Context,
params: WorkerParameters
) : CoroutineWorker(context, params) {
@Inject
internal lateinit var api: ProtonMailApiManager
@Inject
internal lateinit var pendingActionsDatabase: PendingActionsDao
@Inject
internal lateinit var messageDetailsRepository: MessageDetailsRepository
init {
context.app.appComponent.inject(this)
}
override suspend fun doWork(): Result {
val messageIds = inputData.getStringArray(KEY_INPUT_DATA_MESSAGE_IDS)
?: emptyArray()
// skip empty input
if (messageIds.isEmpty()) {
return Result.failure(
workDataOf(KEY_WORKER_ERROR_DESCRIPTION to "Cannot proceed with empty messages list")
)
}
val validAndInvalidMessagesPair = getValidAndInvalidMessages(messageIds)
val validMessageIdList = validAndInvalidMessagesPair.first
val invalidMessageIdList = validAndInvalidMessagesPair.second
return withContext(Dispatchers.IO) {
// delete messages on remote
if (validMessageIdList.isNotEmpty()) {
val response = api.deleteMessage(IDList(validMessageIdList))
if (response.code == Constants.RESPONSE_CODE_OK ||
response.code == Constants.RESPONSE_CODE_MULTIPLE_OK
) {
Timber.v("Response success code ${response.code}")
updateDb(validMessageIdList)
getInvalidMessagesResult(invalidMessageIdList)
} else {
Timber.v("ApiException failure response code ${response.code}")
Result.failure(
workDataOf(KEY_WORKER_ERROR_DESCRIPTION to "ApiException response code ${response.code}")
)
}
}
getInvalidMessagesResult(invalidMessageIdList)
}
}
private fun getInvalidMessagesResult(invalidMessageIdList: List<String>): Result {
return if (invalidMessageIdList.isNotEmpty()) {
Timber.v("ApiException failure invalidMessageIdList not empty")
Result.failure(
workDataOf(KEY_INVALID_MESSAGE_IDS_RESULT to invalidMessageIdList)
)
} else {
Result.success()
}
}
private fun updateDb(validMessageIdList: List<String>) {
for (id in validMessageIdList) {
val message = messageDetailsRepository.findMessageById(id)
val searchMessage = messageDetailsRepository.findSearchMessageById(id)
if (message != null) {
message.deleted = true
messageDetailsRepository.saveMessageInDB(message)
}
if (searchMessage != null) {
searchMessage.deleted = true
messageDetailsRepository.saveSearchMessageInDB(searchMessage)
}
}
}
private fun getValidAndInvalidMessages(messageIds: Array<String>): Pair<List<String>, List<String>> {
val validMessageIdList = mutableListOf<String>()
val invalidMessageIdList = mutableListOf<String>()
for (id in messageIds) {
if (id.isEmpty()) {
continue
}
val pendingUploads = pendingActionsDatabase.findPendingUploadByMessageId(id)
val pendingForSending = pendingActionsDatabase.findPendingSendByMessageId(id)
if (pendingUploads == null && (pendingForSending == null || pendingForSending.sent == false)) {
// do the logic below if there is no pending upload and not pending send for the message
// trying to be deleted
// or if there is a failed pending send expressed by value `false` in the nullable Sent
// property of the PendingSend class
validMessageIdList.add(id)
} else {
invalidMessageIdList.add(id)
}
}
return validMessageIdList to invalidMessageIdList
}
class Enqueuer(private val workManager: WorkManager) {
fun enqueue(messageIds: List<String>): Operation {
val constraints = Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED)
.build()
val workRequest = OneTimeWorkRequestBuilder<DeleteMessageWorker>()
.setConstraints(constraints)
.setInputData(workDataOf(KEY_INPUT_DATA_MESSAGE_IDS to messageIds.toTypedArray()))
.addTag(WORKER_TAG)
.build()
Timber.v("Scheduling PostDeleteWorker")
return workManager.enqueue(workRequest)
}
fun getWorkStatusLiveData() = workManager.getWorkInfosByTagLiveData(WORKER_TAG)
}
}