Заготовка
This commit is contained in:
150
client-mobile/chats/data/workers/SendMessageWorker.kt
Normal file
150
client-mobile/chats/data/workers/SendMessageWorker.kt
Normal file
@@ -0,0 +1,150 @@
|
||||
package chats.data.workers
|
||||
|
||||
import android.content.Context
|
||||
import android.util.Log
|
||||
import androidx.hilt.work.HiltWorker
|
||||
import androidx.work.*
|
||||
import chats.data.local.dao.MessageDao
|
||||
import chats.data.local.database.MessageEntity
|
||||
import chats.data.remote.api.ChatApi
|
||||
import chats.data.remote.api.SendMessageRequest
|
||||
import chats.domain.model.MessageStatus
|
||||
import dagger.assisted.Assisted
|
||||
import dagger.assisted.AssistedInject
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.io.IOException
|
||||
import java.net.SocketTimeoutException
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
/**
|
||||
* Worker для отправки сообщений с поддержкой офлайн-режима.
|
||||
* Использует WorkManager для гарантированной доставки при появлении сети.
|
||||
*/
|
||||
@HiltWorker
|
||||
class SendMessageWorker @AssistedInject constructor(
|
||||
@Assisted context: Context,
|
||||
@Assisted params: WorkerParameters,
|
||||
private val messageDao: MessageDao,
|
||||
private val chatApi: ChatApi
|
||||
) : CoroutineWorker(context, params) {
|
||||
|
||||
companion object {
|
||||
const val WORK_TAG = "send_message_worker"
|
||||
const val KEY_LOCAL_ID = "local_id"
|
||||
const val MAX_RETRY_COUNT = 5
|
||||
|
||||
/**
|
||||
* Создает запрос на отправку сообщения через WorkManager
|
||||
*/
|
||||
fun createWorkRequest(localId: String): OneTimeWorkRequest {
|
||||
val constraints = Constraints.Builder()
|
||||
.setRequiredNetworkType(NetworkType.CONNECTED)
|
||||
.build()
|
||||
|
||||
val inputData = workDataOf(KEY_LOCAL_ID to localId)
|
||||
|
||||
return OneTimeWorkRequestBuilder<SendMessageWorker>()
|
||||
.setConstraints(constraints)
|
||||
.setInputData(inputData)
|
||||
.addTag(WORK_TAG)
|
||||
.addTag("$WORK_TAG:$localId")
|
||||
.setBackoffCriteria(
|
||||
BackoffPolicy.EXPONENTIAL,
|
||||
WorkRequest.MIN_BACKOFF_MILLIS,
|
||||
TimeUnit.MILLISECONDS
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun doWork(): Result = withContext(Dispatchers.IO) {
|
||||
val localId = inputData.getString(KEY_LOCAL_ID)
|
||||
?: return@withContext Result.failure()
|
||||
|
||||
Log.d("SendMessageWorker", "Starting work for message: $localId")
|
||||
|
||||
try {
|
||||
// Получаем сообщение из локальной БД
|
||||
val message = messageDao.getMessageByLocalId(localId)
|
||||
?: return@withContext Result.failure(
|
||||
workDataOf("error" to "Message not found: $localId")
|
||||
)
|
||||
|
||||
// Проверяем, не отправлено ли уже
|
||||
if (message.status == MessageStatus.SENT || message.status == MessageStatus.DELIVERED || message.status == MessageStatus.READ) {
|
||||
Log.d("SendMessageWorker", "Message already sent: $localId")
|
||||
return@withContext Result.success()
|
||||
}
|
||||
|
||||
// Проверяем лимит повторных попыток
|
||||
if (message.retryCount >= MAX_RETRY_COUNT) {
|
||||
Log.w("SendMessageWorker", "Max retries reached for message: $localId")
|
||||
messageDao.updateMessageError(
|
||||
localId = localId,
|
||||
status = MessageStatus.FAILED,
|
||||
errorMessage = "Max retry count exceeded",
|
||||
updatedAtMillis = System.currentTimeMillis()
|
||||
)
|
||||
return@withContext Result.failure(
|
||||
workDataOf("error" to "Max retries reached")
|
||||
)
|
||||
}
|
||||
|
||||
// Обновляем статус на SENDING
|
||||
messageDao.updateMessageStatus(
|
||||
localId = localId,
|
||||
status = MessageStatus.SENDING,
|
||||
updatedAtMillis = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
// Выполняем запрос к API с Idempotency-Key
|
||||
val request = SendMessageRequest(
|
||||
content = message.content,
|
||||
type = message.mediaType.lowercase(),
|
||||
replyToId = message.replyToServerId
|
||||
)
|
||||
|
||||
val serverId = chatApi.sendMessage(
|
||||
chatId = message.chatId,
|
||||
request = request,
|
||||
idempotencyKey = message.idempotencyKey
|
||||
)
|
||||
|
||||
// Успешная отправка - обновляем статус и serverId
|
||||
messageDao.updateMessageWithServerId(
|
||||
localId = localId,
|
||||
serverId = serverId,
|
||||
status = MessageStatus.SENT,
|
||||
updatedAtMillis = System.currentTimeMillis()
|
||||
)
|
||||
|
||||
Log.i("SendMessageWorker", "Message sent successfully: local=$localId, server=$serverId")
|
||||
Result.success()
|
||||
|
||||
} catch (e: IOException) {
|
||||
// Сетевая ошибка - повторяем позже
|
||||
Log.w("SendMessageWorker", "Network error for message $localId: ${e.message}")
|
||||
Result.retry()
|
||||
} catch (e: SocketTimeoutException) {
|
||||
Log.w("SendMessageWorker", "Timeout for message $localId: ${e.message}")
|
||||
Result.retry()
|
||||
} catch (e: Exception) {
|
||||
// Остальные ошибки - помечаем как FAILED
|
||||
Log.e("SendMessageWorker", "Error sending message $localId", e)
|
||||
try {
|
||||
messageDao.updateMessageError(
|
||||
localId = localId,
|
||||
status = MessageStatus.FAILED,
|
||||
errorMessage = e.message ?: "Unknown error",
|
||||
updatedAtMillis = System.currentTimeMillis()
|
||||
)
|
||||
} catch (dbError: Exception) {
|
||||
Log.e("SendMessageWorker", "Failed to update error status", dbError)
|
||||
}
|
||||
Result.failure(
|
||||
workDataOf("error" to (e.message ?: "Unknown error"))
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user