Make sync request works as initial sync

This commit is contained in:
ganfra
2018-10-08 17:52:45 +02:00
parent a7eecdffae
commit 058f1704fa
35 changed files with 422 additions and 224 deletions

View File

@ -1,21 +1,42 @@
package im.vector.matrix.android.api
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.DefaultSession
import android.content.Context
import im.vector.matrix.android.BuildConfig
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.thread.MainThreadExecutor
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 org.koin.standalone.KoinComponent
import org.koin.standalone.StandAloneContext.loadKoinModules
import org.koin.standalone.inject
import timber.log.Timber
import timber.log.Timber.DebugTree
import java.util.concurrent.Executor
class Matrix(matrixOptions: MatrixOptions) {
class Matrix(matrixOptions: MatrixOptions) : KoinComponent {
private val authenticator by inject<Authenticator>()
var currentSession: Session? = null
init {
val matrixModule = MatrixModule(matrixOptions)
val networkModule = NetworkModule()
loadKoinModules(listOf(matrixModule, networkModule))
val authModule = AuthModule(matrixOptions.context)
loadKoinModules(listOf(matrixModule, networkModule, authModule))
if (BuildConfig.DEBUG) {
Timber.plant(DebugTree())
}
}
fun createSession(homeServerConnectionConfig: HomeServerConnectionConfig): Session {
return DefaultSession(homeServerConnectionConfig)
fun authenticator(): Authenticator {
return authenticator
}
}
data class MatrixOptions(val context: Context,
val mainExecutor: Executor = MainThreadExecutor())

View File

@ -1,8 +0,0 @@
package im.vector.matrix.android.api
import android.content.Context
import im.vector.matrix.android.api.thread.MainThreadExecutor
import java.util.concurrent.Executor
data class MatrixOptions(val context: Context,
val mainExecutor: Executor = MainThreadExecutor())

View File

@ -1,11 +0,0 @@
package im.vector.matrix.android.api
import im.vector.matrix.android.api.auth.Authenticator
interface Session {
fun authenticator(): Authenticator
fun close()
}

View File

@ -1,11 +1,12 @@
package im.vector.matrix.android.api.auth
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.auth.data.Credentials
interface Authenticator {
fun authenticate(login: String, password: String, callback: MatrixCallback<Credentials>): Cancelable
fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, login: String, password: String, callback: MatrixCallback<Session>): Cancelable
}

View File

@ -1,6 +1,6 @@
package im.vector.matrix.android.api.auth
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.Credentials
interface CredentialsStore {

View File

@ -1,15 +0,0 @@
package im.vector.matrix.android.api.auth.data
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
import io.objectbox.annotation.Entity
import io.objectbox.annotation.Id
@Entity
@JsonClass(generateAdapter = true)
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,
@Json(name = "refresh_token") val refreshToken: String?,
@Json(name = "device_id") val deviceId: String?)

View File

@ -1,31 +1,17 @@
package im.vector.matrix.android.api.events
import com.squareup.moshi.Json
import com.squareup.moshi.Moshi
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class Event(
@Json(name = "event_id") val eventId: String,
@Json(name = "type") val type: EventType,
@Json(name = "content") val content: String,
@Json(name = "origin_server_ts") val originServerTs: Long,
@Json(name = "prev_content") val prevContent: String? = null,
@Json(name = "type") val type: String,
@Json(name = "event_id") val eventId: String? = null,
@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,
@Json(name = "sender") val sender: String? = null,
@Json(name = "state_key") val stateKey: String? = null,
@Json(name = "room_id") val roomId: String? = null,
@Json(name = "unsigned_data") val unsignedData: UnsignedData? = null
) {
inline fun <reified T> content(): T? {
val moshi = Moshi.Builder().build()
return moshi.adapter<T>(T::class.java).fromJson(content)
}
inline fun <reified T> prevContent(): T? {
if (prevContent == null) {
return null
}
val moshi = Moshi.Builder().build()
return moshi.adapter<T>(T::class.java).fromJson(prevContent)
}
}
)

View File

@ -0,0 +1,3 @@
package im.vector.matrix.android.api.events
class EventContent : HashMap<Any, Any>()

View File

@ -40,5 +40,4 @@ enum class EventType {
@Json(name ="m.room.related_groups") STATE_RELATED_GROUPS,
@Json(name ="m.room.pinned_events") STATE_PINNED_EVENT
}

View File

