Unverified Commit 9567523e authored by Lucio Maciel's avatar Lucio Maciel Committed by GitHub

Merge pull request #1615 from luckcoolla/fix/chat_history_sync

[WIP][FIX] Chat history sync
parents b01c1258 0cb06f84
......@@ -158,23 +158,36 @@ class ChatRoomAdapter(
}
fun prependData(dataSet: List<BaseUiModel<*>>) {
val item = dataSet.indexOfFirst { newItem ->
this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType } > -1
}
if (item == -1) {
this.dataSet.addAll(0, dataSet)
notifyItemRangeInserted(0, dataSet.size)
} else {
dataSet.forEach { item ->
val index = this.dataSet.indexOfFirst {
item.messageId == it.messageId && item.viewType == it.viewType
}
if (index > -1) {
this.dataSet[index] = item
notifyItemChanged(index)
}
//---At first we will update all already saved elements with received updated ones
val filteredDataSet = dataSet.filter { newItem ->
val matchedIndex = this.dataSet.indexOfFirst { it.messageId == newItem.messageId && it.viewType == newItem.viewType }
if (matchedIndex > -1) {
this.dataSet[matchedIndex] = newItem
notifyItemChanged(matchedIndex)
}
return@filter (matchedIndex < 0)
}
val minAdditionDate = filteredDataSet.minBy { it.message.timestamp } ?: return
//---In the most cases we will just add new elements to the top of messages heap
if (minAdditionDate.message.timestamp > this.dataSet[0].message.timestamp) {
this.dataSet.addAll(0, filteredDataSet)
notifyItemRangeInserted(0, filteredDataSet.size)
return
}
//---Else branch: merging messages---
//---We are inserting new received elements into set. Sort them by time+type and show
if (filteredDataSet.isEmpty()) return
this.dataSet.addAll(0, filteredDataSet)
val tmp = this.dataSet.sortedWith(Comparator { t, t2 ->
val timeComparison = t.message.timestamp.compareTo(t2.message.timestamp)
if (timeComparison == 0) {
return@Comparator t.viewType.compareTo(t2.viewType)
}
timeComparison
}).reversed()
this.dataSet.clear()
this.dataSet.addAll(tmp)
notifyDataSetChanged()
}
fun updateItem(message: BaseUiModel<*>) {
......
......@@ -184,7 +184,8 @@ class ChatRoomPresenter @Inject constructor(
isBroadcast = chatIsBroadcast, isRoom = true
)
)
if (oldMessages.isNotEmpty()) {
val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId)
if (oldMessages.isNotEmpty() && lastSyncDate != null) {
view.showMessages(oldMessages, clearDataSet)
loadMissingMessages()
} else {
......@@ -226,6 +227,19 @@ class ChatRoomPresenter @Inject constructor(
client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result
}
messagesRepository.saveAll(messages)
//we are saving last sync date of latest synced chat room message
if (offset == 0L) {
//if success - saving last synced time
if (messages.isEmpty()) {
//chat history is empty - just saving current date
messagesRepository.saveLastSyncDate(chatRoomId, System.currentTimeMillis())
} else {
//assume that BE returns ordered messages, the first message is the latest one
messagesRepository.saveLastSyncDate(chatRoomId, messages.first().timestamp)
}
}
view.showMessages(
mapper.map(
messages,
......@@ -275,7 +289,7 @@ class ChatRoomPresenter @Inject constructor(
timestamp = Instant.now().toEpochMilli(),
sender = SimpleUser(null, username, username),
attachments = null,
avatar = currentServer.avatarUrl(username!!),
avatar = currentServer.avatarUrl(username ?: ""),
channels = null,
editedAt = null,
editedBy = null,
......@@ -483,45 +497,49 @@ class ChatRoomPresenter @Inject constructor(
private fun loadMissingMessages() {
launch(parent = strategy.jobs) {
if (chatRoomId != null && chatRoomType != null) {
val roomType = roomTypeOf(chatRoomType!!)
messagesRepository.getByRoomId(chatRoomId!!)
.sortedByDescending { it.timestamp }.firstOrNull()?.let { lastMessage ->
val instant = Instant.ofEpochMilli(lastMessage.timestamp).toString()
try {
val messages =
retryIO(description = "history($chatRoomId, $roomType, $instant)") {
client.history(
chatRoomId!!, roomType, count = 50,
oldest = instant
)
}
Timber.d("History: $messages")
if (messages.result.isNotEmpty()) {
val models = mapper.map(messages.result, RoomUiModel(
roles = chatRoles,
isBroadcast = chatIsBroadcast,
// FIXME: Why are we fixing isRoom attribute to true here?
isRoom = true
))
messagesRepository.saveAll(messages.result)
launchUI(strategy) {
view.showNewMessage(models, true)
}
chatRoomId?.let { chatRoomId ->
val roomType = roomTypeOf(chatRoomType)
val lastSyncDate = messagesRepository.getLastSyncDate(chatRoomId)
// lastSyncDate or 0. LastSyncDate could be in case when we sent some messages offline(and saved them locally),
// but never has obtained chatMessages(or history) from remote. In this case we should sync all chat history from beginning
val instant = Instant.ofEpochMilli(lastSyncDate ?: 0).toString()
//
try {
val messages =
retryIO(description = "history($chatRoomId, $roomType, $instant)") {
client.history(
chatRoomId!!, roomType, count = 50,
oldest = instant
)
}
Timber.d("History: $messages")
if (messages.result.size == 50) {
// we loaded at least count messages, try one more to fetch more messages
loadMissingMessages()
}
}
} catch (ex: Exception) {
// TODO - we need to better treat connection problems here, but no let gaps
// on the messages list
Timber.d(ex, "Error fetching channel history")
if (messages.result.isNotEmpty()) {
val models = mapper.map(messages.result, RoomUiModel(
roles = chatRoles,
isBroadcast = chatIsBroadcast,
// FIXME: Why are we fixing isRoom attribute to true here?
isRoom = true
))
messagesRepository.saveAll(messages.result)
//if success - saving last synced time
//assume that BE returns ordered messages, the first message is the latest one
messagesRepository.saveLastSyncDate(chatRoomId, messages.result.first().timestamp)
launchUI(strategy) {
view.showNewMessage(models, true)
}
if (messages.result.size == 50) {
// we loaded at least count messages, try one more to fetch more messages
loadMissingMessages()
}
}
} catch (ex: Exception) {
// TODO - we need to better treat connection problems here, but no let gaps
// on the messages list
Timber.d(ex, "Error fetching channel history")
}
}
}
}
......
......@@ -72,4 +72,22 @@ interface MessagesRepository {
suspend fun getAllUnsent(): List<Message>
suspend fun getUnsentByRoomId(roomId: String): List<Message>
/**
* Save time of the latest room messages sync.
* Call this fun only when the latest messages list being received via /history or /messages network calls
*
* @param rid The id of the room the messages are.
* @param timeMillis time of room messages sync or the latest room message timestamp(which came with /history request)
*/
suspend fun saveLastSyncDate(rid: String, timeMillis: Long)
/**
* Get time when the room chat history has been loaded last time.
*
* @param rid The id of the room the messages are.
*
* @return Last Sync time or Null.
*/
suspend fun getLastSyncDate(rid: String): Long?
}
\ No newline at end of file
......@@ -7,8 +7,16 @@ import kotlinx.coroutines.experimental.withContext
class MemoryMessagesRepository : MessagesRepository {
private var lastSyncDates: HashMap<String, Long> = HashMap()
private val messages: HashMap<String, Message> = HashMap()
override suspend fun saveLastSyncDate(rid: String, timeMillis: Long) {
lastSyncDates[rid] = timeMillis
}
override suspend fun getLastSyncDate(rid: String) = lastSyncDates[rid]
override suspend fun getById(id: String): Message? = withContext(CommonPool) {
return@withContext messages[id]
}
......
......@@ -13,6 +13,27 @@ class SharedPreferencesMessagesRepository(
private val moshi: Moshi,
private val currentServerInteractor: GetCurrentServerInteractor
) : MessagesRepository {
private val KEY_LAST_SYNC_DATE = "KEY_LAST_SYNC_DATE"
override suspend fun saveLastSyncDate(rid: String, timeMillis: Long) {
withContext(CommonPool) {
currentServerInteractor.get()?.let {
prefs.edit().putLong(getSyncDateKey(it, rid), timeMillis).apply()
}
}
}
override suspend fun getLastSyncDate(rid: String): Long? = withContext(CommonPool) {
currentServerInteractor.get()?.also { server ->
if (!prefs.contains(getSyncDateKey(server, rid)))
return@withContext null
val time = prefs.getLong(getSyncDateKey(server, rid), -1)
return@withContext if (time == -1L) null else time
}
return@withContext null
}
private fun getSyncDateKey(server: String, rid: String) = "${KEY_LAST_SYNC_DATE}_$server $rid"
override suspend fun getById(id: String): Message? = withContext(CommonPool) {
currentServerInteractor.get()?.also { server ->
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment