Фикс: монотонный статус доставки — DELIVERED больше не откатывается на SENT. Логирование отправки/delivery в rosettadev1.
This commit is contained in:
@@ -1009,18 +1009,28 @@ class MessageRepository private constructor(private val context: Context) {
|
||||
}
|
||||
|
||||
/** Обработка подтверждения доставки */
|
||||
private fun devLog(msg: String) {
|
||||
val ts = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date())
|
||||
val line = "$ts [MsgRepo] $msg"
|
||||
android.util.Log.d("MsgRepo", msg)
|
||||
try {
|
||||
val dir = java.io.File(context.filesDir, "crash_reports")
|
||||
if (!dir.exists()) dir.mkdirs()
|
||||
java.io.File(dir, "rosettadev1.txt").appendText("$line\n")
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
suspend fun handleDelivery(packet: PacketDelivery) {
|
||||
val account = currentAccount ?: return
|
||||
|
||||
// 📝 LOG: Получено подтверждение доставки
|
||||
devLog("DELIVERY RECEIVED: msgId=${packet.messageId.take(8)}..., to=${packet.toPublicKey.take(12)}...")
|
||||
|
||||
MessageLogger.logDeliveryStatus(
|
||||
messageId = packet.messageId,
|
||||
toPublicKey = packet.toPublicKey,
|
||||
status = "DELIVERED"
|
||||
)
|
||||
|
||||
// Desktop parity: update both delivery status AND timestamp on delivery confirmation.
|
||||
// Desktop sets timestamp = Date.now() when PacketDelivery arrives (useSynchronize.ts).
|
||||
val deliveryTimestamp = System.currentTimeMillis()
|
||||
messageDao.updateDeliveryStatusAndTimestamp(
|
||||
account, packet.messageId, DeliveryStatus.DELIVERED.value, deliveryTimestamp
|
||||
|
||||
@@ -57,6 +57,18 @@ object ProtocolManager {
|
||||
private var groupRepository: GroupRepository? = null
|
||||
private var appContext: Context? = null
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
private fun protocolDevLog(msg: String) {
|
||||
val ts = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date())
|
||||
val line = "$ts [Protocol] $msg"
|
||||
android.util.Log.d("Protocol", msg)
|
||||
try {
|
||||
val ctx = appContext ?: return
|
||||
val dir = java.io.File(ctx.filesDir, "crash_reports")
|
||||
if (!dir.exists()) dir.mkdirs()
|
||||
java.io.File(dir, "rosettadev1.txt").appendText("$line\n")
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
@Volatile private var packetHandlersRegistered = false
|
||||
@Volatile private var stateMonitoringStarted = false
|
||||
@Volatile private var syncRequestInFlight = false
|
||||
@@ -335,22 +347,24 @@ object ProtocolManager {
|
||||
}
|
||||
|
||||
// Обработчик доставки (0x08)
|
||||
// Desktop parity: useDialogFiber.ts updates sync time on delivery (await updateSyncTime(Date.now()))
|
||||
waitPacket(0x08) { packet ->
|
||||
val deliveryPacket = packet as PacketDelivery
|
||||
|
||||
protocolDevLog("PACKET 0x08 DELIVERY: msgId=${deliveryPacket.messageId.take(8)}..., to=${deliveryPacket.toPublicKey.take(12)}...")
|
||||
|
||||
launchInboundPacketTask {
|
||||
val repository = messageRepository
|
||||
if (repository == null || !repository.isInitialized()) {
|
||||
protocolDevLog(" DELIVERY SKIPPED: repo not init")
|
||||
requireResyncAfterAccountInit("⏳ Delivery status before account init, scheduling re-sync")
|
||||
markInboundProcessingFailure("Delivery packet skipped before account init")
|
||||
return@launchInboundPacketTask
|
||||
}
|
||||
try {
|
||||
repository.handleDelivery(deliveryPacket)
|
||||
// iOS parity: cancel retry timer on delivery ACK
|
||||
resolveOutgoingRetry(deliveryPacket.messageId)
|
||||
protocolDevLog(" DELIVERY HANDLED OK: ${deliveryPacket.messageId.take(8)}...")
|
||||
} catch (e: Exception) {
|
||||
protocolDevLog(" DELIVERY ERROR: ${e.javaClass.simpleName}: ${e.message}")
|
||||
markInboundProcessingFailure("Delivery processing failed", e)
|
||||
return@launchInboundPacketTask
|
||||
}
|
||||
|
||||
@@ -37,6 +37,18 @@ import org.json.JSONObject
|
||||
*/
|
||||
class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
private fun devLog(msg: String) {
|
||||
val ts = java.text.SimpleDateFormat("HH:mm:ss.SSS", java.util.Locale.getDefault()).format(java.util.Date())
|
||||
val line = "$ts [SendMsg] $msg"
|
||||
android.util.Log.d("SendMsg", msg)
|
||||
try {
|
||||
val ctx = getApplication<Application>()
|
||||
val dir = java.io.File(ctx.filesDir, "crash_reports")
|
||||
if (!dir.exists()) dir.mkdirs()
|
||||
java.io.File(dir, "rosettadev1.txt").appendText("$line\n")
|
||||
} catch (_: Exception) {}
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val TAG = "ChatViewModel"
|
||||
private const val PAGE_SIZE = 30
|
||||
@@ -529,23 +541,26 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val opponent = opponentKey ?: return@collect
|
||||
val currentDialogKey = getDialogKey(account, opponent)
|
||||
|
||||
devLog("DELIVERY EVENT: msgId=${update.messageId.take(8)}..., status=${update.status}, dialogKey=${update.dialogKey.take(20)}..., currentKey=${currentDialogKey.take(20)}..., match=${update.dialogKey == currentDialogKey}")
|
||||
|
||||
if (update.dialogKey == currentDialogKey) {
|
||||
if (!isDialogActive) return@collect
|
||||
when (update.status) {
|
||||
DeliveryStatus.DELIVERED -> {
|
||||
// Обновляем конкретное сообщение
|
||||
devLog(" → updateMessageStatus DELIVERED: ${update.messageId.take(8)}...")
|
||||
updateMessageStatus(update.messageId, MessageStatus.DELIVERED)
|
||||
}
|
||||
DeliveryStatus.ERROR -> {
|
||||
// Синхронизируем ошибку отправки с открытым диалогом
|
||||
devLog(" → updateMessageStatus ERROR: ${update.messageId.take(8)}...")
|
||||
updateMessageStatus(update.messageId, MessageStatus.ERROR)
|
||||
}
|
||||
DeliveryStatus.READ -> {
|
||||
// Помечаем все исходящие как прочитанные
|
||||
devLog(" → markAllOutgoingAsRead")
|
||||
markAllOutgoingAsRead()
|
||||
}
|
||||
else -> {}
|
||||
}
|
||||
} else {
|
||||
devLog(" → SKIPPED (dialog mismatch)")
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -602,22 +617,45 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
}
|
||||
|
||||
private fun updateMessageStatus(messageId: String, status: MessageStatus) {
|
||||
val currentMessages = _messages.value
|
||||
val currentStatus = currentMessages.find { it.id == messageId }?.status
|
||||
|
||||
devLog("updateMessageStatus: msgId=${messageId.take(8)}..., newStatus=$status, currentStatus=$currentStatus, totalMsgs=${currentMessages.size}")
|
||||
|
||||
_messages.value =
|
||||
_messages.value.map { msg ->
|
||||
currentMessages.map { msg ->
|
||||
if (msg.id != messageId) return@map msg
|
||||
|
||||
// Keep read status monotonic: late DELIVERED must not downgrade READ.
|
||||
val mergedStatus =
|
||||
when (status) {
|
||||
MessageStatus.DELIVERED ->
|
||||
if (msg.status == MessageStatus.READ) MessageStatus.READ
|
||||
else MessageStatus.DELIVERED
|
||||
else -> status
|
||||
}
|
||||
if (mergedStatus != msg.status) msg.copy(status = mergedStatus) else msg
|
||||
// Monotonic status: never downgrade
|
||||
// SENDING < SENT < DELIVERED < READ
|
||||
val statusOrder = mapOf(
|
||||
MessageStatus.ERROR to 0,
|
||||
MessageStatus.SENDING to 1,
|
||||
MessageStatus.SENT to 2,
|
||||
MessageStatus.DELIVERED to 3,
|
||||
MessageStatus.READ to 4
|
||||
)
|
||||
val currentOrd = statusOrder[msg.status] ?: 0
|
||||
val newOrd = statusOrder[status] ?: 0
|
||||
|
||||
// ERROR can always override, otherwise only upgrade
|
||||
val mergedStatus = if (status == MessageStatus.ERROR) {
|
||||
status
|
||||
} else if (newOrd > currentOrd) {
|
||||
status
|
||||
} else {
|
||||
msg.status
|
||||
}
|
||||
|
||||
if (mergedStatus != msg.status) {
|
||||
devLog(" → STATUS CHANGED: ${msg.status} → $mergedStatus")
|
||||
msg.copy(status = mergedStatus)
|
||||
} else {
|
||||
devLog(" → STATUS NOT UPGRADED: ${msg.status} ≥ $status, skip")
|
||||
msg
|
||||
}
|
||||
}
|
||||
|
||||
// 🔥 Также обновляем кэш!
|
||||
updateCacheFromCurrentMessages()
|
||||
}
|
||||
|
||||
@@ -2834,6 +2872,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
val messageId = UUID.randomUUID().toString().replace("-", "").take(32)
|
||||
val timestamp = System.currentTimeMillis()
|
||||
|
||||
devLog("=== SEND START: msgId=${messageId.take(8)}..., to=${recipient.take(12)}..., text='${text.take(30)}' ===")
|
||||
devLog(" isForward=$isForward, replyMsgs=${replyMsgsToSend.size}, isConnected=${ProtocolManager.isConnected()}, isAuth=${ProtocolManager.isAuthenticated()}")
|
||||
|
||||
// 🔥 Формируем ReplyData для отображения в UI (только первое сообщение)
|
||||
// Используется для обычного reply (не forward).
|
||||
val replyData: ReplyData? =
|
||||
@@ -3093,15 +3134,15 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
packet.attachments.forEachIndexed { idx, att -> }
|
||||
|
||||
// 📁 Для Saved Messages - НЕ отправляем пакет на сервер
|
||||
// Только сохраняем локально
|
||||
val isSavedMessages = (sender == recipient)
|
||||
if (!isSavedMessages) {
|
||||
// Отправляем пакет только для обычных диалогов
|
||||
devLog(" SENDING packet: msgId=${messageId.take(8)}..., isConnected=${ProtocolManager.isConnected()}, isAuth=${ProtocolManager.isAuthenticated()}")
|
||||
ProtocolManager.send(packet)
|
||||
devLog(" SENT packet OK: msgId=${messageId.take(8)}...")
|
||||
} else {
|
||||
devLog(" Saved Messages — skip send")
|
||||
}
|
||||
|
||||
// 3. 🎯 UI обновление в Main потоке
|
||||
// Для обычных диалогов статус остаётся SENDING до PacketDelivery(messageId).
|
||||
withContext(Dispatchers.Main) {
|
||||
if (isSavedMessages) {
|
||||
updateMessageStatus(messageId, MessageStatus.SENT)
|
||||
@@ -3168,10 +3209,10 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
|
||||
saveDialog(text, timestamp)
|
||||
} catch (e: Exception) {
|
||||
devLog(" SEND ERROR: msgId=${messageId.take(8)}..., ${e.javaClass.simpleName}: ${e.message}")
|
||||
withContext(Dispatchers.Main) {
|
||||
updateMessageStatus(messageId, MessageStatus.ERROR)
|
||||
}
|
||||
// Update error status in DB + dialog
|
||||
updateMessageStatusInDb(messageId, DeliveryStatus.ERROR.value)
|
||||
saveDialog(text, timestamp)
|
||||
} finally {
|
||||
@@ -3389,7 +3430,9 @@ class ChatViewModel(application: Application) : AndroidViewModel(application) {
|
||||
attachments = finalMessageAttachments
|
||||
}
|
||||
if (!isSavedMessages) {
|
||||
devLog(" FWD SENDING: msgId=${messageId.take(8)}..., to=${recipientPublicKey.take(12)}..., isConn=${ProtocolManager.isConnected()}, isAuth=${ProtocolManager.isAuthenticated()}")
|
||||
ProtocolManager.send(packet)
|
||||
devLog(" FWD SENT OK: msgId=${messageId.take(8)}...")
|
||||
}
|
||||
|
||||
val finalAttachmentsJson =
|
||||
|
||||
Reference in New Issue
Block a user