[Chat][Feature]Storage Implementation with Tree based data structure (#531)

This commit is contained in:
Mohtasim 2022-10-21 12:15:07 -07:00 коммит произвёл GitHub
Родитель 4b219a754f
Коммит f655e2fe56
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 219 добавлений и 1 удалений

Просмотреть файл

@ -4,6 +4,10 @@
package com.azure.android.communication.ui.chat.repository
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.repository.storage.MessageRepositoryListReader
import com.azure.android.communication.ui.chat.repository.storage.MessageRepositoryListWriter
import com.azure.android.communication.ui.chat.repository.storage.MessageRepositoryTreeReader
import com.azure.android.communication.ui.chat.repository.storage.MessageRepositoryTreeWriter
internal class MessageRepository private constructor(
val readerDelegate: MessageRepositoryReader,
@ -31,5 +35,14 @@ internal class MessageRepository private constructor(
writerDelegate = writer
)
}
fun createTreeBackedRepository(): MessageRepository {
val writer = MessageRepositoryTreeWriter()
val reader = MessageRepositoryTreeReader(writer)
return MessageRepository(
readerDelegate = reader,
writerDelegate = writer
)
}
}
}

Просмотреть файл

@ -1,7 +1,12 @@
package com.azure.android.communication.ui.chat.repository
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.repository.storage
import com.azure.android.communication.ui.chat.models.EMPTY_MESSAGE_INFO_MODEL
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.repository.MessageRepositoryReader
import com.azure.android.communication.ui.chat.repository.MessageRepositoryWriter
import java.util.Collections
internal class MessageRepositoryListWriter : MessageRepositoryWriter {

Просмотреть файл

@ -0,0 +1,118 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.repository.storage
import com.azure.android.communication.ui.chat.models.EMPTY_MESSAGE_INFO_MODEL
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.repository.MessageRepositoryReader
import com.azure.android.communication.ui.chat.repository.MessageRepositoryWriter
import java.util.TreeMap
internal class MessageRepositoryTreeWriter : MessageRepositoryWriter {
private val treeMapStoragePointer: TreeMap<Long, String> = TreeMap()
private val treeMapStorage: TreeMap<String, MessageInfoModel> = TreeMap()
val size: Int
get() = treeMapStorage.size
override fun addLocalMessage(messageInfoModel: MessageInfoModel) {
val orderId: Long = getOrderId(messageInfoModel)
messageInfoModel.id?.let { treeMapStoragePointer.put(orderId, it) }
messageInfoModel.id?.let { treeMapStorage.put(it, messageInfoModel) }
}
override fun addPage(page: List<MessageInfoModel>) {
page.forEach { it -> addLocalMessage(it) }
}
override fun addServerMessage(message: MessageInfoModel) {
addLocalMessage(message)
}
override fun removeMessage(message: MessageInfoModel) {
val orderId = getOrderId(message)
if (treeMapStoragePointer.contains(orderId)) {
treeMapStoragePointer.remove(orderId)
treeMapStorage.remove(message.id)
}
}
override fun editMessage(message: MessageInfoModel) {
val orderId = getOrderId(message)
if (treeMapStoragePointer.contains(orderId)) {
treeMapStorage.get(message.id)?.let { mergeWithPreviousMessage(it, message) }
} else {
addLocalMessage(message)
}
}
override fun getLastMessage(): MessageInfoModel? {
val key = treeMapStoragePointer.lastKey()
return treeMapStorage.get(treeMapStoragePointer.get(key))!!
}
fun searchItem(kth: Int): MessageInfoModel {
var highestKey = treeMapStoragePointer.lastKey() + 1
var lowestKey = treeMapStoragePointer.firstKey()
var elements = 0
var midKey: Long = 0
while (lowestKey <= highestKey) {
midKey = (highestKey + lowestKey).div(2)
elements = treeMapStoragePointer.headMap(midKey).size
if (elements < kth) {
lowestKey = midKey + 1
} else if (elements > kth) {
highestKey = midKey - 1
} else {
break
}
}
val key = treeMapStoragePointer.headMap(midKey).lastKey()
return treeMapStorage.get(treeMapStoragePointer.get(key))!!
}
private fun getOrderId(message: MessageInfoModel): Long {
return message.id?.toLong() ?: 0
}
private fun mergeWithPreviousMessage(
previousMessage: MessageInfoModel,
message: MessageInfoModel
): MessageInfoModel {
var newMessage = MessageInfoModel(
id = previousMessage.id,
internalId = previousMessage.internalId,
content = message.content,
messageType = previousMessage.messageType,
version = previousMessage.version,
senderDisplayName = previousMessage.senderDisplayName,
createdOn = previousMessage.createdOn,
editedOn = previousMessage.editedOn,
deletedOn = previousMessage.deletedOn,
senderCommunicationIdentifier = previousMessage.senderCommunicationIdentifier,
isCurrentUser = previousMessage.isCurrentUser,
)
return newMessage
}
}
internal class MessageRepositoryTreeReader(private val writer: MessageRepositoryTreeWriter) : MessageRepositoryReader() {
override val size: Int
get() = writer.size
override fun get(index: Int): MessageInfoModel = try {
writer.searchItem(index + 1)
} catch (exception: Exception) {
EMPTY_MESSAGE_INFO_MODEL
}
}

Просмотреть файл

@ -0,0 +1,82 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package com.azure.android.communication.ui.chat.repository.storage
import com.azure.android.communication.ui.chat.models.MessageInfoModel
import com.azure.android.communication.ui.chat.repository.MessageRepository
import com.azure.android.communication.ui.chat.service.sdk.wrapper.ChatMessageType
import org.junit.Assert
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.junit.MockitoJUnitRunner
@RunWith(MockitoJUnitRunner::class)
class MessageRepositoryTreeStorageUnitTest {
@Test
fun messageRepositoryTreeStorage_addPage_test() {
val storage = MessageRepository.createTreeBackedRepository()
val numberOfTestMessages = 170
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
Assert.assertEquals(numberOfTestMessages, storage.size)
for (i in 1..numberOfTestMessages) {
Assert.assertEquals("Message $i", storage[i - 1].content)
}
// Assert.assertEquals("Message 16", storage[15].content)
}
@Test
fun messageRepositoryTreeStorage_removeMessage_test() {
val storage = MessageRepository.createTreeBackedRepository()
val numberOfTestMessages = 17
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.removeMessage(
MessageInfoModel(
id = "17",
content = "Message 17",
messageType = ChatMessageType.TEXT
)
)
Assert.assertEquals(numberOfTestMessages - 1, storage.size)
Assert.assertEquals(16, storage.getLastMessage()?.id?.toLong() ?: 0)
}
@Test
fun messageRepositoryTreeStorage_getLastMessage_test() {
val storage = MessageRepository.createTreeBackedRepository()
val numberOfTestMessages = 17
for (i in 1..numberOfTestMessages) {
storage.addLocalMessage(
MessageInfoModel(
id = i.toString(),
content = "Message $i",
messageType = ChatMessageType.TEXT
)
)
}
storage.getLastMessage()?.id?.let { Assert.assertEquals(17, it.toLong()) }
}
}