android, desktop: better handling of parallel updates of chats (#3204)
* android, desktop: better handling of parallel updates of chats * one more case
This commit is contained in:
parent
b956988a83
commit
8ffe1c23c1
@ -9,12 +9,14 @@ import chat.simplex.common.helpers.APPLICATION_ID
|
|||||||
import chat.simplex.common.helpers.requiresIgnoringBattery
|
import chat.simplex.common.helpers.requiresIgnoringBattery
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
import chat.simplex.common.model.ChatController.appPrefs
|
import chat.simplex.common.model.ChatController.appPrefs
|
||||||
|
import chat.simplex.common.model.ChatModel.updatingChatsMutex
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.onboarding.OnboardingStage
|
import chat.simplex.common.views.onboarding.OnboardingStage
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.views.call.RcvCallInvitation
|
import chat.simplex.common.views.call.RcvCallInvitation
|
||||||
import com.jakewharton.processphoenix.ProcessPhoenix
|
import com.jakewharton.processphoenix.ProcessPhoenix
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.util.*
|
import java.util.*
|
||||||
import java.util.concurrent.TimeUnit
|
import java.util.concurrent.TimeUnit
|
||||||
@ -52,21 +54,23 @@ class SimplexApp: Application(), LifecycleEventObserver {
|
|||||||
Lifecycle.Event.ON_START -> {
|
Lifecycle.Event.ON_START -> {
|
||||||
isAppOnForeground = true
|
isAppOnForeground = true
|
||||||
if (chatModel.chatRunning.value == true) {
|
if (chatModel.chatRunning.value == true) {
|
||||||
kotlin.runCatching {
|
updatingChatsMutex.withLock {
|
||||||
val currentUserId = chatModel.currentUser.value?.userId
|
kotlin.runCatching {
|
||||||
val chats = ArrayList(chatController.apiGetChats())
|
val currentUserId = chatModel.currentUser.value?.userId
|
||||||
/** Active user can be changed in background while [ChatController.apiGetChats] is executing */
|
val chats = ArrayList(chatController.apiGetChats())
|
||||||
if (chatModel.currentUser.value?.userId == currentUserId) {
|
/** Active user can be changed in background while [ChatController.apiGetChats] is executing */
|
||||||
val currentChatId = chatModel.chatId.value
|
if (chatModel.currentUser.value?.userId == currentUserId) {
|
||||||
val oldStats = if (currentChatId != null) chatModel.getChat(currentChatId)?.chatStats else null
|
val currentChatId = chatModel.chatId.value
|
||||||
if (oldStats != null) {
|
val oldStats = if (currentChatId != null) chatModel.getChat(currentChatId)?.chatStats else null
|
||||||
val indexOfCurrentChat = chats.indexOfFirst { it.id == currentChatId }
|
if (oldStats != null) {
|
||||||
/** Pass old chatStats because unreadCounter can be changed already while [ChatController.apiGetChats] is executing */
|
val indexOfCurrentChat = chats.indexOfFirst { it.id == currentChatId }
|
||||||
if (indexOfCurrentChat >= 0) chats[indexOfCurrentChat] = chats[indexOfCurrentChat].copy(chatStats = oldStats)
|
/** Pass old chatStats because unreadCounter can be changed already while [ChatController.apiGetChats] is executing */
|
||||||
|
if (indexOfCurrentChat >= 0) chats[indexOfCurrentChat] = chats[indexOfCurrentChat].copy(chatStats = oldStats)
|
||||||
|
}
|
||||||
|
chatModel.updateChats(chats)
|
||||||
}
|
}
|
||||||
chatModel.updateChats(chats)
|
}.onFailure { Log.e(TAG, it.stackTraceToString()) }
|
||||||
}
|
}
|
||||||
}.onFailure { Log.e(TAG, it.stackTraceToString()) }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Lifecycle.Event.ON_RESUME -> {
|
Lifecycle.Event.ON_RESUME -> {
|
||||||
|
@ -6,7 +6,6 @@ import androidx.compose.ui.graphics.Color
|
|||||||
import androidx.compose.ui.text.SpanStyle
|
import androidx.compose.ui.text.SpanStyle
|
||||||
import androidx.compose.ui.text.font.*
|
import androidx.compose.ui.text.font.*
|
||||||
import androidx.compose.ui.text.style.TextDecoration
|
import androidx.compose.ui.text.style.TextDecoration
|
||||||
import chat.simplex.common.model.*
|
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.call.*
|
import chat.simplex.common.views.call.*
|
||||||
@ -16,6 +15,8 @@ import chat.simplex.res.MR
|
|||||||
import dev.icerock.moko.resources.ImageResource
|
import dev.icerock.moko.resources.ImageResource
|
||||||
import dev.icerock.moko.resources.StringResource
|
import dev.icerock.moko.resources.StringResource
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.sync.Mutex
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.datetime.*
|
import kotlinx.datetime.*
|
||||||
import kotlinx.datetime.TimeZone
|
import kotlinx.datetime.TimeZone
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
@ -102,6 +103,8 @@ object ChatModel {
|
|||||||
val filesToDelete = mutableSetOf<File>()
|
val filesToDelete = mutableSetOf<File>()
|
||||||
val simplexLinkMode by lazy { mutableStateOf(ChatController.appPrefs.simplexLinkMode.get()) }
|
val simplexLinkMode by lazy { mutableStateOf(ChatController.appPrefs.simplexLinkMode.get()) }
|
||||||
|
|
||||||
|
var updatingChatsMutex: Mutex = Mutex()
|
||||||
|
|
||||||
fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) {
|
fun getUser(userId: Long): User? = if (currentUser.value?.userId == userId) {
|
||||||
currentUser.value
|
currentUser.value
|
||||||
} else {
|
} else {
|
||||||
@ -198,7 +201,7 @@ object ChatModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) {
|
suspend fun addChatItem(cInfo: ChatInfo, cItem: ChatItem) = updatingChatsMutex.withLock {
|
||||||
// update previews
|
// update previews
|
||||||
val i = getChatIndex(cInfo.id)
|
val i = getChatIndex(cInfo.id)
|
||||||
val chat: Chat
|
val chat: Chat
|
||||||
@ -221,10 +224,11 @@ object ChatModel {
|
|||||||
} else {
|
} else {
|
||||||
addChat(Chat(chatInfo = cInfo, chatItems = arrayListOf(cItem)))
|
addChat(Chat(chatInfo = cInfo, chatItems = arrayListOf(cItem)))
|
||||||
}
|
}
|
||||||
// add to current chat
|
Log.d(TAG, "TODOCHAT: addChatItem: adding to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||||
if (chatId.value == cInfo.id) {
|
withContext(Dispatchers.Main) {
|
||||||
Log.d(TAG, "TODOCHAT: addChatItem: adding to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
// add to current chat
|
||||||
withContext(Dispatchers.Main) {
|
if (chatId.value == cInfo.id) {
|
||||||
|
Log.d(TAG, "TODOCHAT: addChatItem: chatIds are equal, size ${chatItems.size}")
|
||||||
// Prevent situation when chat item already in the list received from backend
|
// Prevent situation when chat item already in the list received from backend
|
||||||
if (chatItems.none { it.id == cItem.id }) {
|
if (chatItems.none { it.id == cItem.id }) {
|
||||||
if (chatItems.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
|
if (chatItems.lastOrNull()?.id == ChatItem.TEMP_LIVE_CHAT_ITEM_ID) {
|
||||||
@ -238,7 +242,7 @@ object ChatModel {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun upsertChatItem(cInfo: ChatInfo, cItem: ChatItem): Boolean {
|
suspend fun upsertChatItem(cInfo: ChatInfo, cItem: ChatItem): Boolean = updatingChatsMutex.withLock {
|
||||||
// update previews
|
// update previews
|
||||||
val i = getChatIndex(cInfo.id)
|
val i = getChatIndex(cInfo.id)
|
||||||
val chat: Chat
|
val chat: Chat
|
||||||
@ -258,10 +262,10 @@ object ChatModel {
|
|||||||
addChat(Chat(chatInfo = cInfo, chatItems = arrayListOf(cItem)))
|
addChat(Chat(chatInfo = cInfo, chatItems = arrayListOf(cItem)))
|
||||||
res = true
|
res = true
|
||||||
}
|
}
|
||||||
// update current chat
|
Log.d(TAG, "TODOCHAT: upsertChatItem: upserting to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||||
return if (chatId.value == cInfo.id) {
|
return withContext(Dispatchers.Main) {
|
||||||
Log.d(TAG, "TODOCHAT: upsertChatItem: upserting to chat ${chatId.value} from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
// update current chat
|
||||||
withContext(Dispatchers.Main) {
|
if (chatId.value == cInfo.id) {
|
||||||
val itemIndex = chatItems.indexOfFirst { it.id == cItem.id }
|
val itemIndex = chatItems.indexOfFirst { it.id == cItem.id }
|
||||||
if (itemIndex >= 0) {
|
if (itemIndex >= 0) {
|
||||||
chatItems[itemIndex] = cItem
|
chatItems[itemIndex] = cItem
|
||||||
@ -272,15 +276,15 @@ object ChatModel {
|
|||||||
Log.d(TAG, "TODOCHAT: upsertChatItem: added to chat $chatId from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
Log.d(TAG, "TODOCHAT: upsertChatItem: added to chat $chatId from ${cInfo.id} ${cItem.id}, size ${chatItems.size}")
|
||||||
true
|
true
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
res
|
||||||
}
|
}
|
||||||
} else {
|
|
||||||
res
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem) {
|
suspend fun updateChatItem(cInfo: ChatInfo, cItem: ChatItem) {
|
||||||
if (chatId.value == cInfo.id) {
|
withContext(Dispatchers.Main) {
|
||||||
withContext(Dispatchers.Main) {
|
if (chatId.value == cInfo.id) {
|
||||||
val itemIndex = chatItems.indexOfFirst { it.id == cItem.id }
|
val itemIndex = chatItems.indexOfFirst { it.id == cItem.id }
|
||||||
if (itemIndex >= 0) {
|
if (itemIndex >= 0) {
|
||||||
chatItems[itemIndex] = cItem
|
chatItems[itemIndex] = cItem
|
||||||
|
@ -4,6 +4,7 @@ import chat.simplex.common.views.helpers.*
|
|||||||
import androidx.compose.runtime.*
|
import androidx.compose.runtime.*
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.painter.Painter
|
import androidx.compose.ui.graphics.painter.Painter
|
||||||
|
import chat.simplex.common.model.ChatModel.updatingChatsMutex
|
||||||
import dev.icerock.moko.resources.compose.painterResource
|
import dev.icerock.moko.resources.compose.painterResource
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
@ -16,6 +17,7 @@ import com.charleskorn.kaml.YamlConfiguration
|
|||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
import com.russhwolf.settings.Settings
|
import com.russhwolf.settings.Settings
|
||||||
import kotlinx.coroutines.*
|
import kotlinx.coroutines.*
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.datetime.Clock
|
import kotlinx.datetime.Clock
|
||||||
import kotlinx.datetime.Instant
|
import kotlinx.datetime.Instant
|
||||||
import kotlinx.serialization.*
|
import kotlinx.serialization.*
|
||||||
@ -349,8 +351,10 @@ object ChatController {
|
|||||||
startReceiver()
|
startReceiver()
|
||||||
Log.d(TAG, "startChat: started")
|
Log.d(TAG, "startChat: started")
|
||||||
} else {
|
} else {
|
||||||
val chats = apiGetChats()
|
updatingChatsMutex.withLock {
|
||||||
chatModel.updateChats(chats)
|
val chats = apiGetChats()
|
||||||
|
chatModel.updateChats(chats)
|
||||||
|
}
|
||||||
Log.d(TAG, "startChat: running")
|
Log.d(TAG, "startChat: running")
|
||||||
}
|
}
|
||||||
} catch (e: Error) {
|
} catch (e: Error) {
|
||||||
@ -384,8 +388,10 @@ object ChatController {
|
|||||||
suspend fun getUserChatData() {
|
suspend fun getUserChatData() {
|
||||||
chatModel.userAddress.value = apiGetUserAddress()
|
chatModel.userAddress.value = apiGetUserAddress()
|
||||||
chatModel.chatItemTTL.value = getChatItemTTL()
|
chatModel.chatItemTTL.value = getChatItemTTL()
|
||||||
val chats = apiGetChats()
|
updatingChatsMutex.withLock {
|
||||||
chatModel.updateChats(chats)
|
val chats = apiGetChats()
|
||||||
|
chatModel.updateChats(chats)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun startReceiver() {
|
private fun startReceiver() {
|
||||||
|
@ -20,11 +20,13 @@ import androidx.compose.ui.text.*
|
|||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import chat.simplex.common.model.*
|
import chat.simplex.common.model.*
|
||||||
|
import chat.simplex.common.model.ChatModel.updatingChatsMutex
|
||||||
import chat.simplex.common.ui.theme.*
|
import chat.simplex.common.ui.theme.*
|
||||||
import chat.simplex.common.views.helpers.*
|
import chat.simplex.common.views.helpers.*
|
||||||
import chat.simplex.common.views.usersettings.*
|
import chat.simplex.common.views.usersettings.*
|
||||||
import chat.simplex.common.platform.*
|
import chat.simplex.common.platform.*
|
||||||
import chat.simplex.res.MR
|
import chat.simplex.res.MR
|
||||||
|
import kotlinx.coroutines.sync.withLock
|
||||||
import kotlinx.datetime.*
|
import kotlinx.datetime.*
|
||||||
import java.io.*
|
import java.io.*
|
||||||
import java.net.URI
|
import java.net.URI
|
||||||
@ -620,8 +622,10 @@ private fun afterSetCiTTL(
|
|||||||
appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath)
|
appFilesCountAndSize.value = directoryFileCountAndSize(appFilesDir.absolutePath)
|
||||||
withApi {
|
withApi {
|
||||||
try {
|
try {
|
||||||
val chats = m.controller.apiGetChats()
|
updatingChatsMutex.withLock {
|
||||||
m.updateChats(chats)
|
val chats = m.controller.apiGetChats()
|
||||||
|
m.updateChats(chats)
|
||||||
|
}
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
Log.e(TAG, "apiGetChats error: ${e.message}")
|
Log.e(TAG, "apiGetChats error: ${e.message}")
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user