Migrate to realm + better insertion

This commit is contained in:
ganfra 2018-10-15 19:42:13 +02:00
parent 95fd7190e4
commit b79d23ad24
37 changed files with 309 additions and 497 deletions

View file

@ -8,9 +8,12 @@ import android.widget.Toast
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.events.sync.data.SyncResponse
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.RiotActivity
import io.realm.RealmChangeListener
import io.realm.RealmResults
import kotlinx.android.synthetic.main.activity_home.*
import org.koin.android.ext.android.inject
@ -18,6 +21,7 @@ class HomeActivity : RiotActivity() {
private val matrix by inject<Matrix>()
private val synchronizer = matrix.currentSession?.synchronizer()
private val realmHolder = matrix.currentSession?.realmInstanceHolder()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@ -32,8 +36,6 @@ class HomeActivity : RiotActivity() {
override fun onSuccess(data: SyncResponse?) {
synchronizeButton.visibility = View.VISIBLE
loadingView.visibility = View.GONE
Toast.makeText(this@HomeActivity, "Success", Toast.LENGTH_LONG).show()
}
override fun onFailure(failure: Failure) {
@ -42,10 +44,17 @@ class HomeActivity : RiotActivity() {
Toast.makeText(this@HomeActivity, failure.toString(), Toast.LENGTH_LONG).show()
}
})
if (realmHolder != null) {
val results = realmHolder.realm.where(EventEntity::class.java).equalTo("chunk.room.roomId", "!UlckfcnwgLKswCmUbe:matrix.org").findAll()
results.addChangeListener(RealmChangeListener<RealmResults<EventEntity>> {
Toast.makeText(this@HomeActivity, "Room events data changed", Toast.LENGTH_LONG).show()
})
}
}
companion object {
fun newIntent(context: Context): Intent {
return Intent(context, HomeActivity::class.java)
}

View file

@ -1,7 +1,7 @@
apply plugin: 'com.android.library'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-kapt'
apply plugin: 'io.objectbox'
apply plugin: 'realm-android'
buildscript {
@ -9,7 +9,7 @@ buildscript {
jcenter()
}
dependencies {
classpath "io.objectbox:objectbox-gradle-plugin:2.2.0"
classpath "io.realm:realm-gradle-plugin:5.7.0"
}
}

View file

@ -1,194 +0,0 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "6:5991986256653472999",
"lastPropertyId": "6:5675340775405504775",
"name": "Credentials",
"properties": [
{
"id": "1:904844404428661340",
"name": "id"
},
{
"id": "2:4962733318674873893",
"name": "userId"
},
{
"id": "3:6496111157191804406",
"name": "homeServer"
},
{
"id": "4:6583709765305423919",
"name": "accessToken"
},
{
"id": "5:865976960498370870",
"name": "refreshToken"
},
{
"id": "6:5675340775405504775",
"name": "deviceId"
}
],
"relations": []
},
{
"id": "7:6920598293865885979",
"lastPropertyId": "3:5795529890406591571",
"name": "ObjectBoxSessionParams",
"properties": [
{
"id": "1:7073183456619398402",
"name": "credentialsJson"
},
{
"id": "2:8905537494398356078",
"name": "homeServerConnectionConfigJson"
},
{
"id": "3:5795529890406591571",
"name": "id"
}
],
"relations": []
},
{
"id": "8:1761107158737061066",
"lastPropertyId": "3:787896491046945850",
"name": "RoomEntity",
"properties": [
{
"id": "1:7021996505563941556",
"name": "id"
},
{
"id": "2:1269026517652311997",
"name": "membership"
},
{
"id": "3:787896491046945850",
"name": "roomId"
}
],
"relations": [
{
"id": "1:5345919940124619772",
"name": "chunks"
}
]
},
{
"id": "9:6019404356061358330",
"lastPropertyId": "3:3869954049926012588",
"name": "ChunkEntity",
"properties": [
{
"id": "1:3461196015528391821",
"name": "id"
},
{
"id": "2:4852790652764050720",
"name": "prevToken"
},
{
"id": "3:3869954049926012588",
"name": "nextToken"
}
],
"relations": [
{
"id": "2:5944867505344991328",
"name": "events"
}
]
},
{
"id": "10:7579750767964019860",
"lastPropertyId": "6:6069780711726222192",
"name": "EventEntity",
"properties": [
{
"id": "1:3170318841756889053",
"name": "id"
},
{
"id": "2:6651269605038929561",
"name": "type"
},
{
"id": "3:470436998712759552",
"name": "content"
},
{
"id": "4:1820638541606769650",
"name": "eventId"
},
{
"id": "5:4423270757987421556",
"name": "prevContent"
},
{
"id": "6:6069780711726222192",
"name": "stateKey"
}
],
"relations": []
}
],
"lastEntityId": "10:7579750767964019860",
"lastIndexId": "4:1616715309690181473",
"lastRelationId": "2:5944867505344991328",
"lastSequenceId": "0:0",
"modelVersion": 4,
"modelVersionParserMinimum": 4,
"retiredEntityUids": [
637433444018824383,
5577202525246066978,
213989653016909134,
826321009570992494,
1775102368193732759
],
"retiredIndexUids": [
7573100044105293219,
7664899561635023422,
762622607983996029,
1616715309690181473
],
"retiredPropertyUids": [
6211403495341530846,
1774175862476960436,
5757014528669120452,
6085081322264805865,
5476767963007280768,
2515822585258942903,
1374359365665650669,
2615322165288227076,
8258973118557394726,
4167129800901721265,
4654729568692986907,
958528673818813300,
3516672544602138732,
8457998089049891725,
878535388660167894,
7030303501035684102,
7193051897929077560,
8192129106205548340,
7632656533038841948,
1390065349267803135,
6024884732066241356,
3502558420506448922,
3999645739247298623,
8488502568193639300,
594512591943458255,
5113574037182501000,
4929796643260285955,
5690571528511905880,
1811444397594387116,
8817556524159529201
],
"retiredRelationUids": [],
"version": 1
}

