Commit ac7a6e1e authored by Lucio Maciel's avatar Lucio Maciel

Read messages from DB

parent cc24c585
673693445664
673693445664
......@@ -190,7 +190,7 @@ class ChatRoomAdapter(
notifyDataSetChanged()
}
fun updateItem(message: BaseUiModel<*>) {
fun updateItem(message: BaseUiModel<*>): Boolean {
val index = dataSet.indexOfLast { it.messageId == message.messageId }
val indexOfNext = dataSet.indexOfFirst { it.messageId == message.messageId }
Timber.d("index: $index")
......@@ -209,7 +209,9 @@ class ChatRoomAdapter(
dataSet.removeAt(indexOfNext)
notifyItemRemoved(indexOfNext)
}
return true
}
return false
}
fun removeItem(messageId: String) {
......
......@@ -226,7 +226,6 @@ class ChatRoomPresenter @Inject constructor(
retryIO("loadAndShowMessages($chatRoomId, $chatRoomType, $offset") {
client.messages(chatRoomId, roomTypeOf(chatRoomType), offset, 30).result
}
dbManager.processMessagesBatch(messages)
messagesRepository.saveAll(messages)
//we are saving last sync date of latest synced chat room message
......@@ -304,7 +303,7 @@ class ChatRoomPresenter @Inject constructor(
type = null,
updatedAt = null,
urls = null,
isTemporary = true,
synced = false,
unread = true
)
try {
......@@ -316,6 +315,7 @@ class ChatRoomPresenter @Inject constructor(
), false
)
client.sendMessage(id, chatRoomId, text)
messagesRepository.save(newMessage.copy(synced = true))
logMessageSent()
} catch (ex: Exception) {
// Ok, not very beautiful, but the backend sends us a not valid response
......@@ -511,8 +511,6 @@ class ChatRoomPresenter @Inject constructor(
}
Timber.d("History: $messages")
dbManager.processMessagesBatch(messages.result)
if (messages.result.isNotEmpty()) {
val models = mapper.map(messages.result, RoomUiModel(
roles = chatRoles,
......@@ -1128,11 +1126,11 @@ class ChatRoomPresenter @Inject constructor(
val index = roomMessages.indexOfFirst { msg -> msg.id == streamedMessage.id }
if (index > -1) {
Timber.d("Updating message at $index")
messagesRepository.save(streamedMessage)
//messagesRepository.save(streamedMessage)
view.dispatchUpdateMessage(index, viewModelStreamedMessage)
} else {
Timber.d("Adding new message")
messagesRepository.save(streamedMessage)
//messagesRepository.save(streamedMessage)
view.showNewMessage(viewModelStreamedMessage, true)
}
}
......
......@@ -2,9 +2,11 @@ package chat.rocket.android.chatroom.service
import android.app.job.JobParameters
import android.app.job.JobService
import chat.rocket.android.server.domain.CurrentServerRepository
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.db.DatabaseManagerFactory
import chat.rocket.android.server.domain.GetAccountsInteractor
import chat.rocket.android.server.infraestructure.ConnectionManagerFactory
import chat.rocket.android.server.infraestructure.DatabaseMessageMapper
import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository
import chat.rocket.core.internal.rest.sendMessage
import chat.rocket.core.model.Message
import dagger.android.AndroidInjection
......@@ -17,9 +19,9 @@ class MessageService : JobService() {
@Inject
lateinit var factory: ConnectionManagerFactory
@Inject
lateinit var currentServerRepository: CurrentServerRepository
lateinit var dbFactory: DatabaseManagerFactory
@Inject
lateinit var messageRepository: MessagesRepository
lateinit var getAccountsInteractor: GetAccountsInteractor
override fun onCreate() {
super.onCreate()
......@@ -32,21 +34,21 @@ class MessageService : JobService() {
override fun onStartJob(params: JobParameters?): Boolean {
launch(CommonPool) {
val currentServer = currentServerRepository.get()
if (currentServer != null) {
retrySendingMessages(params, currentServer)
jobFinished(params, false)
getAccountsInteractor.get().forEach { account ->
retrySendingMessages(params, account.serverUrl)
}
jobFinished(params, false)
}
return true
}
private suspend fun retrySendingMessages(params: JobParameters?, currentServer: String) {
private suspend fun retrySendingMessages(params: JobParameters?, serverUrl: String) {
val dbManager = dbFactory.create(serverUrl)
val messageRepository = DatabaseMessagesRepository(dbManager, DatabaseMessageMapper())
val temporaryMessages = messageRepository.getAllUnsent()
.sortedWith(compareBy(Message::timestamp))
if (temporaryMessages.isNotEmpty()) {
val connectionManager = factory.create(currentServer)
val client = connectionManager.client
val client = factory.create(serverUrl).client
temporaryMessages.forEach { message ->
try {
client.sendMessage(
......@@ -57,7 +59,7 @@ class MessageService : JobService() {
attachments = message.attachments,
alias = message.senderAlias
)
messageRepository.save(message.copy(isTemporary = false))
messageRepository.save(message.copy(synced = true))
Timber.d("Sent scheduled message given by id: ${message.id}")
} catch (ex: Exception) {
Timber.e(ex)
......@@ -71,7 +73,7 @@ class MessageService : JobService() {
// some other error
if (ex.message?.contains("E11000", true) == true) {
// XXX: Temporary solution. We need proper error codes from the api.
messageRepository.save(message.copy(isTemporary = false))
messageRepository.save(message.copy(synced = false))
}
jobFinished(params, true)
}
......
......@@ -545,9 +545,12 @@ class ChatRoomFragment : Fragment(), ChatRoomView, EmojiKeyboardListener, EmojiR
override fun dispatchUpdateMessage(index: Int, message: List<BaseUiModel<*>>) {
ui {
adapter.updateItem(message.last())
if (message.size > 1) {
adapter.prependData(listOf(message.first()))
if (adapter.updateItem(message.last())) {
if (message.size > 1) {
adapter.prependData(listOf(message.first()))
}
} else {
showNewMessage(message, true)
}
}
}
......
......@@ -477,7 +477,7 @@ class UiModelMapper @Inject constructor(
val time = getTime(message.timestamp)
val avatar = getUserAvatar(message)
val preview = mapMessagePreview(message)
val isTemp = message.isTemporary ?: false
val synced = message.synced ?: true
val unread = if (settings.messageReadReceiptEnabled()) {
message.unread ?: false
} else {
......@@ -492,7 +492,7 @@ class UiModelMapper @Inject constructor(
messageId = message.id, avatar = avatar!!, time = time, senderName = sender,
content = content, isPinned = message.pinned, currentDayMarkerText = dayMarkerText,
showDayMarker = false, reactions = getReactions(message), isFirstUnread = false,
preview = preview, isTemporary = isTemp, unread = unread)
preview = preview, isTemporary = !synced, unread = unread)
}
private fun mapMessagePreview(message: Message): Message {
......
......@@ -43,6 +43,8 @@ import chat.rocket.android.server.domain.RoomRepository
import chat.rocket.android.server.domain.SettingsRepository
import chat.rocket.android.server.domain.TokenRepository
import chat.rocket.android.server.domain.UsersRepository
import chat.rocket.android.server.infraestructure.DatabaseMessageMapper
import chat.rocket.android.server.infraestructure.DatabaseMessagesRepository
import chat.rocket.android.server.infraestructure.JobSchedulerInteractorImpl
import chat.rocket.android.server.infraestructure.MemoryActiveUsersRepository
import chat.rocket.android.server.infraestructure.MemoryChatRoomsRepository
......@@ -253,7 +255,7 @@ class AppModule {
return SharedPreferencesMultiServerTokenRepository(repository, moshi)
}
@Provides
/*@Provides
@Singleton
fun provideMessageRepository(
@ForMessages preferences: SharedPreferences,
......@@ -261,6 +263,11 @@ class AppModule {
currentServerInteractor: GetCurrentServerInteractor
): MessagesRepository {
return SharedPreferencesMessagesRepository(preferences, moshi, currentServerInteractor)
}*/
@Provides
fun provideMessageRepository(databaseManager: DatabaseManager): MessagesRepository {
return DatabaseMessagesRepository(databaseManager, DatabaseMessageMapper())
}
@Provides
......
......@@ -5,12 +5,11 @@ import chat.rocket.android.R
import chat.rocket.android.db.model.BaseMessageEntity
import chat.rocket.android.db.model.BaseUserEntity
import chat.rocket.android.db.model.ChatRoomEntity
import chat.rocket.android.db.model.MessageChannelsRelation
import chat.rocket.android.db.model.MessageChannels
import chat.rocket.android.db.model.MessageEntity
import chat.rocket.android.db.model.MessageFavoritesRelation
import chat.rocket.android.db.model.MessageMentionsRelation
import chat.rocket.android.db.model.ReactionEntity
import chat.rocket.android.db.model.ReactionMessageRelation
import chat.rocket.android.db.model.UrlEntity
import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.db.model.UserStatus
......@@ -31,6 +30,7 @@ import chat.rocket.core.model.Myself
import chat.rocket.core.model.Room
import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.userId
import kotlinx.coroutines.experimental.Job
import kotlinx.coroutines.experimental.launch
import kotlinx.coroutines.experimental.newSingleThreadContext
import kotlinx.coroutines.experimental.withContext
......@@ -44,7 +44,7 @@ class DatabaseManager(val context: Application,
RCDatabase::class.java, serverUrl.databaseName())
.fallbackToDestructiveMigration()
.build()
private val dbContext = newSingleThreadContext("$serverUrl-db-context")
val dbContext = newSingleThreadContext("$serverUrl-db-context")
private val insertSubs = HashMap<String, Subscription>()
private val insertRooms = HashMap<String, Room>()
......@@ -58,6 +58,11 @@ class DatabaseManager(val context: Application,
fun clearUsersStatus() {
launch(dbContext) {
userDao().clearStatus()
val message = messageDao().getMessageById("Ne6Wm9LqvZuBFiyME")
message?.let {
Timber.d("MESSAGE FROM DB: $it")
}
}
}
......@@ -153,8 +158,8 @@ class DatabaseManager(val context: Application,
}
}
fun processMessagesBatch(messages: List<Message>) {
launch(dbContext) {
fun processMessagesBatch(messages: List<Message>): Job {
return launch(dbContext) {
val dao = messageDao()
val list = mutableListOf<Pair<MessageEntity, List<BaseMessageEntity>>>()
messages.forEach { message ->
......@@ -190,11 +195,9 @@ class DatabaseManager(val context: Application,
val list = mutableListOf<BaseMessageEntity>()
reactions.keys.forEach { reaction ->
list.add(ReactionEntity(reaction))
val users = reactions[reaction]
users?.size?.let { size ->
list.add(ReactionMessageRelation(reaction, message.id, size))
users?.let { users ->
list.add(ReactionEntity(reaction, message.id, users.size, users.joinToString()))
}
}
......@@ -220,9 +223,9 @@ class DatabaseManager(val context: Application,
return null
}
val list = mutableListOf<MessageChannelsRelation>()
val list = mutableListOf<MessageChannels>()
message.channels!!.forEach { channel ->
list.add(MessageChannelsRelation(message.id, channel.id, channel.name))
list.add(MessageChannels(message.id, channel.id, channel.name))
}
return list
......
package chat.rocket.android.db
import chat.rocket.android.infrastructure.MessagesRepository
import chat.rocket.core.model.Message
class DatabaseMessagesRepository(val dbManager: DatabaseManager) : MessagesRepository {
override fun saveMessage(message: Message) {
saveMessages(listOf(message))
}
override fun saveMessages(messages: List<Message>) {
messages.forEach { message ->
}
}
}
\ No newline at end of file
......@@ -8,15 +8,17 @@ import androidx.room.Transaction
import chat.rocket.android.db.model.AttachmentEntity
import chat.rocket.android.db.model.AttachmentFieldEntity
import chat.rocket.android.db.model.BaseMessageEntity
import chat.rocket.android.db.model.MessageChannelsRelation
import chat.rocket.android.db.model.FullMessage
import chat.rocket.android.db.model.PartialMessage
import chat.rocket.android.db.model.MessageChannels
import chat.rocket.android.db.model.MessageEntity
import chat.rocket.android.db.model.MessageFavoritesRelation
import chat.rocket.android.db.model.MessageMentionsRelation
import chat.rocket.android.db.model.MessagesSync
import chat.rocket.android.db.model.ReactionEntity
import chat.rocket.android.db.model.ReactionMessageRelation
import chat.rocket.android.db.model.UrlEntity
import chat.rocket.android.db.model.UserEntity
import timber.log.Timber
@Dao
abstract class MessageDao {
@Insert
......@@ -29,7 +31,7 @@ abstract class MessageDao {
abstract fun insert(relation: MessageMentionsRelation)
@Insert
abstract fun insert(relation: MessageChannelsRelation)
abstract fun insert(relation: MessageChannels)
@Insert
abstract fun insert(attachment: AttachmentEntity)
......@@ -40,15 +42,15 @@ abstract class MessageDao {
@Insert(onConflict = OnConflictStrategy.IGNORE)
abstract fun insert(reaction: ReactionEntity)
@Insert
abstract fun insert(relation: ReactionMessageRelation)
@Insert
abstract fun insert(url: UrlEntity)
@Query("DELETE FROM messages WHERE id = :id")
abstract fun delete(id: String)
@Query("DELETE FROM messages WHERE roomId = :roomId")
abstract fun deleteByRoomId(roomId: String)
@Transaction
open fun insert(message: MessageEntity, entities: List<BaseMessageEntity>) {
insertInternal(message, entities)
......@@ -68,11 +70,10 @@ abstract class MessageDao {
is MessageEntity -> insert(entity)
is MessageFavoritesRelation -> insert(entity)
is MessageMentionsRelation -> insert(entity)
is MessageChannelsRelation -> insert(entity)
is MessageChannels -> insert(entity)
is AttachmentEntity -> insert(entity)
is AttachmentFieldEntity -> insert(entity)
is ReactionEntity -> insert(entity)
is ReactionMessageRelation -> insert(entity)
is UrlEntity -> insert(entity)
}
}
......@@ -83,4 +84,105 @@ abstract class MessageDao {
insertInternal(message, entities)
}
}
//@Query("SELECT * FROM messages WHERE id = :id")
@Query("""
$BASE_MESSAGE_QUERY
WHERE messages.id = :id
""")
abstract fun internalGetMessageById(id: String): PartialMessage?
@Transaction
open fun getMessageById(id: String): FullMessage? {
return internalGetMessageById(id)?.let { message ->
retrieveFullMessage(message)
}
}
@Query("""
$BASE_MESSAGE_QUERY
WHERE messages.roomId = :roomId
ORDER BY messages.timestamp DESC
"""
)
abstract fun internalGetMessagesByRoomId(roomId: String): List<PartialMessage>
@Query("""
$BASE_MESSAGE_QUERY
WHERE messages.roomId = :roomId
ORDER BY messages.timestamp DESC
LIMIT :count
"""
)
abstract fun internalGetRecentMessagesByRoomId(roomId: String, count: Long): List<PartialMessage>
@Transaction
open fun getMessagesByRoomId(roomId: String): List<FullMessage> {
return internalGetMessagesByRoomId(roomId).map { message ->
retrieveFullMessage(message)
}
}
@Transaction
open fun getRecentMessagesByRoomId(roomId: String, count: Long): List<FullMessage> {
return internalGetRecentMessagesByRoomId(roomId, count).map { message ->
retrieveFullMessage(message)
}
}
@Query("""
SELECT * FROM users WHERE users.id IN
(SELECT userId FROM message_favorites WHERE messageId = :messageId)
""")
abstract fun getFavoritesByMessage(messageId: String): List<UserEntity>
@Query("""
SELECT * FROM users WHERE users.id IN
(SELECT userId FROM message_mentions WHERE messageId = :messageId)
""")
abstract fun getMentionsByMessage(messageId: String): List<UserEntity>
@Query("""
$BASE_MESSAGE_QUERY
WHERE synced = 0
ORDER BY messages.timestamp DESC
""")
abstract fun internalUnsetMessages(): List<PartialMessage>
@Transaction
open fun getUnsentMessages(): List<FullMessage> {
return internalUnsetMessages().map { message ->
retrieveFullMessage(message)
}
}
internal fun retrieveFullMessage(message: PartialMessage): FullMessage {
val favorites = getFavoritesByMessage(message.message.id)
val mentions = getFavoritesByMessage(message.message.id)
return FullMessage(message, favorites, mentions)
}
@Query("SELECT * FROM messages_sync WHERE roomId = :roomId")
abstract fun getLastSync(roomId: String): MessagesSync?
@Insert(onConflict = OnConflictStrategy.REPLACE)
abstract fun saveLastSync(entity: MessagesSync)
companion object {
const val BASE_MESSAGE_QUERY = """
SELECT
messages.*,
senderBy.name as senderName,
senderBy.username as senderUsername,
editBy.name as editName,
editBy.username as editUsername
FROM messages
LEFT JOIN urls as u ON u.messageId = messages.id
LEFT JOIN attachments as attachment ON attachment.message_id = messages.id
LEFT JOIN reactions ON reactions.messageId = messages.id
LEFT JOIN message_channels ON message_channels.messageId = messages.id
LEFT JOIN users as senderBy ON messages.senderId = senderBy.id
LEFT JOIN users as editBy ON messages.editedBy = editBy.id
"""
}
}
\ No newline at end of file
......@@ -5,12 +5,12 @@ import androidx.room.RoomDatabase
import chat.rocket.android.db.model.AttachmentEntity
import chat.rocket.android.db.model.AttachmentFieldEntity
import chat.rocket.android.db.model.ChatRoomEntity
import chat.rocket.android.db.model.MessageChannelsRelation
import chat.rocket.android.db.model.MessageChannels
import chat.rocket.android.db.model.MessageEntity
import chat.rocket.android.db.model.MessageFavoritesRelation
import chat.rocket.android.db.model.MessageMentionsRelation
import chat.rocket.android.db.model.MessagesSync
import chat.rocket.android.db.model.ReactionEntity
import chat.rocket.android.db.model.ReactionMessageRelation
import chat.rocket.android.db.model.UrlEntity
import chat.rocket.android.db.model.UserEntity
......@@ -18,11 +18,11 @@ import chat.rocket.android.db.model.UserEntity
entities = [
UserEntity::class, ChatRoomEntity::class, MessageEntity::class,
MessageFavoritesRelation::class, MessageMentionsRelation::class,
MessageChannelsRelation::class, AttachmentEntity::class,
MessageChannels::class, AttachmentEntity::class,
AttachmentFieldEntity::class, UrlEntity::class, ReactionEntity::class,
ReactionMessageRelation::class
MessagesSync::class
],
version = 6,
version = 7,
exportSchema = true
)
abstract class RCDatabase : RoomDatabase() {
......
......@@ -21,7 +21,7 @@ import timber.log.Timber
])
data class AttachmentEntity(
@PrimaryKey
var id: String,
var _id: String,
@ColumnInfo(name = "message_id")
val messageId: String,
val title: String? = null,
......@@ -66,7 +66,7 @@ data class AttachmentEntity(
@Entity(tableName = "attachment_fields",
foreignKeys = [
ForeignKey(entity = AttachmentEntity::class, parentColumns = ["id"],
ForeignKey(entity = AttachmentEntity::class, parentColumns = ["_id"],
childColumns = ["attachmentId"], onDelete = ForeignKey.CASCADE)
])
data class AttachmentFieldEntity(
......@@ -96,7 +96,7 @@ fun Attachment.asEntity(msgId: String): List<BaseMessageEntity> {
fun ImageAttachment.asEntity(msgId: String): AttachmentEntity =
AttachmentEntity(
id = "${msgId}_${hashCode()}",
_id = "${msgId}_${hashCode()}",
messageId = msgId,
title = title,
description = description,
......@@ -110,7 +110,7 @@ fun ImageAttachment.asEntity(msgId: String): AttachmentEntity =
fun VideoAttachment.asEntity(msgId: String): AttachmentEntity =
AttachmentEntity(
id = "${msgId}_${hashCode()}",
_id = "${msgId}_${hashCode()}",
messageId = msgId,
title = title,
description = description,
......@@ -124,7 +124,7 @@ fun VideoAttachment.asEntity(msgId: String): AttachmentEntity =
fun AudioAttachment.asEntity(msgId: String): AttachmentEntity =
AttachmentEntity(
id = "${msgId}_${hashCode()}",
_id = "${msgId}_${hashCode()}",
messageId = msgId,
title = title,
description = description,
......@@ -139,7 +139,7 @@ fun AudioAttachment.asEntity(msgId: String): AttachmentEntity =
fun AuthorAttachment.asEntity(msgId: String): List<BaseMessageEntity> {
val list = mutableListOf<BaseMessageEntity>()
val attachment = AttachmentEntity(
id = "${msgId}_${hashCode()}",
_id = "${msgId}_${hashCode()}",
messageId = msgId,
authorLink = url,
authorIcon = authorIcon,
......@@ -149,7 +149,7 @@ fun AuthorAttachment.asEntity(msgId: String): List<BaseMessageEntity> {
fields?.forEach { field ->
val entity = AttachmentFieldEntity(
attachmentId = attachment.id,
attachmentId = attachment._id,
title = field.title,
value = field.value
)
......@@ -161,7 +161,7 @@ fun AuthorAttachment.asEntity(msgId: String): List<BaseMessageEntity> {
fun ColorAttachment.asEntity(msgId: String): AttachmentEntity =
AttachmentEntity(
id = "${msgId}_${hashCode()}",
_id = "${msgId}_${hashCode()}",
messageId = msgId,
color = color.rawColor
)
......@@ -169,7 +169,7 @@ fun ColorAttachment.asEntity(msgId: String): AttachmentEntity =
// TODO - how to model An message attachment with attachments???
fun MessageAttachment.asEntity(msgId: String): AttachmentEntity =
AttachmentEntity(
id = "${msgId}_${hashCode()}",
_id = "${msgId}_${hashCode()}",
messageId = msgId,
authorName = author,
authorIcon = icon,
......
package chat.rocket.android.db.model
import androidx.room.Embedded
import androidx.room.Entity
import androidx.room.ForeignKey
import androidx.room.PrimaryKey
import androidx.room.Relation
interface BaseMessageEntity
......@@ -26,7 +28,9 @@ data class MessageEntity(
val groupable: Boolean = false,
val parseUrls: Boolean = false,
val pinned: Boolean = false,
val role: String?
val role: String?,
val synced: Boolean = true,
val unread: Boolean? = null
) : BaseMessageEntity
@Entity(tableName = "message_favorites",
......@@ -60,8 +64,43 @@ data class MessageMentionsRelation(
childColumns = ["messageId"], onDelete = ForeignKey.CASCADE)
]
)
data class MessageChannelsRelation(
data class MessageChannels(
val messageId: String,
val roomId: String,
val roomName: String?
) : BaseMessageEntity
@Entity(tableName = "messages_sync")
data class MessagesSync(
@PrimaryKey val roomId: String,
val timestamp: Long
)
data class PartialMessage(
@Embedded val message: MessageEntity,
val senderName: String?,
val senderUsername: String?,
val editName: String?,
val editUsername: String?
) {
@Relation(parentColumn = "id", entityColumn = "messageId")
var urls: List<UrlEntity>? = null
@Relation(parentColumn = "id", entityColumn = "message_id")
var attachments: List<AttachmentEntity>? = null
@Relation(parentColumn = "id", entityColumn = "messageId")
var reactions: List<ReactionEntity>? = null
@Relation(parentColumn = "id", entityColumn = "messageId")
var channels: List<MessageChannels>? = null
override fun toString(): String {
return "PartialMessage(message=$message, senderName=$senderName, senderUsername=$senderUsername, editName=$editName, editUsername=$editUsername, urls=$urls, attachments=$attachments, reactions=$reactions, channels=$channels)"
}
}
data class FullMessage(
val message: PartialMessage,
val favorites: List<UserEntity>,
val mentions: List<UserEntity>
)
......@@ -5,15 +5,8 @@ import androidx.room.ForeignKey
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(tableName = "reactions")
data class ReactionEntity(
@PrimaryKey val reaction: String
) : BaseMessageEntity
@Entity(tableName = "reactions_message_relations",
@Entity(tableName = "reactions",
foreignKeys = [
ForeignKey(entity = ReactionEntity::class, parentColumns = ["reaction"],
childColumns = ["reactionId"]),
ForeignKey(entity = MessageEntity::class, parentColumns = ["id"],
childColumns = ["messageId"], onDelete = ForeignKey.CASCADE)
],
......@@ -21,11 +14,9 @@ data class ReactionEntity(
Index(value = ["messageId"])
]
)
data class ReactionMessageRelation(
val reactionId: String,
data class ReactionEntity(
@PrimaryKey val reaction: String,
val messageId: String,
val count: Int
) : BaseMessageEntity {
@PrimaryKey(autoGenerate = true)
var id: Long? = null
}
\ No newline at end of file
val count: Int,
val usernames: String
) : BaseMessageEntity
\ No newline at end of file
......@@ -22,5 +22,5 @@ data class UrlEntity(
val imageUrl: String?
) : BaseMessageEntity {
@PrimaryKey(autoGenerate = true)
var id: Long? = null
var urlId: Long? = null
}
\ No newline at end of file
package chat.rocket.android.infrastructure
import chat.rocket.core.model.Message
interface MessagesRepository {
fun saveMessage(message: Message)
fun saveMessages(messages: List<Message>)
}
\ No newline at end of file
......@@ -16,27 +16,20 @@ interface MessagesRepository {
/**
* Get all messages from the current room id.
*
* @param rid The room id.
* @param roomId The room id.
* @return A list of Message objects for the room with given room id or an empty list.
*/
suspend fun getByRoomId(rid: String): List<Message>
suspend fun getByRoomId(roomId: String): List<Message>
/**
* Get most recent messages up to count different users.
*
* @param rid The id of the room the messages are.
* @param roomId The id of the room the messages are.
* @param count The count last messages to get.
*
* @return List of last count messages.
*/
suspend fun getRecentMessages(rid: String, count: Long): List<Message>
/**
* Get all messages. Use carefully!
*
* @return All messages or an empty list.
*/
suspend fun getAll(): List<Message>
suspend fun getRecentMessages(roomId: String, count: Long): List<Message>
/**
* Save a single message object.
......@@ -50,11 +43,6 @@ interface MessagesRepository {
*/
suspend fun saveAll(newMessages: List<Message>)
/**
* Removes all messages.
*/
suspend fun clear()
/**
* Remove message by id.
*
......@@ -65,29 +53,27 @@ interface MessagesRepository {
/**
* Remove all messages from a given room.
*
* @param rid The room id where messages are to be removed.
* @param roomId The room id where messages are to be removed.
*/
suspend fun removeByRoomId(rid: String)
suspend fun removeByRoomId(roomId: String)
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 roomId 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)
suspend fun saveLastSyncDate(roomId: 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.
* @param roomId The id of the room the messages are.
*
* @return Last Sync time or Null.
*/
suspend fun getLastSyncDate(rid: String): Long?
suspend fun getLastSyncDate(roomId: String): Long?
}
\ No newline at end of file
......@@ -133,6 +133,13 @@ class ConnectionManager(
maxSize = 100, maxTime = 500) { messages ->
Timber.d("Processing Messages batch: ${messages.size}")
dbManager.processMessagesBatch(messages.distinctBy { it.id })
launch {
messages.forEach { message ->
val channel = roomMessagesChannels[message.roomId]
channel?.send(message)
}
}
}
// stream-notify-user - ${userId}/rooms-changed
......@@ -161,8 +168,6 @@ class ConnectionManager(
for (message in client.messagesChannel) {
Timber.d("Received new Message for room ${message.roomId}")
messagesActor.send(message)
val channel = roomMessagesChannels[message.roomId]
channel?.send(message)
}
}
......
package chat.rocket.android.server.infraestructure
import chat.rocket.android.db.model.AttachmentEntity
import chat.rocket.android.db.model.FullMessage
import chat.rocket.android.db.model.ReactionEntity
import chat.rocket.android.db.model.UrlEntity
import chat.rocket.android.db.model.UserEntity
import chat.rocket.common.model.SimpleRoom
import chat.rocket.common.model.SimpleUser
import chat.rocket.core.model.Message
import chat.rocket.core.model.Reactions
import chat.rocket.core.model.attachment.Attachment
import chat.rocket.core.model.messageTypeOf
import chat.rocket.core.model.url.Meta
import chat.rocket.core.model.url.ParsedUrl
import chat.rocket.core.model.url.Url
class DatabaseMessageMapper {
fun map(message: FullMessage): Message? = map(listOf(message)).firstOrNull()
fun map(messages: List<FullMessage>): List<Message> {
val list = mutableListOf<Message>()
messages.forEach { message ->
val favorites = mutableListOf<SimpleUser>()
message.favorites.forEach { user ->
favorites.add(mapUser(user))
}
val mentions = mutableListOf<SimpleUser>()
message.mentions.forEach { user ->
mentions.add(mapUser(user))
}
val channels = mutableListOf<SimpleRoom>()
message.message.channels?.forEach { channel ->
channels.add(SimpleRoom(channel.roomId, channel.roomName))
}
with(message.message) {
val sender = this.message.senderId?.let { id ->
SimpleUser(id, this.senderUsername, this.senderName)
}
val editedBy = this.message.editedBy?.let { id ->
SimpleUser(id, this.editUsername, this.editName)
}
val urls = this.urls?.let { mapUrl(it) }
val reactions = this.reactions?.let { mapReactions(it) }
val attachments = this.attachments?.let { mapAttachments(it) }
val messageType = messageTypeOf(this.message.type)
list.add(Message(
id = this.message.id,
roomId = this.message.roomId,
message = this.message.message,
timestamp = this.message.timestamp,
sender = sender,
updatedAt = this.message.updatedAt,
editedAt = this.message.editedAt,
editedBy = editedBy,
senderAlias = this.message.senderAlias,
avatar = this.message.avatar,
type = messageType,
groupable = this.message.groupable,
parseUrls = this.message.parseUrls,
urls = urls,
mentions = mentions,
channels = channels,
attachments = attachments,
pinned = this.message.pinned,
starred = favorites,
reactions = reactions,
role = this.message.role,
synced = this.message.synced,
unread = this.message.unread
))
}
}
return list
}
private fun mapReactions(reactions: List<ReactionEntity>): Reactions {
val map = Reactions()
reactions.forEach { reaction ->
val usernames = reaction.usernames.split(",").map { it.trim() }
map[reaction.reaction] = usernames
}
return map
}
private fun mapUrl(urls: List<UrlEntity>): List<Url> {
val list = mutableListOf<Url>()
urls.forEach { url ->
val parsedUrl = url.hostname?.let {
ParsedUrl(host = it)
}
val meta = if (!url.description.isNullOrEmpty() || !url.imageUrl.isNullOrEmpty() || !url.title.isNullOrEmpty()) {
val raw = HashMap<String, String>()
if (url.description != null) raw["ogDescription"] = url.description
if (url.title != null) raw["ogTitle"] = url.title
if (url.imageUrl != null) raw["ogImage"] = url.imageUrl
Meta(title = url.title,description = url.description, imageUrl = url.imageUrl, raw = raw)
} else null
list.add(Url(url = url.url, meta = meta, parsedUrl = parsedUrl))
}
return list
}
private fun mapUser(user: UserEntity): SimpleUser {
return with(user) {
SimpleUser(
id = id,
username = username,
name = name
)
}
}
private fun mapAttachments(attachments: List<AttachmentEntity>): List<Attachment> {
val list = mutableListOf<Attachment>()
attachments.forEach { attachment ->
}
// TODO - implement mapping
return list
}
}
\ No newline at end of file
package chat.rocket.android.server.infraestructure
import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.model.MessagesSync
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.core.model.Message
import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext
class DatabaseMessagesRepository(
private val dbManager: DatabaseManager,
private val mapper: DatabaseMessageMapper
) : MessagesRepository {
override suspend fun getById(id: String): Message? = withContext(CommonPool) {
dbManager.messageDao().getMessageById(id)?.let { message -> mapper.map(message) }
}
override suspend fun getByRoomId(roomId: String): List<Message> = withContext(CommonPool) {
// FIXME - investigate how to avoid this distinctBy here, since DAO is returning a lot of
// duplicate rows (something related to our JOINS and relations on Room)
dbManager.messageDao().getMessagesByRoomId(roomId)
.distinctBy { it.message.message.id }
.let { messages ->
mapper.map(messages)
}
}
override suspend fun getRecentMessages(roomId: String, count: Long): List<Message> = withContext(CommonPool) {
dbManager.messageDao().getRecentMessagesByRoomId(roomId, count)
.distinctBy { it.message.message.id }
.let { messages ->
mapper.map(messages)
}
}
override suspend fun save(message: Message) {
dbManager.processMessagesBatch(listOf(message)).join()
}
override suspend fun saveAll(messages: List<Message>) {
dbManager.processMessagesBatch(messages).join()
}
override suspend fun removeById(id: String) {
withContext(CommonPool) {
dbManager.messageDao().delete(id)
}
}
override suspend fun removeByRoomId(roomId: String) {
withContext(CommonPool) {
dbManager.messageDao().deleteByRoomId(roomId)
}
}
override suspend fun getAllUnsent(): List<Message> = withContext(CommonPool) {
dbManager.messageDao().getUnsentMessages()
.distinctBy { it.message.message.id }
.let { mapper.map(it) }
}
override suspend fun saveLastSyncDate(roomId: String, timeMillis: Long) {
withContext(dbManager.dbContext) {
dbManager.messageDao().saveLastSync(MessagesSync(roomId, timeMillis))
}
}
override suspend fun getLastSyncDate(roomId: String): Long? = withContext(CommonPool) {
dbManager.messageDao().getLastSync(roomId)?.let { it.timestamp }
}
}
\ No newline at end of file
package chat.rocket.android.server.infraestructure
import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.core.model.Message
import kotlinx.coroutines.experimental.CommonPool
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]
}
override suspend fun getByRoomId(rid: String): List<Message> = withContext(CommonPool) {
return@withContext messages.filter { it.value.roomId == rid }.values.toList()
}
override suspend fun getRecentMessages(rid: String, count: Long): List<Message> = withContext(CommonPool) {
return@withContext getByRoomId(rid).sortedByDescending { it.timestamp }
.distinctBy { it.sender }.take(count.toInt())
}
override suspend fun getAll(): List<Message> = withContext(CommonPool) {
return@withContext messages.values.toList()
}
override suspend fun getUnsentByRoomId(roomId: String): List<Message> = withContext(CommonPool) {
val allByRoomId = getByRoomId(roomId)
if (allByRoomId.isEmpty()) {
return@withContext emptyList<Message>()
}
return@withContext allByRoomId.filter { it.isTemporary ?: false && it.roomId == roomId }
}
override suspend fun getAllUnsent(): List<Message> = withContext(CommonPool) {
val all = getAll()
if (all.isEmpty()) {
return@withContext emptyList<Message>()
}
return@withContext all.filter { it.isTemporary ?: false }
}
override suspend fun save(message: Message) = withContext(CommonPool) {
messages[message.id] = message
}
override suspend fun saveAll(newMessages: List<Message>) = withContext(CommonPool) {
for (msg in newMessages) {
messages[msg.id] = msg
}
}
override suspend fun clear() = withContext(CommonPool) {
messages.clear()
}
override suspend fun removeById(id: String) {
withContext(CommonPool) {
messages.remove(id)
}
}
override suspend fun removeByRoomId(rid: String) = withContext(CommonPool) {
val roomMessages = messages.filter { it.value.roomId == rid }.values
roomMessages.forEach {
messages.remove(it.roomId)
}
}
}
\ No newline at end of file
......@@ -68,19 +68,6 @@ class SharedPreferencesMessagesRepository(
.distinctBy { it.sender }.take(count.toInt())
}
override suspend fun getAll(): List<Message> = withContext(CommonPool) {
val adapter = moshi.adapter<Message>(Message::class.java)
if (prefs.all.values.isEmpty()) {
return@withContext emptyList<Message>()
}
currentServerInteractor.get()?.also { server ->
val values = prefs.all.entries.filter { it.key.startsWith(server) }
.map { it.value } as Collection<String>
return@withContext values.mapNotNull { adapter.fromJson(it) }
}
return@withContext emptyList<Message>()
}
override suspend fun getAllUnsent(): List<Message> = withContext(CommonPool) {
if (prefs.all.values.isEmpty()) {
return@withContext emptyList<Message>()
......@@ -90,19 +77,11 @@ class SharedPreferencesMessagesRepository(
.map { it.value } as Collection<String>
val adapter = moshi.adapter<Message>(Message::class.java)
return@withContext values.mapNotNull { adapter.fromJson(it) }
.filter { it.isTemporary ?: false }
.filterNot { it.synced ?: false }
}
return@withContext emptyList<Message>()
}
override suspend fun getUnsentByRoomId(roomId: String): List<Message> = withContext(CommonPool) {
val allByRoomId = getByRoomId(roomId)
if (allByRoomId.isEmpty()) {
return@withContext emptyList<Message>()
}
return@withContext allByRoomId.filter { it.isTemporary ?: false }
}
override suspend fun save(message: Message) {
withContext(CommonPool) {
currentServerInteractor.get()?.also {
......@@ -125,10 +104,6 @@ class SharedPreferencesMessagesRepository(
}
}
override suspend fun clear() = withContext(CommonPool) {
prefs.edit().clear().apply()
}
override suspend fun removeById(id: String) {
withContext(CommonPool) {
currentServerInteractor.get()?.also {
......
......@@ -31,20 +31,21 @@ suspend fun RocketChatClientFactory.registerPushToken(
fun Message.toEntity(): MessageEntity {
return MessageEntity(
id = id,
roomId = roomId,
message = message,
timestamp = timestamp,
senderId = sender?.id,
updatedAt = updatedAt,
editedAt = editedAt,
editedBy = editedBy?.id,
senderAlias = senderAlias,
avatar = avatar,
type = type.asString(),
groupable = groupable,
parseUrls = parseUrls,
pinned = pinned,
role = role
id = id,
roomId = roomId,
message = message,
timestamp = timestamp,
senderId = sender?.id,
updatedAt = updatedAt,
editedAt = editedAt,
editedBy = editedBy?.id,
senderAlias = senderAlias,
avatar = avatar,
type = type.asString(),
groupable = groupable,
parseUrls = parseUrls,
pinned = pinned,
role = role,
synced = synced
)
}
\ No newline at end of file
......@@ -10,7 +10,7 @@ buildscript {
}
dependencies {
classpath 'com.android.tools.build:gradle:3.2.0-rc03'
classpath 'com.android.tools.build:gradle:3.2.0'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:${versions.kotlin}"
classpath "org.jetbrains.dokka:dokka-gradle-plugin:${versions.dokka}"
classpath 'com.google.gms:google-services:4.0.2'
......
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