Commit c3037e3e authored by Lucio Maciel's avatar Lucio Maciel

retryDB

parent 644d6518
...@@ -3,9 +3,12 @@ package chat.rocket.android.chatrooms.infrastructure ...@@ -3,9 +3,12 @@ package chat.rocket.android.chatrooms.infrastructure
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import chat.rocket.android.db.ChatRoomDao import chat.rocket.android.db.ChatRoomDao
import chat.rocket.android.db.model.ChatRoom import chat.rocket.android.db.model.ChatRoom
import chat.rocket.android.util.retryDB
import javax.inject.Inject import javax.inject.Inject
class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao){ class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao) {
// TODO - check how to use retryDB here - suspend
fun getChatRooms(order: Order): LiveData<List<ChatRoom>> { fun getChatRooms(order: Order): LiveData<List<ChatRoom>> {
return when(order) { return when(order) {
Order.ACTIVITY -> dao.getAll() Order.ACTIVITY -> dao.getAll()
...@@ -15,9 +18,10 @@ class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao){ ...@@ -15,9 +18,10 @@ class ChatRoomsRepository @Inject constructor(private val dao: ChatRoomDao){
} }
} }
fun search(query: String) = dao.searchSync(query) suspend fun search(query: String) =
retryDB("roomSearch($query)") { dao.searchSync(query) }
fun count() = dao.count() suspend fun count() = retryDB("roomsCount") { dao.count() }
enum class Order { enum class Order {
ACTIVITY, ACTIVITY,
......
...@@ -19,6 +19,7 @@ import chat.rocket.android.util.extensions.exhaustive ...@@ -19,6 +19,7 @@ import chat.rocket.android.util.extensions.exhaustive
import chat.rocket.android.util.extensions.removeTrailingSlash import chat.rocket.android.util.extensions.removeTrailingSlash
import chat.rocket.android.util.extensions.toEntity import chat.rocket.android.util.extensions.toEntity
import chat.rocket.android.util.extensions.userId import chat.rocket.android.util.extensions.userId
import chat.rocket.android.util.retryDB
import chat.rocket.common.model.BaseRoom import chat.rocket.common.model.BaseRoom
import chat.rocket.common.model.RoomType import chat.rocket.common.model.RoomType
import chat.rocket.common.model.SimpleUser import chat.rocket.common.model.SimpleUser
...@@ -96,8 +97,10 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -96,8 +97,10 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} }
suspend fun getRoom(id: String) = withContext(dbManagerContext) { suspend fun getRoom(id: String) = withContext(dbManagerContext) {
retryDB("getRoom($id)") {
chatRoomDao().get(id) chatRoomDao().get(id)
} }
}
fun processUsersBatch(users: List<User>) { fun processUsersBatch(users: List<User>) {
launch(dbManagerContext) { launch(dbManagerContext) {
...@@ -151,7 +154,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -151,7 +154,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
fun updateSelfUser(myself: Myself) { fun updateSelfUser(myself: Myself) {
launch(dbManagerContext) { launch(dbManagerContext) {
val user = userDao().getUser(myself.id) val user = retryDB("getUser(${myself.id})") { userDao().getUser(myself.id) }
val entity = user?.copy( val entity = user?.copy(
name = myself.name ?: user.name, name = myself.name ?: user.name,
username = myself.username ?: user.username, username = myself.username ?: user.username,
...@@ -335,7 +338,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -335,7 +338,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} }
private suspend fun updateRoom(data: Room): ChatRoomEntity? { private suspend fun updateRoom(data: Room): ChatRoomEntity? {
return chatRoomDao().get(data.id)?.let { current -> return retryDB("getChatRoom(${data.id})") { chatRoomDao().get(data.id) }?.let { current ->
with(data) { with(data) {
val chatRoom = current.chatRoom val chatRoom = current.chatRoom
...@@ -373,7 +376,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -373,7 +376,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
context.getString(R.string.msg_sent_attachment) context.getString(R.string.msg_sent_attachment)
private suspend fun updateSubscription(data: Subscription): ChatRoomEntity? { private suspend fun updateSubscription(data: Subscription): ChatRoomEntity? {
return chatRoomDao().get(data.roomId)?.let { current -> return retryDB("getRoom(${data.roomId}") { chatRoomDao().get(data.roomId) }?.let { current ->
with(data) { with(data) {
val userId = if (type is RoomType.DirectMessage) { val userId = if (type is RoomType.DirectMessage) {
...@@ -539,9 +542,11 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -539,9 +542,11 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} }
} }
private fun findUser(userId: String): String? = userDao().findUser(userId) private suspend fun findUser(userId: String): String? =
retryDB("findUser($userId)") { userDao().findUser(userId) }
private fun doOperation(operation: Operation) { private suspend fun doOperation(operation: Operation) {
retryDB(description = "doOperation($operation)") {
when (operation) { when (operation) {
is Operation.ClearStatus -> userDao().clearStatus() is Operation.ClearStatus -> userDao().clearStatus()
is Operation.UpdateRooms -> { is Operation.UpdateRooms -> {
...@@ -573,6 +578,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) { ...@@ -573,6 +578,7 @@ class DatabaseManager(val context: Application, val serverUrl: String) {
} }
}.exhaustive }.exhaustive
} }
}
} }
sealed class Operation { sealed class Operation {
......
...@@ -7,6 +7,7 @@ import chat.rocket.android.db.model.FullMessage ...@@ -7,6 +7,7 @@ import chat.rocket.android.db.model.FullMessage
import chat.rocket.android.db.model.ReactionEntity import chat.rocket.android.db.model.ReactionEntity
import chat.rocket.android.db.model.UrlEntity import chat.rocket.android.db.model.UrlEntity
import chat.rocket.android.db.model.UserEntity import chat.rocket.android.db.model.UserEntity
import chat.rocket.android.util.retryDB
import chat.rocket.common.model.SimpleRoom import chat.rocket.common.model.SimpleRoom
import chat.rocket.common.model.SimpleUser import chat.rocket.common.model.SimpleUser
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
...@@ -135,14 +136,18 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -135,14 +136,18 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
with(attachment) { with(attachment) {
val fields = if (hasFields) { val fields = if (hasFields) {
withContext(CommonPool) { withContext(CommonPool) {
retryDB("getAttachmentFields(${attachment._id})") {
dbManager.messageDao().getAttachmentFields(attachment._id) dbManager.messageDao().getAttachmentFields(attachment._id)
}
}.map { Field(it.title, it.value) } }.map { Field(it.title, it.value) }
} else { } else {
null null
} }
val actions = if (hasActions) { val actions = if (hasActions) {
withContext(CommonPool) { withContext(CommonPool) {
retryDB("getAttachmentActions(${attachment._id})") {
dbManager.messageDao().getAttachmentActions(attachment._id) dbManager.messageDao().getAttachmentActions(attachment._id)
}
}.mapNotNull { mapAction(it) } }.mapNotNull { mapAction(it) }
} else { } else {
null null
...@@ -183,29 +188,6 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -183,29 +188,6 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
return list return list
} }
/*private suspend fun mapColorAttachmentWithFields(entity: AttachmentEntity): ColorAttachment {
val fields = withContext(CommonPool) {
dbManager.messageDao().getAttachmentFields(entity._id)
}.map { Field(it.title, it.value) }
return with(entity) {
ColorAttachment(
color = Color.Custom(color ?: DEFAULT_COLOR_STR),
text = text ?: "",
fallback = fallback,
fields = fields)
}
}
private suspend fun mapActionAttachment(attachment: AttachmentEntity): ActionsAttachment {
val actions = withContext(CommonPool) {
dbManager.messageDao().getAttachmentActions(attachment._id)
}.mapNotNull { mapAction(it) }
return with(attachment) {
// TODO - remove the default "vertical" value from here...
ActionsAttachment(title, actions, buttonAlignment ?: "vertical")
}
}*/
private fun mapAction(action: AttachmentActionEntity): Action? { private fun mapAction(action: AttachmentActionEntity): Action? {
return when (action.type) { return when (action.type) {
"button" -> ButtonAction(action.type, action.text, action.url, action.isWebView, "button" -> ButtonAction(action.type, action.text, action.url, action.isWebView,
...@@ -214,13 +196,4 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) { ...@@ -214,13 +196,4 @@ class DatabaseMessageMapper(private val dbManager: DatabaseManager) {
else -> null else -> null
} }
} }
/*private suspend fun mapAuthorAttachment(attachment: AttachmentEntity): AuthorAttachment {
val fields = withContext(CommonPool) {
dbManager.messageDao().getAttachmentFields(attachment._id)
}.map { Field(it.title, it.value) }
return with(attachment) {
AuthorAttachment(authorLink!!, authorIcon, authorName, fields)
}
}*/
} }
\ No newline at end of file
...@@ -4,6 +4,7 @@ import chat.rocket.android.db.DatabaseManager ...@@ -4,6 +4,7 @@ import chat.rocket.android.db.DatabaseManager
import chat.rocket.android.db.Operation import chat.rocket.android.db.Operation
import chat.rocket.android.db.model.MessagesSync import chat.rocket.android.db.model.MessagesSync
import chat.rocket.android.server.domain.MessagesRepository import chat.rocket.android.server.domain.MessagesRepository
import chat.rocket.android.util.retryDB
import chat.rocket.core.model.Message import chat.rocket.core.model.Message
import kotlinx.coroutines.experimental.CommonPool import kotlinx.coroutines.experimental.CommonPool
import kotlinx.coroutines.experimental.withContext import kotlinx.coroutines.experimental.withContext
...@@ -14,26 +15,32 @@ class DatabaseMessagesRepository( ...@@ -14,26 +15,32 @@ class DatabaseMessagesRepository(
) : MessagesRepository { ) : MessagesRepository {
override suspend fun getById(id: String): Message? = withContext(CommonPool) { override suspend fun getById(id: String): Message? = withContext(CommonPool) {
retryDB("getMessageById($id)") {
dbManager.messageDao().getMessageById(id)?.let { message -> mapper.map(message) } dbManager.messageDao().getMessageById(id)?.let { message -> mapper.map(message) }
} }
}
override suspend fun getByRoomId(roomId: String): List<Message> = withContext(CommonPool) { 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 // 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) // duplicate rows (something related to our JOINS and relations on Room)
retryDB("getMessagesByRoomId($roomId)") {
dbManager.messageDao().getMessagesByRoomId(roomId) dbManager.messageDao().getMessagesByRoomId(roomId)
.distinctBy { it.message.message.id } .distinctBy { it.message.message.id }
.let { messages -> .let { messages ->
mapper.map(messages) mapper.map(messages)
} }
} }
}
override suspend fun getRecentMessages(roomId: String, count: Long): List<Message> = withContext(CommonPool) { override suspend fun getRecentMessages(roomId: String, count: Long): List<Message> = withContext(CommonPool) {
retryDB("getRecentMessagesByRoomId($roomId, $count)") {
dbManager.messageDao().getRecentMessagesByRoomId(roomId, count) dbManager.messageDao().getRecentMessagesByRoomId(roomId, count)
.distinctBy { it.message.message.id } .distinctBy { it.message.message.id }
.let { messages -> .let { messages ->
mapper.map(messages) mapper.map(messages)
} }
} }
}
override suspend fun save(message: Message) { override suspend fun save(message: Message) {
dbManager.processMessagesBatch(listOf(message)).join() dbManager.processMessagesBatch(listOf(message)).join()
...@@ -45,27 +52,33 @@ class DatabaseMessagesRepository( ...@@ -45,27 +52,33 @@ class DatabaseMessagesRepository(
override suspend fun removeById(id: String) { override suspend fun removeById(id: String) {
withContext(CommonPool) { withContext(CommonPool) {
dbManager.messageDao().delete(id) retryDB("delete($id)") { dbManager.messageDao().delete(id) }
} }
} }
override suspend fun removeByRoomId(roomId: String) { override suspend fun removeByRoomId(roomId: String) {
withContext(CommonPool) { withContext(CommonPool) {
retryDB("deleteByRoomId($roomId)") {
dbManager.messageDao().deleteByRoomId(roomId) dbManager.messageDao().deleteByRoomId(roomId)
} }
} }
}
override suspend fun getAllUnsent(): List<Message> = withContext(CommonPool) { override suspend fun getAllUnsent(): List<Message> = withContext(CommonPool) {
retryDB("getUnsentMessages") {
dbManager.messageDao().getUnsentMessages() dbManager.messageDao().getUnsentMessages()
.distinctBy { it.message.message.id } .distinctBy { it.message.message.id }
.let { mapper.map(it) } .let { mapper.map(it) }
} }
}
override suspend fun saveLastSyncDate(roomId: String, timeMillis: Long) { override suspend fun saveLastSyncDate(roomId: String, timeMillis: Long) {
dbManager.sendOperation(Operation.SaveLastSync(MessagesSync(roomId, timeMillis))) dbManager.sendOperation(Operation.SaveLastSync(MessagesSync(roomId, timeMillis)))
} }
override suspend fun getLastSyncDate(roomId: String): Long? = withContext(CommonPool) { override suspend fun getLastSyncDate(roomId: String): Long? = withContext(CommonPool) {
retryDB("getLastSync($roomId)") {
dbManager.messageDao().getLastSync(roomId)?.let { it.timestamp } dbManager.messageDao().getLastSync(roomId)?.let { it.timestamp }
} }
}
} }
\ No newline at end of file
package chat.rocket.android.util package chat.rocket.android.util
import android.database.sqlite.SQLiteDatabaseLockedException
import chat.rocket.common.RocketChatNetworkErrorException import chat.rocket.common.RocketChatNetworkErrorException
import kotlinx.coroutines.experimental.TimeoutCancellationException import kotlinx.coroutines.experimental.TimeoutCancellationException
import kotlinx.coroutines.experimental.delay import kotlinx.coroutines.experimental.delay
...@@ -8,6 +9,7 @@ import timber.log.Timber ...@@ -8,6 +9,7 @@ import timber.log.Timber
import kotlin.coroutines.experimental.coroutineContext import kotlin.coroutines.experimental.coroutineContext
const val DEFAULT_RETRY = 3 const val DEFAULT_RETRY = 3
private const val DEFAULT_DB_RETRY = 5
suspend fun <T> retryIO( suspend fun <T> retryIO(
description: String = "<missing description>", description: String = "<missing description>",
...@@ -35,3 +37,30 @@ suspend fun <T> retryIO( ...@@ -35,3 +37,30 @@ suspend fun <T> retryIO(
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled") if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
return block() // last attempt return block() // last attempt
} }
suspend fun <T> retryDB(
description: String = "<missing description>",
times: Int = DEFAULT_DB_RETRY,
initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 500, // 0.5 second
factor: Double = 1.2,
block: suspend () -> T): T
{
var currentDelay = initialDelay
repeat(times - 1) { currentTry ->
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
try {
return block()
} catch (e: SQLiteDatabaseLockedException) {
Timber.d(e, "failed call($currentTry): $description")
e.printStackTrace()
}
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
}
if (!coroutineContext.isActive) throw TimeoutCancellationException("job canceled")
return block() // last attempt
}
\ No newline at end of file
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