View file

@ -1,112 +0,0 @@
{
"_note1": "KEEP THIS FILE! Check it into a version control system (VCS) like git.",
"_note2": "ObjectBox manages crucial IDs for your object model. See docs for details.",
"_note3": "If you have VCS merge conflicts, you must resolve them according to ObjectBox docs.",
"entities": [
{
"id": "6:5991986256653472999",
"lastPropertyId": "6:5675340775405504775",
"name": "Credentials",
"properties": [
{
"id": "1:904844404428661340",
"name": "id"
},
{
"id": "2:4962733318674873893",
"name": "userId"
},
{
"id": "3:6496111157191804406",
"name": "homeServer"
},
{
"id": "4:6583709765305423919",
"name": "accessToken"
},
{
"id": "5:865976960498370870",
"name": "refreshToken"
},
{
"id": "6:5675340775405504775",
"name": "deviceId"
}
],
"relations": []
},
{
"id": "7:6920598293865885979",
"lastPropertyId": "3:5795529890406591571",
"name": "ObjectBoxSessionParams",
"properties": [
{
"id": "1:7073183456619398402",
"name": "credentialsJson"
},
{
"id": "2:8905537494398356078",
"name": "homeServerConnectionConfigJson"
},
{
"id": "3:5795529890406591571",
"name": "id"
}
],
"relations": []
}
],
"lastEntityId": "7:6920598293865885979",
"lastIndexId": "4:1616715309690181473",
"lastRelationId": "0:0",
"lastSequenceId": "0:0",
"modelVersion": 4,
"modelVersionParserMinimum": 4,
"retiredEntityUids": [
637433444018824383,
5577202525246066978,
213989653016909134,
826321009570992494,
1775102368193732759
],
"retiredIndexUids": [
7573100044105293219,
7664899561635023422,
762622607983996029,
1616715309690181473
],
"retiredPropertyUids": [
6211403495341530846,
1774175862476960436,
5757014528669120452,
6085081322264805865,
5476767963007280768,
2515822585258942903,
1374359365665650669,
2615322165288227076,
8258973118557394726,
4167129800901721265,
4654729568692986907,
958528673818813300,
3516672544602138732,
8457998089049891725,
878535388660167894,
7030303501035684102,
7193051897929077560,
8192129106205548340,
7632656533038841948,
1390065349267803135,
6024884732066241356,
3502558420506448922,
3999645739247298623,
8488502568193639300,
594512591943458255,
5113574037182501000,
4929796643260285955,
5690571528511905880,
1811444397594387116,
8817556524159529201
],
"retiredRelationUids": [],
"version": 1
}

View file