@ -1,7 +1,9 @@
package im.vector.matrix.android.api.events
import com.squareup.moshi.Json
import com.squareup.moshi.JsonClass
@JsonClass(generateAdapter = true)
data class UnsignedData(
@Json(name = "age") val age: Int,
@Json(name = "redacted_because") val redactedEvent: Event? = null,

View File

@ -0,0 +1,11 @@
package im.vector.matrix.android.api.session
import im.vector.matrix.android.internal.events.sync.Synchronizer
interface Session {
fun synchronizer(): Synchronizer
fun close()
}

View File

@ -1,37 +0,0 @@
package im.vector.matrix.android.internal
import im.vector.matrix.android.api.Session
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.auth.LoginModule
import org.koin.core.scope.Scope
import org.koin.standalone.KoinComponent
import org.koin.standalone.StandAloneContext
import org.koin.standalone.getKoin
import org.koin.standalone.inject
class DefaultSession(val homeServerConnectionConfig: HomeServerConnectionConfig) : Session, KoinComponent {
private val authenticator by inject<Authenticator>()
private val scope: Scope
init {
val loginModule = LoginModule(homeServerConnectionConfig)
StandAloneContext.loadKoinModules(listOf(loginModule))
scope = getKoin().createScope(SCOPE)
}
override fun authenticator(): Authenticator {
return authenticator
}
override fun close() {
scope.close()
}
companion object {
const val SCOPE: String = "session"
}
}

View File

@ -1,6 +1,6 @@
package im.vector.matrix.android.internal.auth
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.network.NetworkConstants
import kotlinx.coroutines.Deferred
@ -11,7 +11,7 @@ import retrofit2.http.POST
/**
* The login REST API.
*/
interface LoginApi {
interface AuthAPI {
/**
* Pass params to the server for the current login phase.

View File

@ -0,0 +1,39 @@
package im.vector.matrix.android.internal.auth
import android.content.Context
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.CredentialsStore
import im.vector.matrix.android.internal.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.MyObjectBox
import im.vector.matrix.android.internal.auth.db.ObjectBoxCredentialsStore
import io.objectbox.Box
import io.objectbox.BoxStore
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 {
override fun invoke(): ModuleDefinition = module {
single {
DefaultAuthenticator(get(), 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(Credentials::class.java) as Box<Credentials>
}
single {
ObjectBoxCredentialsStore(get()) as CredentialsStore
}
}.invoke()
}

View File

@ -2,36 +2,49 @@ package im.vector.matrix.android.internal.auth
import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.CredentialsStore
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.map
import im.vector.matrix.android.internal.session.DefaultSession
import im.vector.matrix.android.internal.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.util.map
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import retrofit2.Retrofit
class DefaultAuthenticator(private val loginApi: LoginApi,
class DefaultAuthenticator(private val retrofitBuilder: Retrofit.Builder,
private val jsonMapper: Moshi,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val credentialsStore: CredentialsStore) : Authenticator {
override fun authenticate(login: String, password: String, callback: MatrixCallback<Credentials>): Cancelable {
override fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig, login: String, password: String, callback: MatrixCallback<Session>): Cancelable {
val authAPI = buildAuthAPI(homeServerConnectionConfig)
val job = GlobalScope.launch(coroutineDispatchers.main) {
val loginParams = PasswordLoginParams.userIdentifier(login, password, "Mobile")
val loginResult = executeRequest<Credentials> {
apiCall = loginApi.login(loginParams)
apiCall = authAPI.login(loginParams)
moshi = jsonMapper
dispatcher = coroutineDispatchers.io
}.map {
it?.apply { credentialsStore.save(it) }
}.map {
DefaultSession(homeServerConnectionConfig)
}
loginResult.either({ callback.onFailure(it) }, { callback.onSuccess(it) })
}
return CancelableCoroutine(job)
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
val retrofit = retrofitBuilder.baseUrl(homeServerConnectionConfig.hsUri).build()
return retrofit.create(AuthAPI::class.java)
}
}

View File

@ -1,33 +0,0 @@
package im.vector.matrix.android.internal.auth
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.DefaultSession
import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module
import org.koin.dsl.module.module
import retrofit2.Retrofit
class LoginModule(private val connectionConfig: HomeServerConnectionConfig) : Module {
override fun invoke(): ModuleDefinition = module {
scope(DefaultSession.SCOPE) {
Retrofit.Builder()
.client(get())
.baseUrl(connectionConfig.hsUri)
.addConverterFactory(get())
.addCallAdapterFactory(get())
.build()
}
scope(DefaultSession.SCOPE) {
val retrofit: Retrofit = get()
retrofit.create(LoginApi::class.java)
}
scope(DefaultSession.SCOPE) {
DefaultAuthenticator(get(), get(), get(), get()) as Authenticator
}
}.invoke()
}

View File

@ -0,0 +1,17 @@
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
import io.objectbox.annotation.Index
@Entity
@JsonClass(generateAdapter = true)
data class Credentials(
@Id var id: Long = 0,
@Index @Json(name = "user_id") val userId: String,
@Json(name = "home_server") val homeServer: String,
@Json(name = "access_token") val accessToken: String,
@Json(name = "refresh_token") val refreshToken: String?,
@Json(name = "device_id") val deviceId: String?)

View File

@ -1,18 +0,0 @@
package im.vector.matrix.android.internal.auth.db
import im.vector.matrix.android.api.auth.CredentialsStore
import im.vector.matrix.android.api.auth.data.Credentials
class InMemoryCredentialsStore : CredentialsStore {
var credentials: Credentials? = null
override fun get(): Credentials? {
return credentials
}
override fun save(credentials: Credentials) {
this.credentials = credentials.copy()
}
}

View File

@ -1,7 +1,7 @@
package im.vector.matrix.android.internal.auth.db
import im.vector.matrix.android.api.auth.CredentialsStore
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.Credentials
import io.objectbox.Box
class ObjectBoxCredentialsStore(private val box: Box<Credentials>) : CredentialsStore {
@ -11,7 +11,7 @@ class ObjectBoxCredentialsStore(private val box: Box<Credentials>) : Credentials
}
override fun get(): Credentials? {
return box.all.firstOrNull()
return box.all.lastOrNull()
}
}

View File

@ -1,13 +1,7 @@
package im.vector.matrix.android.internal.di
import im.vector.matrix.android.api.MatrixOptions
import im.vector.matrix.android.api.auth.CredentialsStore
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.MyObjectBox
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.auth.db.ObjectBoxCredentialsStore
import io.objectbox.Box
import io.objectbox.BoxStore
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.IO
import kotlinx.coroutines.asCoroutineDispatcher
@ -23,19 +17,5 @@ class MatrixModule(private val options: MatrixOptions) : Module {
single {
MatrixCoroutineDispatchers(io = Dispatchers.IO, computation = Dispatchers.IO, main = options.mainExecutor.asCoroutineDispatcher())
}
single {
MyObjectBox.builder().androidContext(options.context).build()
}
single {
val boxStore = get() as BoxStore
boxStore.boxFor(Credentials::class.java) as Box<Credentials>
}
single {
ObjectBoxCredentialsStore(get()) as CredentialsStore
}
}.invoke()
}

View File

@ -10,9 +10,12 @@ import org.koin.dsl.module.Module
import org.koin.dsl.module.module
import retrofit2.CallAdapter
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import timber.log.Timber
import java.util.concurrent.TimeUnit
class NetworkModule() : Module {
class NetworkModule : Module {
override fun invoke(): ModuleDefinition = module {
@ -21,17 +24,23 @@ class NetworkModule() : Module {
}
single {
HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.BODY }
val logger = HttpLoggingInterceptor.Logger { message -> Timber.v(message) }
HttpLoggingInterceptor(logger).apply { level = HttpLoggingInterceptor.Level.BODY }
}
single {
OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS)
.readTimeout(30, TimeUnit.SECONDS)
.writeTimeout(30, TimeUnit.SECONDS)
.addInterceptor(get() as AccessTokenInterceptor)
.addInterceptor(get() as HttpLoggingInterceptor)
.build()
}
single { Moshi.Builder().build() }
single {
Moshi.Builder().build()
}
single {
MoshiConverterFactory.create(get()) as Converter.Factory
@ -41,6 +50,12 @@ class NetworkModule() : Module {
CoroutineCallAdapterFactory() as CallAdapter.Factory
}
factory {
Retrofit.Builder()
.client(get())
.addConverterFactory(get())
.addCallAdapterFactory(get())
}
}.invoke()
}

View File

@ -0,0 +1,20 @@
package im.vector.matrix.android.internal.di
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.internal.session.DefaultSession
import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module
import org.koin.dsl.module.module
import retrofit2.Retrofit
class SessionModule(private val connectionConfig: HomeServerConnectionConfig) : Module {
override fun invoke(): ModuleDefinition = module {
scope(DefaultSession.SCOPE) {
val retrofitBuilder = get() as Retrofit.Builder
retrofitBuilder
.baseUrl(connectionConfig.hsUri)
.build()
}
}.invoke()
}

View File

@ -1,7 +1,7 @@
package im.vector.matrix.android.internal.events.sync
import im.vector.matrix.android.internal.network.NetworkConstants
import im.vector.matrix.android.internal.events.sync.data.SyncResponse
import im.vector.matrix.android.internal.network.NetworkConstants
import kotlinx.coroutines.Deferred
import retrofit2.Response
import retrofit2.http.GET
@ -10,6 +10,6 @@ import retrofit2.http.QueryMap
interface SyncAPI {
@GET(NetworkConstants.URI_API_PREFIX_PATH_R0 + "sync")
fun sync(@QueryMap params: Map<String, Any>): Deferred<Response<SyncResponse>>
fun sync(@QueryMap params: Map<String, String>): Deferred<Response<SyncResponse>>
}

View File

@ -0,0 +1,23 @@
package im.vector.matrix.android.internal.events.sync
import im.vector.matrix.android.internal.session.DefaultSession
import org.koin.dsl.context.ModuleDefinition
import org.koin.dsl.module.Module
import org.koin.dsl.module.module
import retrofit2.Retrofit
class SyncModule : Module {
override fun invoke(): ModuleDefinition = module {
scope(DefaultSession.SCOPE) {
val retrofit: Retrofit = get()
retrofit.create(SyncAPI::class.java)
}
scope(DefaultSession.SCOPE) {
Synchronizer(get(), get(), get())
}
}.invoke()
}

View File

@ -1,11 +1,32 @@
package im.vector.matrix.android.internal.events.sync
class Synchronizer(private val syncAPI: SyncAPI) {
fun synchronize() {
import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.events.sync.data.SyncResponse
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
class Synchronizer(private val syncAPI: SyncAPI,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val jsonMapper: Moshi) {
fun synchronize(callback: MatrixCallback<SyncResponse>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) {
val params = HashMap<String, String>()
params["timeout"] = "0"
params["filter"] = "{}"
val syncResponse = executeRequest<SyncResponse> {
apiCall = syncAPI.sync(params)
moshi = jsonMapper
dispatcher = coroutineDispatchers.io
}
syncResponse.either({ callback.onFailure(it) }, { callback.onSuccess(it) })
}
return CancelableCoroutine(job)
}
}

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
// SyncResponse represents the request response for server sync v2.
@ -9,36 +10,36 @@ data class SyncResponse(
/**
* The user private data.
*/
val accountData: Map<String, Any> = emptyMap(),
@Json(name = "account_data") val accountData: Map<String, Any>? = emptyMap(),
/**
* The opaque token for the end.
*/
val nextBatch: String? = null,
@Json(name = "next_batch") val nextBatch: String? = null,
/**
* The updates to the presence status of other users.
*/
val presence: PresenceSyncResponse? = null,
@Json(name = "presence") val presence: PresenceSyncResponse? = null,
/*
* Data directly sent to one of user's devices.
*/
val toDevice: ToDeviceSyncResponse? = null,
@Json(name = "to_device") val toDevice: ToDeviceSyncResponse? = null,
/**
* List of rooms.
*/
val rooms: RoomsSyncResponse? = null,
@Json(name = "rooms") val rooms: RoomsSyncResponse? = null,
/**
* Devices list update
*/
val deviceLists: DeviceListResponse? = null,
@Json(name = "device_lists") val deviceLists: DeviceListResponse? = null,
/**
* One time keys management
*/
val deviceOneTimeKeysCount: DeviceOneTimeKeysCountSyncResponse? = null
@Json(name = "device_one_time_keys_count") val deviceOneTimeKeysCount: DeviceOneTimeKeysCountSyncResponse? = null
)

View File

@ -0,0 +1,38 @@
package im.vector.matrix.android.internal.session
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
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
import org.koin.core.scope.Scope
import org.koin.standalone.KoinComponent
import org.koin.standalone.StandAloneContext
import org.koin.standalone.getKoin
import org.koin.standalone.inject
class DefaultSession(homeServerConnectionConfig: HomeServerConnectionConfig) : Session, KoinComponent {
private val synchronizer by inject<Synchronizer>()
private val scope: Scope
init {
val sessionModule = SessionModule(homeServerConnectionConfig)
val syncModule = SyncModule()
StandAloneContext.loadKoinModules(listOf(sessionModule, syncModule))
scope = getKoin().createScope(SCOPE)
}
override fun synchronizer(): Synchronizer {
return synchronizer
}
override fun close() {
scope.close()
}
companion object {
const val SCOPE: String = "session"
}
}