Files
forkmessager/client-mobile/chats/data/workers/SendMessageWorker.kt
Халимов Рустам 9daa786cfb Заготовка
2026-05-07 23:27:37 +03:00

151 lines
5.9 KiB
Kotlin
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
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"))
)
}
}
}