@ -7,6 +7,7 @@ import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.auth.AuthModule
import im.vector.matrix.android.internal.di.MatrixModule
import im.vector.matrix.android.internal.di.NetworkModule
import io.realm.Realm
import org.koin.standalone.KoinComponent
import org.koin.standalone.StandAloneContext.loadKoinModules
import org.koin.standalone.inject
@ -21,9 +22,10 @@ class Matrix(matrixOptions: MatrixOptions) : KoinComponent {
var currentSession: Session? = null
init {
Realm.init(matrixOptions.context)
val matrixModule = MatrixModule(matrixOptions)
val networkModule = NetworkModule()
val authModule = AuthModule(matrixOptions.context)
val authModule = AuthModule()
loadKoinModules(listOf(matrixModule, networkModule, authModule))
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())

View file

@ -9,7 +9,7 @@ import im.vector.matrix.android.internal.legacy.util.JsonUtils
@JsonClass(generateAdapter = true)
data class Event(
@Json(name = "type") val type: String,
@Json(name = "event_id") val eventId: String? = null,
@Json(name = "event_id") val eventId: String?,
@Json(name = "content") val content: Map<String, Any>? = null,
@Json(name = "prev_content") val prevContent: Map<String, Any>? = null,
@Json(name = "origin_server_ts") val originServerTs: Long? = null,
@ -45,9 +45,9 @@ data class Event(
val isCallEvent: Boolean by lazy {
EventType.CALL_INVITE == type
|| EventType.CALL_CANDIDATES == type
|| EventType.CALL_ANSWER == type
|| EventType.CALL_HANGUP == type
|| EventType.CALL_CANDIDATES == type
|| EventType.CALL_ANSWER == type
|| EventType.CALL_HANGUP == type
}
}

View file

@ -0,0 +1,2 @@
package im.vector.matrix.android.api.events

View file

@ -1,11 +1,15 @@
package im.vector.matrix.android.api.session
import im.vector.matrix.android.internal.database.RealmInstanceHolder
import im.vector.matrix.android.internal.events.sync.Synchronizer
interface Session {
fun synchronizer(): Synchronizer
// Visible for testing request directly. Will be deleted
fun realmInstanceHolder(): RealmInstanceHolder
fun close()
}

View file

@ -1,19 +1,14 @@
package im.vector.matrix.android.internal.auth
import android.content.Context
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.internal.auth.db.ObjectBoxSessionParams
import im.vector.matrix.android.internal.auth.db.ObjectBoxSessionParamsMapper
import im.vector.matrix.android.internal.auth.db.ObjectBoxSessionParamsStore
import io.objectbox.Box
import io.objectbox.BoxStore
import im.vector.matrix.android.internal.auth.db.RealmSessionParamsStore
import im.vector.matrix.android.internal.auth.db.SessionParamsMapper
import io.realm.RealmConfiguration
import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module
import org.koin.dsl.module.module
private const val AUTH_BOX_STORE = "AUTH_BOX_STORE"
class AuthModule(private val context: Context) : Module {
class AuthModule : Module {
override fun invoke(): ModuleDefinition = module {
@ -21,18 +16,10 @@ class AuthModule(private val context: Context) : Module {
DefaultAuthenticator(get(), get(), get()) as Authenticator
}
single(name = AUTH_BOX_STORE) {
MyObjectBox.builder().androidContext(context).build()
}
single {
val boxStore = get(name = AUTH_BOX_STORE) as BoxStore
boxStore.boxFor(ObjectBoxSessionParams::class.java) as Box<ObjectBoxSessionParams>
}
single {
ObjectBoxSessionParamsStore(ObjectBoxSessionParamsMapper((get())), get()) as SessionParamsStore
val mapper = SessionParamsMapper((get()))
val realmConfiguration = RealmConfiguration.Builder().name("matrix-sdk-auth").deleteRealmIfMigrationNeeded().build()
RealmSessionParamsStore(mapper, realmConfiguration) as SessionParamsStore
}
}.invoke()

View file

@ -2,13 +2,9 @@ package im.vector.matrix.android.internal.auth.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
@JsonClass(generateAdapter = true)
@Entity
data class Credentials(
@Id var id: Long = 0,
@Json(name = "user_id") val userId: String,
@Json(name = "home_server") val homeServer: String,
@Json(name = "access_token") val accessToken: String,

View file

@ -1,11 +0,0 @@
package im.vector.matrix.android.internal.auth.db
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
@Entity
data class ObjectBoxSessionParams(
val credentialsJson: String,
val homeServerConnectionConfigJson: String,
@Id var id: Long = 0
)

View file

@ -1,21 +0,0 @@
package im.vector.matrix.android.internal.auth.db
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.auth.data.SessionParams
import io.objectbox.Box
class ObjectBoxSessionParamsStore(private val mapper: ObjectBoxSessionParamsMapper,
private val box: Box<ObjectBoxSessionParams>) : SessionParamsStore {
override fun save(sessionParams: SessionParams) {
val objectBoxSessionParams = mapper.map(sessionParams)
objectBoxSessionParams?.let {
box.put(it)
}
}
override fun get(): SessionParams? {
return box.all.map { mapper.map(it) }.lastOrNull()
}
}

View file

@ -0,0 +1,33 @@
package im.vector.matrix.android.internal.auth.db
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.auth.data.SessionParams
import io.realm.Realm
import io.realm.RealmConfiguration
class RealmSessionParamsStore(private val mapper: SessionParamsMapper,
private val realmConfiguration: RealmConfiguration) : SessionParamsStore {
override fun save(sessionParams: SessionParams) {
val entity = mapper.map(sessionParams)
if (entity != null) {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.insert(entity)
}
realm.close()
}
}
override fun get(): SessionParams? {
val realm = Realm.getInstance(realmConfiguration)
val sessionParams = realm
.where(SessionParamsEntity::class.java)
.findAll()
.map { mapper.map(it) }
.lastOrNull()
realm.close()
return sessionParams
}
}

View file

@ -0,0 +1,8 @@
package im.vector.matrix.android.internal.auth.db
import io.realm.RealmObject
open class SessionParamsEntity(
var credentialsJson: String = "",
var homeServerConnectionConfigJson: String = ""
) : RealmObject()

View file

@ -5,24 +5,24 @@ import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.SessionParams
class ObjectBoxSessionParamsMapper(moshi: Moshi) {
class SessionParamsMapper(moshi: Moshi) {
private val credentialsAdapter = moshi.adapter(Credentials::class.java)
private val homeServerConnectionConfigAdapter = moshi.adapter(HomeServerConnectionConfig::class.java)
fun map(objectBoxSessionParams: ObjectBoxSessionParams?): SessionParams? {
if (objectBoxSessionParams == null) {
fun map(entity: SessionParamsEntity?): SessionParams? {
if (entity == null) {
return null
}
val credentials = credentialsAdapter.fromJson(objectBoxSessionParams.credentialsJson)
val homeServerConnectionConfig = homeServerConnectionConfigAdapter.fromJson(objectBoxSessionParams.homeServerConnectionConfigJson)
val credentials = credentialsAdapter.fromJson(entity.credentialsJson)
val homeServerConnectionConfig = homeServerConnectionConfigAdapter.fromJson(entity.homeServerConnectionConfigJson)
if (credentials == null || homeServerConnectionConfig == null) {
return null
}
return SessionParams(credentials, homeServerConnectionConfig)
}
fun map(sessionParams: SessionParams?): ObjectBoxSessionParams? {
fun map(sessionParams: SessionParams?): SessionParamsEntity? {
if (sessionParams == null) {
return null
}
@ -31,7 +31,7 @@ class ObjectBoxSessionParamsMapper(moshi: Moshi) {
if (credentialsJson == null || homeServerConnectionConfigJson == null) {
return null
}
return ObjectBoxSessionParams(credentialsJson, homeServerConnectionConfigJson)
return SessionParamsEntity(credentialsJson, homeServerConnectionConfigJson)
}

View file

@ -0,0 +1,11 @@
package im.vector.matrix.android.internal.database
import io.realm.Realm
import io.realm.RealmConfiguration
class RealmInstanceHolder(realmConfiguration: RealmConfiguration) {
val realm = Realm.getInstance(realmConfiguration)
}

View file

@ -14,7 +14,7 @@ object EventMapper {
fun map(event: Event): EventEntity {
val eventEntity = EventEntity()
eventEntity.eventId = event.eventId
eventEntity.eventId = event.eventId!!
eventEntity.content = adapter.toJson(event.content)
eventEntity.prevContent = adapter.toJson(event.prevContent)
eventEntity.stateKey = event.stateKey

View file

@ -1,13 +1,18 @@
package im.vector.matrix.android.internal.database.model
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import io.objectbox.relation.ToMany
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
@Entity
class ChunkEntity {
@Id var id: Long = 0
var prevToken: String? = null
var nextToken: String? = null
lateinit var events: ToMany<EventEntity>
open class ChunkEntity(var prevToken: String? = null,
var nextToken: String? = null,
var isLimited: Boolean = true,
var events: RealmList<EventEntity> = RealmList()
) : RealmObject() {
@LinkingObjects("chunks")
val room: RealmResults<RoomEntity>? = null
companion object
}

View file

@ -1,14 +1,18 @@
package im.vector.matrix.android.internal.database.model
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import io.realm.RealmObject
import io.realm.RealmResults
import io.realm.annotations.LinkingObjects
import io.realm.annotations.PrimaryKey
open class EventEntity(@PrimaryKey var eventId: String = "",
var type: String = "",
var content: String = "",
var prevContent: String? = null,
var stateKey: String? = null
) : RealmObject() {
@LinkingObjects("events")
val chunk: RealmResults<ChunkEntity>? = null
@Entity
class EventEntity {
@Id var id: Long = 0
lateinit var type: String
lateinit var content: String
var eventId: String? = null
var prevContent: String? = null
var stateKey: String? = null
}

View file

@ -1,18 +1,20 @@
package im.vector.matrix.android.internal.database.model
import io.objectbox.annotation.Convert
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
import io.objectbox.converter.PropertyConverter
import io.objectbox.relation.ToMany
import io.realm.RealmList
import io.realm.RealmObject
import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey
import kotlin.properties.Delegates
@Entity
class RoomEntity {
@Id var id: Long = 0
@Convert(converter = MembershipConverter::class, dbType = String::class)
var membership: Membership = Membership.NONE
lateinit var roomId: String
lateinit var chunks: ToMany<ChunkEntity>
open class RoomEntity : RealmObject() {
@PrimaryKey var roomId: String = ""
var chunks: RealmList<ChunkEntity> = RealmList()
private var membershipStr: String = Membership.NONE.name
@delegate:Ignore var membership: Membership by Delegates.observable(Membership.valueOf(membershipStr)) { _, _, newValue ->
membershipStr = newValue.name
}
companion object;
@ -24,14 +26,3 @@ class RoomEntity {
}
}
class MembershipConverter : PropertyConverter<RoomEntity.Membership, String> {
override fun convertToDatabaseValue(entityProperty: RoomEntity.Membership?): String? {
return entityProperty?.name
}
override fun convertToEntityProperty(databaseValue: String?): RoomEntity.Membership? {
return databaseValue?.let { RoomEntity.Membership.valueOf(databaseValue) }
}
}

View file

@ -0,0 +1,8 @@
package im.vector.matrix.android.internal.database.model
import io.realm.RealmObject
import io.realm.annotations.PrimaryKey
open class SyncEntity(var nextBatch: String? = null,
@PrimaryKey var id: Long = 0
) : RealmObject()

View file

@ -1,21 +1,18 @@
package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomEntity_
import io.objectbox.Box
import io.realm.Realm
fun RoomEntity.Companion.getForId(roomBox: Box<RoomEntity>, roomId: String): RoomEntity? {
return roomBox
.query()
.equal(RoomEntity_.roomId, roomId)
.build()
.findUnique()
fun RoomEntity.Companion.getForId(realm: Realm, roomId: String): RoomEntity? {
return realm.where<RoomEntity>(RoomEntity::class.java)
.equalTo("roomId", roomId)
.findFirst()
}
fun RoomEntity.Companion.getAll(roomBox: Box<RoomEntity>, membership: RoomEntity.Membership? = null): List<RoomEntity> {
val query = roomBox.query()
fun RoomEntity.Companion.getAll(realm: Realm, membership: RoomEntity.Membership? = null): List<RoomEntity> {
val query = realm.where(RoomEntity::class.java)
if (membership != null) {
query.filter { it.membership == membership }
query.equalTo("membership", membership.name)
}
return query.build().find()
return query.findAll()
}

View file

@ -3,6 +3,7 @@ package im.vector.matrix.android.internal.di
import im.vector.matrix.android.api.MatrixOptions
import im.vector.matrix.android.api.thread.MainThreadExecutor
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import io.realm.Realm
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.asCoroutineDispatcher

View file

@ -1,10 +1,12 @@
package im.vector.matrix.android.internal.di
import im.vector.matrix.android.internal.auth.data.SessionParams
import im.vector.matrix.android.internal.database.RealmInstanceHolder
import im.vector.matrix.android.internal.legacy.MXDataHandler
import im.vector.matrix.android.internal.legacy.MXSession
import im.vector.matrix.android.internal.legacy.data.store.MXFileStore
import im.vector.matrix.android.internal.session.DefaultSession
import io.realm.RealmConfiguration
import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module
import org.koin.dsl.module.module
@ -14,6 +16,14 @@ class SessionModule(private val sessionParams: SessionParams) : Module {
override fun invoke(): ModuleDefinition = module(override = true) {
scope(DefaultSession.SCOPE) {
RealmConfiguration.Builder().name(sessionParams.credentials.userId).deleteRealmIfMigrationNeeded().build()
}
scope(DefaultSession.SCOPE) {
RealmInstanceHolder(get())
}
scope(DefaultSession.SCOPE) {
val retrofitBuilder = get() as Retrofit.Builder
retrofitBuilder

View file

@ -3,64 +3,114 @@ package im.vector.matrix.android.internal.events.sync
import im.vector.matrix.android.api.events.Event
import im.vector.matrix.android.internal.database.mapper.EventMapper
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.getForId
import im.vector.matrix.android.internal.events.sync.data.InvitedRoomSync
import im.vector.matrix.android.internal.events.sync.data.RoomSync
import io.objectbox.Box
import io.objectbox.BoxStore
import io.realm.Realm
import io.realm.RealmConfiguration
class RoomSyncHandler(
boxStore: BoxStore
) {
class RoomSyncHandler(private val realmConfiguration: RealmConfiguration) {
private val eventBox: Box<EventEntity> = boxStore.boxFor(EventEntity::class.java)
private val chunkBox: Box<ChunkEntity> = boxStore.boxFor(ChunkEntity::class.java)
private val roomBox: Box<RoomEntity> = boxStore.boxFor(RoomEntity::class.java)
fun handleJoinedRooms(roomSyncByRoom: Map<String, RoomSync>?) {
if (roomSyncByRoom == null) {
return
}
val roomEntities = ArrayList<RoomEntity>()
roomSyncByRoom.forEach {
val roomEntity = handleJoinedRoom(it.key, it.value)
roomEntities.add(roomEntity)
}
roomBox.put(roomEntities)
sealed class HandlingStrategy {
data class JOINED(val data: Map<String, RoomSync>) : HandlingStrategy()
data class INVITED(val data: Map<String, InvitedRoomSync>) : HandlingStrategy()
data class LEFT(val data: Map<String, RoomSync>) : HandlingStrategy()
}
private fun handleJoinedRoom(roomId: String, roomSync: RoomSync): RoomEntity {
val roomEntity = RoomEntity.getForId(roomBox, roomId) ?: RoomEntity().apply { this.roomId = roomId }
if (roomEntity.membership == RoomEntity.Membership.INVITED) {
roomEntity.chunks
.map { it.events }
.forEach { eventBox.remove(it) }
chunkBox.remove(roomEntity.chunks)
fun handleRoomSync(handlingStrategy: HandlingStrategy) {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction { realmInstance ->
val roomEntities = when (handlingStrategy) {
is HandlingStrategy.JOINED -> handlingStrategy.data.map { handleJoinedRoom(realm, it.key, it.value) }
is HandlingStrategy.INVITED -> handlingStrategy.data.map { handleInvitedRoom(realm, it.key, it.value) }
is HandlingStrategy.LEFT -> handlingStrategy.data.map { handleLeftRoom(it.key, it.value) }
}
realmInstance.insertOrUpdate(roomEntities)
}
roomEntity.membership = RoomEntity.Membership.JOINED
if (roomSync.timeline != null) {
val chunkEntity = eventListToChunk(roomSync.timeline.events, roomSync.timeline.prevBatch)
roomEntity.chunks.add(chunkEntity)
realm.close()
}
// PRIVATE METHODS *****************************************************************************
private fun handleJoinedRoom(realm: Realm,
roomId: String,
roomSync: RoomSync): RoomEntity {
val roomEntity = RoomEntity.getForId(realm, roomId)
?: RoomEntity().apply { this.roomId = roomId }
if (roomEntity.membership == RoomEntity.Membership.INVITED) {
roomEntity.chunks.deleteAllFromRealm()
}
if (roomSync.state != null) {
val chunkEntity = eventListToChunk(roomSync.state.events)
roomEntity.chunks.add(chunkEntity)
roomEntity.membership = RoomEntity.Membership.JOINED
if (roomSync.state != null && roomSync.state.events.isNotEmpty()) {
val chunkEntity = eventListToChunk(realm, roomId, roomSync.state.events)
if (!roomEntity.chunks.contains(chunkEntity)) {
roomEntity.chunks.add(chunkEntity)
}
}
if (roomSync.timeline != null && roomSync.timeline.events.isNotEmpty()) {
val chunkEntity = eventListToChunk(realm, roomId, roomSync.timeline.events, roomSync.timeline.prevToken, isLimited = roomSync.timeline.limited)
if (!roomEntity.chunks.contains(chunkEntity)) {
roomEntity.chunks.add(chunkEntity)
}
}
return roomEntity
}
private fun eventListToChunk(eventList: List<Event>,
private fun handleInvitedRoom(realm: Realm,
roomId: String,
roomSync:
InvitedRoomSync): RoomEntity {
val roomEntity = RoomEntity()
roomEntity.roomId = roomId
roomEntity.membership = RoomEntity.Membership.INVITED
if (roomSync.inviteState != null && roomSync.inviteState.events.isNotEmpty()) {
val chunkEntity = eventListToChunk(realm, roomId, roomSync.inviteState.events)
if (!roomEntity.chunks.contains(chunkEntity)) {
roomEntity.chunks.add(chunkEntity)
}
}
return roomEntity
}
// TODO : handle it
private fun handleLeftRoom(roomId: String,
roomSync: RoomSync): RoomEntity {
return RoomEntity().apply {
this.roomId = roomId
this.membership = RoomEntity.Membership.LEFT
}
}
private fun eventListToChunk(realm: Realm,
roomId: String,
eventList: List<Event>,
prevToken: String? = null,
nextToken: String? = null): ChunkEntity {
val chunkEntity = ChunkEntity()
nextToken: String? = null,
isLimited: Boolean = true): ChunkEntity {
val chunkEntity = if (!isLimited) {
realm.where(ChunkEntity::class.java).equalTo("room.roomId", roomId).isNull("nextToken").and().isNotNull("prevToken").findAll().lastOrNull()
} else {
realm.where(ChunkEntity::class.java).`in`("events.eventId", eventList.map { it.eventId }.toTypedArray()).findFirst()
} ?: ChunkEntity()
chunkEntity.prevToken = prevToken
chunkEntity.nextToken = nextToken
eventList
.map { event -> EventMapper.map(event) }
.forEach { chunkEntity.events.add(it) }
chunkEntity.isLimited = isLimited
eventList.forEach { event ->
val eventEntity = EventMapper.map(event).let {
realm.copyToRealmOrUpdate(it)
}
if (!chunkEntity.events.contains(eventEntity)) {
chunkEntity.events.add(eventEntity)
}
}
return chunkEntity
}

View file

@ -30,7 +30,9 @@ class SyncResponseHandler(
if (syncResponse.rooms != null) {
// joined rooms events
roomSyncHandler.handleJoinedRooms(syncResponse.rooms.join)
roomSyncHandler.handleRoomSync(RoomSyncHandler.HandlingStrategy.JOINED(syncResponse.rooms.join))
roomSyncHandler.handleRoomSync(RoomSyncHandler.HandlingStrategy.INVITED(syncResponse.rooms.invite))
roomSyncHandler.handleRoomSync(RoomSyncHandler.HandlingStrategy.LEFT(syncResponse.rooms.leave))
}
/*

View file

@ -1,6 +1,9 @@
package im.vector.matrix.android.internal.events.sync
import arrow.core.Either
import arrow.core.flatMap
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.events.sync.data.SyncResponse
import im.vector.matrix.android.internal.legacy.rest.model.filter.FilterBody
@ -16,6 +19,8 @@ class Synchronizer(private val syncAPI: SyncAPI,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val syncResponseHandler: SyncResponseHandler) {
private var token: String? = null
fun synchronize(callback: MatrixCallback<SyncResponse>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val syncOrFailure = synchronize()
@ -28,13 +33,23 @@ class Synchronizer(private val syncAPI: SyncAPI,
val params = HashMap<String, String>()
val filterBody = FilterBody()
FilterUtil.enableLazyLoading(filterBody, true)
params["timeout"] = "0"
var timeout = 0
if (token != null) {
params["since"] = token as String
timeout = 30
}
params["timeout"] = timeout.toString()
params["filter"] = filterBody.toJSONString()
executeRequest<SyncResponse> {
apiCall = syncAPI.sync(params)
}.map {
syncResponseHandler.handleResponse(it, null, false)
it
}.flatMap {
token = it?.nextBatch
try {
syncResponseHandler.handleResponse(it, null, false)
Either.right(it)
} catch (exception: Exception) {
Either.Left(Failure.Unknown(exception))
}
}
}

View file

@ -15,6 +15,7 @@
*/
package im.vector.matrix.android.internal.events.sync.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
// InvitedRoomSync represents a room invitation during server sync v2.
@ -28,5 +29,5 @@ data class InvitedRoomSync(
* and one from the archived 'state'. If the client joins the room then the current state will be given as a delta against the
* archived 'state' not the 'invite_state'.
*/
val inviteState: RoomInviteState? = null
@Json(name = "invite_state") val inviteState: RoomInviteState? = null
)

View file

@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.events.sync.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.events.Event
@ -26,5 +27,5 @@ data class RoomInviteState(
/**
* List of state events (array of MXEvent).
*/
val events: List<Event> = emptyList()
@Json(name = "events") val events: List<Event> = emptyList()
)

View file

@ -15,6 +15,7 @@
*/
package im.vector.matrix.android.internal.events.sync.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
// RoomSync represents the response for a room during server sync v2.
@ -23,26 +24,26 @@ data class RoomSync(
/**
* The state updates for the room.
*/
val state: RoomSyncState? = null,
@Json(name = "state") val state: RoomSyncState? = null,
/**
* The timeline of messages and state changes in the room.
*/
val timeline: RoomSyncTimeline? = null,
@Json(name = "timeline") val timeline: RoomSyncTimeline? = null,
/**
* The ephemeral events in the room that aren't recorded in the timeline or state of the room (e.g. typing, receipts).
*/
val ephemeral: RoomSyncEphemeral? = null,
@Json(name = "ephemeral") val ephemeral: RoomSyncEphemeral? = null,
/**
* The account data events for the room (e.g. tags).
*/
val accountData: RoomSyncAccountData? = null,
@Json(name = "account_data") val accountData: RoomSyncAccountData? = null,
/**
* The notification counts for the room.
*/
val unreadNotifications: RoomSyncUnreadNotifications? = null
@Json(name = "unread_notifications") val unreadNotifications: RoomSyncUnreadNotifications? = null
)

View file

@ -1,5 +1,6 @@
package im.vector.matrix.android.internal.events.sync.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.events.Event
@ -8,5 +9,5 @@ data class RoomSyncAccountData(
/**
* List of account data events (array of Event).
*/
val events: List<Event>? = null
@Json(name = "events") val events: List<Event>? = null
)

View file

@ -1,6 +1,7 @@
package im.vector.matrix.android.internal.events.sync.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.events.Event
@ -10,5 +11,5 @@ data class RoomSyncEphemeral(
/**
* List of ephemeral events (array of Event).
*/
val events: List<Event>? = null
@Json(name = "events") val events: List<Event>? = null
)

View file

@ -1,6 +1,7 @@
package im.vector.matrix.android.internal.events.sync.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.events.Event
@ -11,15 +12,15 @@ data class RoomSyncTimeline(
/**
* List of events (array of Event).
*/
val events: List<Event> = emptyList(),
@Json(name = "events") val events: List<Event> = emptyList(),
/**
* Boolean which tells whether there are more events on the server
*/
val limited: Boolean = false,
@Json(name = "limited") val limited: Boolean = false,
/**
* If the batch was limited then this is a token that can be supplied to the server to retrieve more events
*/
val prevBatch: String? = null
@Json(name = "prev_batch") val prevToken: String? = null
)

View file

@ -1,6 +1,7 @@
package im.vector.matrix.android.internal.events.sync.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.events.Event
@ -12,14 +13,14 @@ data class RoomSyncUnreadNotifications(
/**
* List of account data events (array of Event).
*/
val events: List<Event>? = null,
@Json(name = "events") val events: List<Event>? = null,
/**
* The number of unread messages that match the push notification rules.
*/
val notificationCount: Int? = null,
@Json(name = "notification_count") val notificationCount: Int? = null,
/**
* The number of highlighted unread messages (subset of notifications).
*/
val highlightCount: Int? = null)
@Json(name = "highlight_count") val highlightCount: Int? = null)

View file

@ -15,6 +15,7 @@
*/
package im.vector.matrix.android.internal.events.sync.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
// RoomsSyncResponse represents the rooms list in server sync v2 response.
@ -23,15 +24,15 @@ data class RoomsSyncResponse(
/**
* Joined rooms: keys are rooms ids.
*/
val join: Map<String, RoomSync> = emptyMap(),
@Json(name = "join") val join: Map<String, RoomSync> = emptyMap(),
/**
* Invitations. The rooms that the user has been invited to: keys are rooms ids.
*/
val invite: Map<String, InvitedRoomSync> = emptyMap(),
@Json(name = "invite") val invite: Map<String, InvitedRoomSync> = emptyMap(),
/**
* Left rooms. The rooms that the user has left or been banned from: keys are rooms ids.
*/
val leave: Map<String, RoomSync> = emptyMap()
@Json(name = "leave") val leave: Map<String, RoomSync> = emptyMap()
)

View file

@ -2,6 +2,7 @@ package im.vector.matrix.android.internal.session
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.auth.data.SessionParams
import im.vector.matrix.android.internal.database.RealmInstanceHolder
import im.vector.matrix.android.internal.di.SessionModule
import im.vector.matrix.android.internal.events.sync.SyncModule
import im.vector.matrix.android.internal.events.sync.Synchronizer
@ -13,6 +14,7 @@ import org.koin.standalone.inject
class DefaultSession(sessionParams: SessionParams) : Session, KoinComponent {
private val realmInstanceHolder by inject<RealmInstanceHolder>()
private val synchronizer by inject<Synchronizer>()
private val scope: Scope
@ -27,7 +29,12 @@ class DefaultSession(sessionParams: SessionParams) : Session, KoinComponent {
return synchronizer
}
override fun realmInstanceHolder(): RealmInstanceHolder {
return realmInstanceHolder
}
override fun close() {
realmInstanceHolder.realm.close()
scope.close()
}