Start to insert room summaries and listen for changes

This commit is contained in:
ganfra 2018-10-16 15:52:30 +02:00
parent b79d23ad24
commit 4904ac894e
29 changed files with 319 additions and 84 deletions

View File

@ -28,6 +28,8 @@ dependencies {
implementation 'com.android.support:appcompat-v7:28.0.0' implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3' implementation 'com.android.support.constraint:constraint-layout:1.1.3'


implementation 'com.jakewharton.timber:timber:4.7.1'

implementation "org.koin:koin-android:$koin_version" implementation "org.koin:koin-android:$koin_version"
implementation "org.koin:koin-android-scope:$koin_version" implementation "org.koin:koin-android-scope:$koin_version"
implementation "org.koin:koin-android-viewmodel:$koin_version" implementation "org.koin:koin-android-viewmodel:$koin_version"

View File

@ -1,13 +1,18 @@
package im.vector.riotredesign package im.vector.riotredesign


import android.app.Application import android.app.Application
import im.vector.matrix.android.BuildConfig
import im.vector.riotredesign.core.di.AppModule import im.vector.riotredesign.core.di.AppModule
import org.koin.standalone.StandAloneContext.startKoin import org.koin.standalone.StandAloneContext.startKoin
import timber.log.Timber


class Riot : Application() { class Riot : Application() {


override fun onCreate() { override fun onCreate() {
super.onCreate() super.onCreate()
if (BuildConfig.DEBUG) {
Timber.plant(Timber.DebugTree())
}
startKoin(listOf(AppModule(this))) startKoin(listOf(AppModule(this)))
} }



View File

@ -4,24 +4,22 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.view.View import android.view.View
import android.widget.Toast
import im.vector.matrix.android.api.Matrix import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.events.sync.data.SyncResponse import im.vector.matrix.android.internal.events.sync.data.SyncResponse
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.RiotActivity import im.vector.riotredesign.core.platform.RiotActivity
import io.realm.RealmChangeListener
import io.realm.RealmResults
import kotlinx.android.synthetic.main.activity_home.* import kotlinx.android.synthetic.main.activity_home.*
import org.koin.android.ext.android.inject import org.koin.android.ext.android.inject
import timber.log.Timber


class HomeActivity : RiotActivity() { class HomeActivity : RiotActivity() {


private val matrix by inject<Matrix>() private val matrix by inject<Matrix>()
private val synchronizer = matrix.currentSession?.synchronizer() private val synchronizer = matrix.currentSession?.synchronizer()
private val realmHolder = matrix.currentSession?.realmInstanceHolder() private val realmHolder = matrix.currentSession?.realmHolder()


override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -33,25 +31,25 @@ class HomeActivity : RiotActivity() {
synchronizeButton.visibility = View.GONE synchronizeButton.visibility = View.GONE
loadingView.visibility = View.VISIBLE loadingView.visibility = View.VISIBLE
synchronizer?.synchronize(object : MatrixCallback<SyncResponse> { synchronizer?.synchronize(object : MatrixCallback<SyncResponse> {
override fun onSuccess(data: SyncResponse?) { override fun onSuccess(data: SyncResponse) {
synchronizeButton.visibility = View.VISIBLE synchronizeButton.visibility = View.VISIBLE
loadingView.visibility = View.GONE loadingView.visibility = View.GONE
Timber.v("Sync successful")
} }


override fun onFailure(failure: Failure) { override fun onFailure(failure: Failure) {
synchronizeButton.visibility = View.VISIBLE synchronizeButton.visibility = View.VISIBLE
loadingView.visibility = View.GONE loadingView.visibility = View.GONE
Toast.makeText(this@HomeActivity, failure.toString(), Toast.LENGTH_LONG).show() Timber.e("Sync has failed : %s", failure.toString())
} }
}) })
if (realmHolder != null) { if (realmHolder != null) {
val results = realmHolder.realm.where(EventEntity::class.java).equalTo("chunk.room.roomId", "!UlckfcnwgLKswCmUbe:matrix.org").findAll() val results = realmHolder.instance.where(RoomSummaryEntity::class.java).findAll()
results.addChangeListener(RealmChangeListener<RealmResults<EventEntity>> { results.addChangeListener { summaries ->
Toast.makeText(this@HomeActivity, "Room events data changed", Toast.LENGTH_LONG).show() Timber.v("Summaries updated")
}) }
} }



} }


companion object { companion object {

View File

@ -22,8 +22,8 @@ class LoginActivity : RiotActivity() {
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
setContentView(R.layout.activity_login) setContentView(R.layout.activity_login)
authenticateButton.setOnClickListener { authenticate() }
checkActiveSessions() checkActiveSessions()
authenticateButton.setOnClickListener { authenticate() }
} }


private fun authenticate() { private fun authenticate() {
@ -37,9 +37,8 @@ class LoginActivity : RiotActivity() {
.build() .build()


authenticator.authenticate(homeServerConnectionConfig, login, password, object : MatrixCallback<Session> { authenticator.authenticate(homeServerConnectionConfig, login, password, object : MatrixCallback<Session> {
override fun onSuccess(data: Session?) { override fun onSuccess(data: Session) {
matrix.currentSession = data openSessionAndGoToHome(data)
goToHomeScreen()
} }


override fun onFailure(failure: Failure) { override fun onFailure(failure: Failure) {
@ -51,12 +50,16 @@ class LoginActivity : RiotActivity() {


private fun checkActiveSessions() { private fun checkActiveSessions() {
if (authenticator.hasActiveSessions()) { if (authenticator.hasActiveSessions()) {
matrix.currentSession = authenticator.getLastActiveSession() val session = authenticator.getLastActiveSession()
goToHomeScreen() session?.let {
openSessionAndGoToHome(it)
}
} }
} }


private fun goToHomeScreen() { private fun openSessionAndGoToHome(session: Session) {
matrix.currentSession = session
session.open()
val intent = HomeActivity.newIntent(this) val intent = HomeActivity.newIntent(this)
startActivity(intent) startActivity(intent)
finish() finish()

View File

@ -44,6 +44,7 @@ dependencies {
def arrow_version = "0.7.3" def arrow_version = "0.7.3"
def support_version = '28.0.0' def support_version = '28.0.0'
def moshi_version = '1.7.0' def moshi_version = '1.7.0'
def lifecycle_version = "1.1.1"


implementation fileTree(dir: 'libs', include: ['*.aar']) implementation fileTree(dir: 'libs', include: ['*.aar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
@ -51,6 +52,10 @@ dependencies {
implementation "com.android.support:appcompat-v7:$support_version" implementation "com.android.support:appcompat-v7:$support_version"
implementation "com.android.support:recyclerview-v7:$support_version" implementation "com.android.support:recyclerview-v7:$support_version"


implementation "android.arch.lifecycle:extensions:$lifecycle_version"
kapt "android.arch.lifecycle:compiler:$lifecycle_version"


// Network // Network
implementation 'com.squareup.retrofit2:retrofit:2.4.0' implementation 'com.squareup.retrofit2:retrofit:2.4.0'
implementation 'com.squareup.retrofit2:converter-moshi:2.4.0' implementation 'com.squareup.retrofit2:converter-moshi:2.4.0'

View File

@ -4,7 +4,7 @@ import im.vector.matrix.android.api.failure.Failure


interface MatrixCallback<in T> { interface MatrixCallback<in T> {


fun onSuccess(data: T?) fun onSuccess(data: T)


fun onFailure(failure: Failure) fun onFailure(failure: Failure)



View File

@ -16,7 +16,7 @@ data class Event(
@Json(name = "sender") val sender: String? = null, @Json(name = "sender") val sender: String? = null,
@Json(name = "state_key") val stateKey: String? = null, @Json(name = "state_key") val stateKey: String? = null,
@Json(name = "room_id") var roomId: String? = null, @Json(name = "room_id") var roomId: String? = null,
@Json(name = "unsigned_data") val unsignedData: UnsignedData? = null @Json(name = "unsigned") val unsignedData: UnsignedData? = null
) { ) {


val contentAsJsonObject: JsonObject? by lazy { val contentAsJsonObject: JsonObject? by lazy {

View File

@ -5,7 +5,7 @@ import com.squareup.moshi.JsonClass


@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
data class UnsignedData( data class UnsignedData(
@Json(name = "age") val age: Int, @Json(name = "age") val age: Long?,
@Json(name = "redacted_because") val redactedEvent: Event? = null, @Json(name = "redacted_because") val redactedEvent: Event? = null,
@Json(name = "transaction_id") val transactionId: String @Json(name = "transaction_id") val transactionId: String? = null
) )

View File

@ -0,0 +1,9 @@
package im.vector.matrix.android.api.rooms

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class RoomNameContent(
@Json(name = "name") val name: String
)

View File

@ -0,0 +1,9 @@
package im.vector.matrix.android.api.rooms

import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass

@JsonClass(generateAdapter = true)
data class RoomTopicContent(
@Json(name = "topic") val topic: String
)

View File

@ -1,15 +1,20 @@
package im.vector.matrix.android.api.session package im.vector.matrix.android.api.session


import im.vector.matrix.android.internal.database.RealmInstanceHolder import android.support.annotation.MainThread
import im.vector.matrix.android.internal.database.SessionRealmHolder
import im.vector.matrix.android.internal.events.sync.Synchronizer import im.vector.matrix.android.internal.events.sync.Synchronizer


interface Session { interface Session {


@MainThread
fun open()

fun synchronizer(): Synchronizer fun synchronizer(): Synchronizer


// Visible for testing request directly. Will be deleted // Visible for testing request directly. Will be deleted
fun realmInstanceHolder(): RealmInstanceHolder fun realmHolder(): SessionRealmHolder


@MainThread
fun close() fun close()


} }

View File

@ -1,11 +0,0 @@
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

@ -0,0 +1,25 @@
package im.vector.matrix.android.internal.database

import android.support.annotation.MainThread
import io.realm.Realm
import io.realm.RealmConfiguration

class SessionRealmHolder(private val realmConfiguration: RealmConfiguration
) {

lateinit var instance: Realm

@MainThread
fun open() {
instance = Realm.getInstance(realmConfiguration)
}

@MainThread
fun close() {
instance.close()
Realm.compactRealm(realmConfiguration)
}


}

View File

@ -2,6 +2,7 @@ package im.vector.matrix.android.internal.database.mapper


import com.squareup.moshi.Types import com.squareup.moshi.Types
import im.vector.matrix.android.api.events.Event import im.vector.matrix.android.api.events.Event
import im.vector.matrix.android.api.events.UnsignedData
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider


@ -12,14 +13,38 @@ object EventMapper {
private val type = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java) private val type = Types.newParameterizedType(Map::class.java, String::class.java, Any::class.java)
private val adapter = moshi.adapter<Map<String, Any>>(type) private val adapter = moshi.adapter<Map<String, Any>>(type)


fun map(event: Event): EventEntity { internal fun map(event: Event): EventEntity {
val eventEntity = EventEntity() val eventEntity = EventEntity()
eventEntity.eventId = event.eventId!! eventEntity.eventId = event.eventId!!
eventEntity.content = adapter.toJson(event.content) eventEntity.content = adapter.toJson(event.content)
eventEntity.prevContent = adapter.toJson(event.prevContent) eventEntity.prevContent = adapter.toJson(event.prevContent)
eventEntity.stateKey = event.stateKey eventEntity.stateKey = event.stateKey
eventEntity.type = event.type eventEntity.type = event.type
eventEntity.sender = event.sender
eventEntity.originServerTs = event.originServerTs
eventEntity.age = event.unsignedData?.age ?: event.originServerTs
return eventEntity return eventEntity
} }


internal fun map(eventEntity: EventEntity): Event {
return Event(
eventEntity.type,
eventEntity.eventId,
adapter.fromJson(eventEntity.content),
adapter.fromJson(eventEntity.prevContent ?: ""),
eventEntity.originServerTs,
eventEntity.sender,
eventEntity.stateKey,
null,
UnsignedData(eventEntity.age)
)
}
}

fun EventEntity.asDomain(): Event {
return EventMapper.map(this)
}

fun Event.asEntity(): EventEntity {
return EventMapper.map(this)
} }

View File

@ -9,9 +9,14 @@ open class EventEntity(@PrimaryKey var eventId: String = "",
var type: String = "", var type: String = "",
var content: String = "", var content: String = "",
var prevContent: String? = null, var prevContent: String? = null,
var stateKey: String? = null var stateKey: String? = null,
var originServerTs: Long? = null,
var sender: String? = null,
var age: Long? = 0
) : RealmObject() { ) : RealmObject() {


companion object

@LinkingObjects("events") @LinkingObjects("events")
val chunk: RealmResults<ChunkEntity>? = null val chunk: RealmResults<ChunkEntity>? = null



View File

@ -6,10 +6,10 @@ import io.realm.annotations.Ignore
import io.realm.annotations.PrimaryKey import io.realm.annotations.PrimaryKey
import kotlin.properties.Delegates import kotlin.properties.Delegates


open class RoomEntity : RealmObject() { open class RoomEntity(@PrimaryKey var roomId: String = "",
var chunks: RealmList<ChunkEntity> = RealmList()
) : RealmObject() {


@PrimaryKey var roomId: String = ""
var chunks: RealmList<ChunkEntity> = RealmList()
private var membershipStr: String = Membership.NONE.name private var membershipStr: String = Membership.NONE.name


@delegate:Ignore var membership: Membership by Delegates.observable(Membership.valueOf(membershipStr)) { _, _, newValue -> @delegate:Ignore var membership: Membership by Delegates.observable(Membership.valueOf(membershipStr)) { _, _, newValue ->

View File

@ -0,0 +1,11 @@
package im.vector.matrix.android.internal.database.model

import io.realm.RealmObject
import io.realm.annotations.PrimaryKey

// TODO to be completed
open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var displayName: String? = "",
var topic: String? = "",
var lastMessage: EventEntity? = null
) : RealmObject()

View File

@ -0,0 +1,20 @@
package im.vector.matrix.android.internal.database.query

import im.vector.matrix.android.internal.database.model.ChunkEntity
import io.realm.Realm

fun ChunkEntity.Companion.getLastChunkFromRoom(realm: Realm, roomId: String): ChunkEntity? {
return realm.where(ChunkEntity::class.java)
.equalTo("room.roomId", roomId)
.isNull("nextToken")
.and()
.isNotNull("prevToken")
.findAll()
.lastOrNull()
}

fun ChunkEntity.Companion.getChunkIncludingEvents(realm: Realm, eventIds: List<String>): ChunkEntity? {
return realm.where(ChunkEntity::class.java)
.`in`("events.eventId", eventIds.toTypedArray())
.findFirst()
}

View File

@ -0,0 +1,19 @@
package im.vector.matrix.android.internal.database.query

import im.vector.matrix.android.internal.database.model.EventEntity
import io.realm.Realm
import io.realm.RealmResults

fun EventEntity.Companion.getAllFromRoom(realm: Realm, roomId: String): RealmResults<EventEntity> {
return realm.where(EventEntity::class.java)
.equalTo("chunk.room.roomId", roomId)
.findAll()
}

fun RealmResults<EventEntity>.getLast(type: String? = null): EventEntity? {
var query = this.where()
if (type != null) {
query = query.equalTo("type", type)
}
return query.findAll().sort("age").lastOrNull()
}

View File

@ -2,6 +2,7 @@ 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.realm.Realm import io.realm.Realm
import io.realm.RealmResults


fun RoomEntity.Companion.getForId(realm: Realm, roomId: String): RoomEntity? { fun RoomEntity.Companion.getForId(realm: Realm, roomId: String): RoomEntity? {
return realm.where<RoomEntity>(RoomEntity::class.java) return realm.where<RoomEntity>(RoomEntity::class.java)
@ -9,10 +10,10 @@ fun RoomEntity.Companion.getForId(realm: Realm, roomId: String): RoomEntity? {
.findFirst() .findFirst()
} }


fun RoomEntity.Companion.getAll(realm: Realm, membership: RoomEntity.Membership? = null): List<RoomEntity> { fun RoomEntity.Companion.getAllAsync(realm: Realm, membership: RoomEntity.Membership? = null): RealmResults<RoomEntity> {
val query = realm.where(RoomEntity::class.java) val query = realm.where(RoomEntity::class.java)
if (membership != null) { if (membership != null) {
query.equalTo("membership", membership.name) query.equalTo("membership", membership.name)
} }
return query.findAll() return query.findAllAsync()
} }

View File

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

View File

@ -1,9 +1,7 @@
package im.vector.matrix.android.internal.di package im.vector.matrix.android.internal.di


import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory import com.jakewharton.retrofit2.adapter.kotlin.coroutines.CoroutineCallAdapterFactory
import com.squareup.moshi.Moshi
import im.vector.matrix.android.internal.network.AccessTokenInterceptor import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.parsing.UriMoshiAdapter
import okhttp3.OkHttpClient import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor import okhttp3.logging.HttpLoggingInterceptor
import org.koin.dsl.context.ModuleDefinition import org.koin.dsl.context.ModuleDefinition

View File

@ -1,11 +1,12 @@
package im.vector.matrix.android.internal.di package im.vector.matrix.android.internal.di


import im.vector.matrix.android.internal.auth.data.SessionParams import im.vector.matrix.android.internal.auth.data.SessionParams
import im.vector.matrix.android.internal.database.RealmInstanceHolder import im.vector.matrix.android.internal.database.SessionRealmHolder
import im.vector.matrix.android.internal.legacy.MXDataHandler import im.vector.matrix.android.internal.legacy.MXDataHandler
import im.vector.matrix.android.internal.legacy.MXSession import im.vector.matrix.android.internal.legacy.MXSession
import im.vector.matrix.android.internal.legacy.data.store.MXFileStore import im.vector.matrix.android.internal.legacy.data.store.MXFileStore
import im.vector.matrix.android.internal.session.DefaultSession import im.vector.matrix.android.internal.session.DefaultSession
import im.vector.matrix.android.internal.session.RoomSummaryObserver
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import org.koin.dsl.context.ModuleDefinition import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module import org.koin.dsl.module.Module
@ -21,7 +22,7 @@ class SessionModule(private val sessionParams: SessionParams) : Module {
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
RealmInstanceHolder(get()) SessionRealmHolder(get())
} }


scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
@ -31,6 +32,10 @@ class SessionModule(private val sessionParams: SessionParams) : Module {
.build() .build()
} }


scope(DefaultSession.SCOPE) {
RoomSummaryObserver(get(), get(), get())
}

scope(DefaultSession.SCOPE) { scope(DefaultSession.SCOPE) {
val store = MXFileStore(sessionParams.credentials, false, get()) val store = MXFileStore(sessionParams.credentials, false, get())
val dataHandler = MXDataHandler(store, sessionParams.credentials) val dataHandler = MXDataHandler(store, sessionParams.credentials)

View File

@ -1,10 +1,12 @@
package im.vector.matrix.android.internal.events.sync package im.vector.matrix.android.internal.events.sync


import im.vector.matrix.android.api.events.Event import im.vector.matrix.android.api.events.Event
import im.vector.matrix.android.internal.database.mapper.EventMapper import im.vector.matrix.android.internal.database.mapper.asEntity
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.getChunkIncludingEvents
import im.vector.matrix.android.internal.database.query.getForId import im.vector.matrix.android.internal.database.query.getForId
import im.vector.matrix.android.internal.database.query.getLastChunkFromRoom
import im.vector.matrix.android.internal.events.sync.data.InvitedRoomSync import im.vector.matrix.android.internal.events.sync.data.InvitedRoomSync
import im.vector.matrix.android.internal.events.sync.data.RoomSync import im.vector.matrix.android.internal.events.sync.data.RoomSync
import io.realm.Realm import io.realm.Realm
@ -39,8 +41,7 @@ class RoomSyncHandler(private val realmConfiguration: RealmConfiguration) {
roomId: String, roomId: String,
roomSync: RoomSync): RoomEntity { roomSync: RoomSync): RoomEntity {


val roomEntity = RoomEntity.getForId(realm, roomId) val roomEntity = RoomEntity.getForId(realm, roomId) ?: RoomEntity(roomId)
?: RoomEntity().apply { this.roomId = roomId }


if (roomEntity.membership == RoomEntity.Membership.INVITED) { if (roomEntity.membership == RoomEntity.Membership.INVITED) {
roomEntity.chunks.deleteAllFromRealm() roomEntity.chunks.deleteAllFromRealm()
@ -95,16 +96,16 @@ class RoomSyncHandler(private val realmConfiguration: RealmConfiguration) {
isLimited: Boolean = true): ChunkEntity { isLimited: Boolean = true): ChunkEntity {


val chunkEntity = if (!isLimited) { val chunkEntity = if (!isLimited) {
realm.where(ChunkEntity::class.java).equalTo("room.roomId", roomId).isNull("nextToken").and().isNotNull("prevToken").findAll().lastOrNull() ChunkEntity.getLastChunkFromRoom(realm, roomId)
} else { } else {
realm.where(ChunkEntity::class.java).`in`("events.eventId", eventList.map { it.eventId }.toTypedArray()).findFirst() val eventIds = eventList.filter { it.eventId != null }.map { it.eventId!! }
} ?: ChunkEntity() ChunkEntity.getChunkIncludingEvents(realm, eventIds)
} ?: ChunkEntity().apply { this.prevToken = prevToken }


chunkEntity.prevToken = prevToken
chunkEntity.nextToken = nextToken chunkEntity.nextToken = nextToken
chunkEntity.isLimited = isLimited chunkEntity.isLimited = isLimited
eventList.forEach { event -> eventList.forEach { event ->
val eventEntity = EventMapper.map(event).let { val eventEntity = event.asEntity().let {
realm.copyToRealmOrUpdate(it) realm.copyToRealmOrUpdate(it)
} }
if (!chunkEntity.events.contains(eventEntity)) { if (!chunkEntity.events.contains(eventEntity)) {

View File

@ -2,6 +2,7 @@ package im.vector.matrix.android.internal.events.sync


import arrow.core.Either import arrow.core.Either
import arrow.core.flatMap import arrow.core.flatMap
import arrow.core.leftIfNull
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
@ -42,6 +43,8 @@ class Synchronizer(private val syncAPI: SyncAPI,
params["filter"] = filterBody.toJSONString() params["filter"] = filterBody.toJSONString()
executeRequest<SyncResponse> { executeRequest<SyncResponse> {
apiCall = syncAPI.sync(params) apiCall = syncAPI.sync(params)
}.leftIfNull {
Failure.Unknown(RuntimeException("Sync response shouln't be null"))
}.flatMap { }.flatMap {
token = it?.nextBatch token = it?.nextBatch
try { try {

View File

@ -32,7 +32,7 @@ class Request<DATA> {
} catch (e: Exception) { } catch (e: Exception) {
when (e) { when (e) {
is IOException -> Either.Left(Failure.NetworkConnection) is IOException -> Either.Left(Failure.NetworkConnection)
else -> Either.Left(Failure.Unknown(e)) else -> Either.Left(Failure.Unknown(e))
} }
} }
} }

View File

@ -1,8 +1,10 @@
package im.vector.matrix.android.internal.session package im.vector.matrix.android.internal.session


import android.os.Looper
import android.support.annotation.MainThread
import im.vector.matrix.android.api.session.Session import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.auth.data.SessionParams import im.vector.matrix.android.internal.auth.data.SessionParams
import im.vector.matrix.android.internal.database.RealmInstanceHolder import im.vector.matrix.android.internal.database.SessionRealmHolder
import im.vector.matrix.android.internal.di.SessionModule 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.SyncModule
import im.vector.matrix.android.internal.events.sync.Synchronizer import im.vector.matrix.android.internal.events.sync.Synchronizer
@ -12,34 +14,57 @@ import org.koin.standalone.StandAloneContext
import org.koin.standalone.getKoin import org.koin.standalone.getKoin
import org.koin.standalone.inject import org.koin.standalone.inject


class DefaultSession(sessionParams: SessionParams) : Session, KoinComponent { class DefaultSession(private val sessionParams: SessionParams) : Session, KoinComponent {

private val realmInstanceHolder by inject<RealmInstanceHolder>()
private val synchronizer by inject<Synchronizer>()
private val scope: Scope

init {
val sessionModule = SessionModule(sessionParams)
val syncModule = SyncModule()
StandAloneContext.loadKoinModules(listOf(sessionModule, syncModule))
scope = getKoin().getOrCreateScope(SCOPE)
}

override fun synchronizer(): Synchronizer {
return synchronizer
}

override fun realmInstanceHolder(): RealmInstanceHolder {
return realmInstanceHolder
}

override fun close() {
realmInstanceHolder.realm.close()
scope.close()
}


companion object { companion object {
const val SCOPE: String = "session" const val SCOPE: String = "session"
} }


private lateinit var scope: Scope

private val realmInstanceHolder by inject<SessionRealmHolder>()
private val synchronizer by inject<Synchronizer>()
private val roomSummaryObserver by inject<RoomSummaryObserver>()

private var isOpen = false

@MainThread
override fun open() {
checkIsMainThread()
assert(!isOpen)
isOpen = true
val sessionModule = SessionModule(sessionParams)
val syncModule = SyncModule()
StandAloneContext.loadKoinModules(listOf(sessionModule, syncModule))
scope = getKoin().getOrCreateScope(SCOPE)
realmInstanceHolder.open()
roomSummaryObserver.start()
}

override fun synchronizer(): Synchronizer {
assert(isOpen)
return synchronizer
}

override fun realmHolder(): SessionRealmHolder {
assert(isOpen)
return realmInstanceHolder
}

@MainThread
override fun close() {
checkIsMainThread()
assert(isOpen)
roomSummaryObserver.dispose()
realmInstanceHolder.close()
scope.close()
isOpen = false
}

private fun checkIsMainThread() {
if (Looper.myLooper() != Looper.getMainLooper()) {
throw IllegalStateException("Should be called on main thread")
}
}

} }

View File

@ -0,0 +1,73 @@
package im.vector.matrix.android.internal.session

import im.vector.matrix.android.api.events.EventType
import im.vector.matrix.android.api.rooms.RoomNameContent
import im.vector.matrix.android.api.rooms.RoomTopicContent
import im.vector.matrix.android.internal.database.SessionRealmHolder
import im.vector.matrix.android.internal.database.mapper.asDomain
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.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.getAllAsync
import im.vector.matrix.android.internal.database.query.getAllFromRoom
import im.vector.matrix.android.internal.database.query.getLast
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmResults
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import java.util.concurrent.atomic.AtomicBoolean

internal class RoomSummaryObserver(private val mainThreadRealm: SessionRealmHolder,
private val matrixCoroutineDispatchers: MatrixCoroutineDispatchers,
private val realmConfiguration: RealmConfiguration
) {

private lateinit var roomResults: RealmResults<RoomEntity>
private var isStarted = AtomicBoolean(false)

fun start() {
if (isStarted.compareAndSet(false, true)) {
roomResults = RoomEntity.getAllAsync(mainThreadRealm.instance)
roomResults.addChangeListener { rooms, changeSet ->
manageRoomResults(rooms, changeSet.changes)
manageRoomResults(rooms, changeSet.insertions)
}
}
}

fun dispose() {
if (isStarted.compareAndSet(true, false)) {
roomResults.removeAllChangeListeners()
}
}

// PRIVATE

private fun manageRoomResults(rooms: RealmResults<RoomEntity>, indexes: IntArray) {
indexes.forEach {
val room = rooms[it]
if (room != null) {
manageRoom(room.roomId)
}
}
}

private fun manageRoom(roomId: String) = GlobalScope.launch(matrixCoroutineDispatchers.io) {
val realm = Realm.getInstance(realmConfiguration)
val roomEvents = EventEntity.getAllFromRoom(realm, roomId)
val lastNameEvent = roomEvents.getLast(EventType.STATE_ROOM_NAME)?.asDomain()
val lastTopicEvent = roomEvents.getLast(EventType.STATE_ROOM_TOPIC)?.asDomain()
val lastMessageEvent = roomEvents.getLast(EventType.MESSAGE)

realm.executeTransaction { realmInstance ->
val roomSummary = realmInstance.copyToRealmOrUpdate(RoomSummaryEntity(roomId))
roomSummary.displayName = lastNameEvent?.content<RoomNameContent>()?.name
roomSummary.topic = lastTopicEvent?.content<RoomTopicContent>()?.topic
roomSummary.lastMessage = lastMessageEvent
}
realm.close()
}

}