feat: Enhance logging and state management in Protocol and MessageRepository; improve dialog read status handling in ChatViewModel
This commit is contained in:
@@ -49,11 +49,44 @@ class Protocol(
|
||||
private var handshakeComplete = false
|
||||
private var handshakeJob: Job? = null
|
||||
|
||||
// 🔍 Диагностика соединения
|
||||
private var reconnectAttempts = 0
|
||||
private var lastStateChangeTime = System.currentTimeMillis()
|
||||
private var lastSuccessfulConnection = 0L
|
||||
|
||||
private val scope = CoroutineScope(Dispatchers.IO + SupervisorJob())
|
||||
|
||||
private val _state = MutableStateFlow(ProtocolState.DISCONNECTED)
|
||||
val state: StateFlow<ProtocolState> = _state.asStateFlow()
|
||||
|
||||
/**
|
||||
* 🔍 Безопасное изменение состояния с подробным логированием
|
||||
*/
|
||||
private fun setState(newState: ProtocolState, reason: String = "") {
|
||||
val oldState = _state.value
|
||||
val now = System.currentTimeMillis()
|
||||
val timeSinceLastChange = now - lastStateChangeTime
|
||||
|
||||
if (oldState != newState) {
|
||||
log("🔄 STATE CHANGE: $oldState -> $newState (${timeSinceLastChange}ms since last change)")
|
||||
if (reason.isNotEmpty()) {
|
||||
log(" Reason: $reason")
|
||||
}
|
||||
log(" Reconnect attempts: $reconnectAttempts")
|
||||
log(" Last successful: ${if (lastSuccessfulConnection > 0) "${(now - lastSuccessfulConnection)/1000}s ago" else "never"}")
|
||||
|
||||
_state.value = newState
|
||||
lastStateChangeTime = now
|
||||
|
||||
// Сброс счетчика при успешном подключении
|
||||
if (newState == ProtocolState.AUTHENTICATED) {
|
||||
lastSuccessfulConnection = now
|
||||
reconnectAttempts = 0
|
||||
log("✅ CONNECTION FULLY ESTABLISHED")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val _lastError = MutableStateFlow<String?>(null)
|
||||
val lastError: StateFlow<String?> = _lastError.asStateFlow()
|
||||
|
||||
@@ -89,10 +122,10 @@ class Protocol(
|
||||
// Register handshake response handler
|
||||
waitPacket(0x00) { packet ->
|
||||
if (packet is PacketHandshake) {
|
||||
log("✅ Handshake response received, protocol version: ${packet.protocolVersion}")
|
||||
log("✅ HANDSHAKE SUCCESS: protocol=${packet.protocolVersion}, heartbeat=${packet.heartbeatInterval}s")
|
||||
handshakeJob?.cancel()
|
||||
handshakeComplete = true
|
||||
_state.value = ProtocolState.AUTHENTICATED
|
||||
setState(ProtocolState.AUTHENTICATED, "Handshake response received")
|
||||
flushPacketQueue()
|
||||
|
||||
// Start heartbeat with interval from server
|
||||
@@ -110,7 +143,7 @@ class Protocol(
|
||||
|
||||
// Отправляем чаще - каждые 1/3 интервала (чтобы не терять соединение)
|
||||
val intervalMs = (intervalSeconds * 1000L) / 3
|
||||
log("💓 Starting heartbeat with server interval: ${intervalSeconds}s (sending every ${intervalMs}ms = ${intervalMs/1000}s)")
|
||||
log("💓 HEARTBEAT START: server=${intervalSeconds}s, sending=${intervalMs/1000}s, state=${_state.value}")
|
||||
|
||||
heartbeatJob = scope.launch {
|
||||
// ⚡ СРАЗУ отправляем первый heartbeat (как в Архиве)
|
||||
@@ -128,21 +161,27 @@ class Protocol(
|
||||
*/
|
||||
private fun sendHeartbeat() {
|
||||
try {
|
||||
if (_state.value == ProtocolState.AUTHENTICATED) {
|
||||
val currentState = _state.value
|
||||
val socketAlive = webSocket != null
|
||||
|
||||
if (currentState == ProtocolState.AUTHENTICATED) {
|
||||
val sent = webSocket?.send("heartbeat") ?: false
|
||||
if (sent) {
|
||||
log("💓 Heartbeat sent")
|
||||
log("💓 Heartbeat OK (socket=$socketAlive, state=$currentState)")
|
||||
} else {
|
||||
log("💔 Heartbeat failed - socket closed or null")
|
||||
log("💔 HEARTBEAT FAILED: socket=$socketAlive, state=$currentState, manuallyClosed=$isManuallyClosed")
|
||||
// Триггерим reconnect если heartbeat не прошёл
|
||||
if (!isManuallyClosed) {
|
||||
log("🔄 Triggering reconnect due to failed heartbeat")
|
||||
log("🔄 TRIGGERING RECONNECT due to failed heartbeat")
|
||||
handleDisconnect()
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log("💔 HEARTBEAT SKIPPED: state=$currentState (not AUTHENTICATED), socket=$socketAlive")
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
log("💔 Heartbeat error: ${e.message}")
|
||||
log("💔 HEARTBEAT EXCEPTION: ${e.message}")
|
||||
e.printStackTrace()
|
||||
}
|
||||
}
|
||||
|
||||
@@ -150,11 +189,17 @@ class Protocol(
|
||||
* Initialize connection to server
|
||||
*/
|
||||
fun connect() {
|
||||
if (_state.value == ProtocolState.CONNECTING) {
|
||||
log("Already connecting, skipping...")
|
||||
val currentState = _state.value
|
||||
log("🔌 CONNECT CALLED: currentState=$currentState, reconnectAttempts=$reconnectAttempts")
|
||||
|
||||
if (currentState == ProtocolState.CONNECTING) {
|
||||
log("⚠️ Already connecting, skipping... (preventing duplicate connect)")
|
||||
return
|
||||
}
|
||||
|
||||
reconnectAttempts++
|
||||
log("📊 RECONNECT ATTEMPT #$reconnectAttempts")
|
||||
|
||||
// Закрываем старый сокет если есть (как в Архиве)
|
||||
webSocket?.let { oldSocket ->
|
||||
try {
|
||||
@@ -167,10 +212,10 @@ class Protocol(
|
||||
webSocket = null
|
||||
|
||||
isManuallyClosed = false
|
||||
_state.value = ProtocolState.CONNECTING
|
||||
setState(ProtocolState.CONNECTING, "Starting new connection attempt #$reconnectAttempts")
|
||||
_lastError.value = null
|
||||
|
||||
log("🔌 Connecting to: $serverAddress")
|
||||
log("🔌 Connecting to: $serverAddress (attempt #$reconnectAttempts)")
|
||||
|
||||
val request = Request.Builder()
|
||||
.url(serverAddress)
|
||||
@@ -178,15 +223,16 @@ class Protocol(
|
||||
|
||||
webSocket = client.newWebSocket(request, object : WebSocketListener() {
|
||||
override fun onOpen(webSocket: WebSocket, response: Response) {
|
||||
log("✅ WebSocket connected")
|
||||
_state.value = ProtocolState.CONNECTED
|
||||
log("✅ WebSocket OPEN: response=${response.code}, hasCredentials=${lastPublicKey != null}")
|
||||
setState(ProtocolState.CONNECTED, "WebSocket onOpen callback")
|
||||
|
||||
// If we have saved credentials, start handshake automatically
|
||||
lastPublicKey?.let { publicKey ->
|
||||
lastPrivateHash?.let { privateHash ->
|
||||
log("🤝 Auto-starting handshake with saved credentials")
|
||||
startHandshake(publicKey, privateHash)
|
||||
}
|
||||
}
|
||||
} ?: log("⚠️ No saved credentials, waiting for manual handshake")
|
||||
}
|
||||
|
||||
override fun onMessage(webSocket: WebSocket, bytes: ByteString) {
|
||||
@@ -199,23 +245,20 @@ class Protocol(
|
||||
}
|
||||
|
||||
override fun onClosing(webSocket: WebSocket, code: Int, reason: String) {
|
||||
// Code 3887 - кастомный код сервера, просто делаем reconnect тихо
|
||||
if (code != 3887) {
|
||||
log("⚠️ WebSocket closing: code=$code reason='$reason'")
|
||||
}
|
||||
log("⚠️ WebSocket CLOSING: code=$code reason='$reason' state=${_state.value}")
|
||||
}
|
||||
|
||||
override fun onClosed(webSocket: WebSocket, code: Int, reason: String) {
|
||||
// Для кода 3887 не логируем - это частое закрытие сервером
|
||||
if (code != 3887) {
|
||||
log("❌ WebSocket closed: code=$code reason='$reason'")
|
||||
}
|
||||
log("❌ WebSocket CLOSED: code=$code reason='$reason' state=${_state.value} manuallyClosed=$isManuallyClosed")
|
||||
handleDisconnect()
|
||||
}
|
||||
|
||||
override fun onFailure(webSocket: WebSocket, t: Throwable, response: Response?) {
|
||||
log("❌ WebSocket failure: ${t.message}")
|
||||
log("❌ Response: ${response?.code} ${response?.message}")
|
||||
log("❌ WebSocket FAILURE: ${t.message}")
|
||||
log(" Response: ${response?.code} ${response?.message}")
|
||||
log(" State: ${_state.value}")
|
||||
log(" Manually closed: $isManuallyClosed")
|
||||
log(" Reconnect attempts: $reconnectAttempts")
|
||||
t.printStackTrace()
|
||||
_lastError.value = t.message
|
||||
handleDisconnect()
|
||||
@@ -236,12 +279,12 @@ class Protocol(
|
||||
lastPrivateHash = privateHash
|
||||
|
||||
if (_state.value != ProtocolState.CONNECTED && _state.value != ProtocolState.AUTHENTICATED) {
|
||||
log("Not connected, will handshake after connection")
|
||||
log("⚠️ HANDSHAKE DEFERRED: Not connected (state=${_state.value}), will handshake after connection")
|
||||
connect()
|
||||
return
|
||||
}
|
||||
|
||||
_state.value = ProtocolState.HANDSHAKING
|
||||
setState(ProtocolState.HANDSHAKING, "Starting handshake")
|
||||
handshakeComplete = false
|
||||
|
||||
val handshake = PacketHandshake().apply {
|
||||
@@ -366,22 +409,34 @@ class Protocol(
|
||||
|
||||
private fun handleDisconnect() {
|
||||
val previousState = _state.value
|
||||
_state.value = ProtocolState.DISCONNECTED
|
||||
log("🔌 DISCONNECT HANDLER: previousState=$previousState, manuallyClosed=$isManuallyClosed, reconnectAttempts=$reconnectAttempts")
|
||||
|
||||
setState(ProtocolState.DISCONNECTED, "Disconnect handler called from $previousState")
|
||||
handshakeComplete = false
|
||||
handshakeJob?.cancel()
|
||||
heartbeatJob?.cancel()
|
||||
|
||||
// Автоматический reconnect (простая логика как в Архиве - без счётчиков)
|
||||
// Автоматический reconnect с защитой от бесконечных попыток
|
||||
if (!isManuallyClosed) {
|
||||
log("🔄 Reconnecting silently...")
|
||||
// Экспоненциальная задержка: 1s, 2s, 4s, 8s, максимум 30s
|
||||
val delayMs = minOf(1000L * (1 shl minOf(reconnectAttempts - 1, 4)), 30000L)
|
||||
log("🔄 SCHEDULING RECONNECT: attempt #$reconnectAttempts, delay=${delayMs}ms")
|
||||
|
||||
if (reconnectAttempts > 20) {
|
||||
log("⚠️ WARNING: Too many reconnect attempts ($reconnectAttempts), may be stuck in loop")
|
||||
}
|
||||
|
||||
scope.launch {
|
||||
// Быстрый reconnect - 1 секунда
|
||||
delay(1000L)
|
||||
delay(delayMs)
|
||||
if (!isManuallyClosed) {
|
||||
log("🔄 EXECUTING RECONNECT after ${delayMs}ms delay")
|
||||
connect()
|
||||
} else {
|
||||
log("🔄 RECONNECT CANCELLED: was manually closed during delay")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
log("🔌 No reconnect: connection was manually closed")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -62,9 +62,21 @@ object ProtocolManager {
|
||||
addLog("🚀 ProtocolManager.initialize() called")
|
||||
messageRepository = MessageRepository.getInstance(context)
|
||||
setupPacketHandlers()
|
||||
setupStateMonitoring()
|
||||
addLog("🚀 ProtocolManager.initialize() completed")
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔍 Мониторинг состояния соединения
|
||||
*/
|
||||
private fun setupStateMonitoring() {
|
||||
scope.launch {
|
||||
getProtocol().state.collect { newState ->
|
||||
addLog("📊 STATE MONITOR: Connection state changed to $newState")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 🔥 Инициализация аккаунта - КРИТИЧНО для получения сообщений!
|
||||
* Должен вызываться после авторизации пользователя
|
||||
@@ -210,7 +222,7 @@ object ProtocolManager {
|
||||
* Connect to server
|
||||
*/
|
||||
fun connect() {
|
||||
addLog("Connect requested")
|
||||
addLog("🔌 CONNECT REQUESTED from ProtocolManager")
|
||||
getProtocol().connect()
|
||||
}
|
||||
|
||||
@@ -218,9 +230,9 @@ object ProtocolManager {
|
||||
* Authenticate with server
|
||||
*/
|
||||
fun authenticate(publicKey: String, privateHash: String) {
|
||||
addLog("Authenticate called")
|
||||
addLog("PublicKey: ${publicKey.take(30)}...")
|
||||
addLog("PrivateHash: ${privateHash.take(20)}...")
|
||||
addLog("🔐 AUTHENTICATE called from ProtocolManager")
|
||||
addLog(" PublicKey: ${publicKey.take(30)}...")
|
||||
addLog(" PrivateHash: ${privateHash.take(20)}...")
|
||||
getProtocol().startHandshake(publicKey, privateHash)
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user