Dagger: prepare for multi session [WIP]

This commit is contained in:
ganfra
2019-06-26 15:22:44 +02:00
parent 47968c9447
commit 6e7adaec59
74 changed files with 727 additions and 358 deletions

View File

@ -53,7 +53,6 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
@Inject internal lateinit var backgroundDetectionObserver: BackgroundDetectionObserver
@Inject internal lateinit var olmManager: OlmManager
@Inject internal lateinit var sessionManager: SessionManager
var currentSession: Session? = null
init {
Monarchy.init(context)
@ -63,12 +62,6 @@ class Matrix private constructor(context: Context, matrixConfiguration: MatrixCo
}
ProcessLifecycleOwner.get().lifecycle.addObserver(backgroundDetectionObserver)
userAgentHolder.setApplicationFlavor(matrixConfiguration.applicationFlavor)
authenticator.getLastActiveSession()?.also {
currentSession = it
it.open()
it.setFilter(FilterService.FilterPreset.RiotFilter)
it.startSync()
}
}
fun getUserAgent() = userAgentHolder.userAgent

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.api.auth
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.HomeServerConnectionConfig
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.util.Cancelable
@ -40,14 +41,24 @@ interface Authenticator {
* Check if there is an active [Session].
* @return true if there is at least one active session.
*/
fun hasActiveSessions(): Boolean
fun hasAuthenticatedSessions(): Boolean
//TODO remove this method. Shouldn't be managed like that.
/**
* Get the last active [Session], if there is an active session.
* @return the lastActive session if any, or null
*/
fun getLastActiveSession(): Session?
fun getLastAuthenticatedSession(): Session?
/**
* Get an authenticated session. You should at least call authenticate one time before.
* If you logout, this session will no longer be valid.
*
* @param sessionParams the sessionParams to open with.
* @return the associated session if any, or null
*/
fun getSession(sessionParams: SessionParams): Session?
}

View File

@ -20,6 +20,7 @@ package im.vector.matrix.android.internal
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.MatrixComponent
import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.android.internal.session.DaggerSessionComponent
@ -27,27 +28,18 @@ import im.vector.matrix.android.internal.session.SessionComponent
import javax.inject.Inject
@MatrixScope
internal class SessionManager @Inject constructor(private val matrixComponent: MatrixComponent) {
internal class SessionManager @Inject constructor(private val matrixComponent: MatrixComponent,
private val sessionParamsStore: SessionParamsStore) {
private val sessionComponents = HashMap<String, SessionComponent>()
fun getSessionComponent(userId: String): SessionComponent? {
return sessionComponents[userId]
val sessionParams = sessionParamsStore.get(userId) ?: return null
return getOrCreateSessionComponent(sessionParams)
}
fun createSession(sessionParams: SessionParams): Session {
val userId = sessionParams.credentials.userId
if (sessionComponents.containsKey(userId)) {
throw RuntimeException("You already have a session for the user $userId")
}
return DaggerSessionComponent
.factory()
.create(matrixComponent, sessionParams)
.also {
sessionComponents[userId] = it
}.let {
it.session()
}
fun getOrCreateSession(sessionParams: SessionParams): Session {
return getOrCreateSessionComponent(sessionParams).session()
}
fun releaseSession(userId: String) {
@ -59,5 +51,17 @@ internal class SessionManager @Inject constructor(private val matrixComponent: M
}
}
private fun getOrCreateSessionComponent(sessionParams: SessionParams): SessionComponent {
val userId = sessionParams.credentials.userId
if (sessionComponents.containsKey(userId)) {
return sessionComponents[userId]!!
}
return DaggerSessionComponent
.factory()
.create(matrixComponent, sessionParams)
.also {
sessionComponents[sessionParams.credentials.userId] = it
}
}
}

View File

@ -49,6 +49,8 @@ internal abstract class AuthModule {
}
}
@Binds
abstract fun bindSessionParamsStore(sessionParamsStore: RealmSessionParamsStore): SessionParamsStore

View File

@ -28,32 +28,41 @@ import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.data.PasswordLoginParams
import im.vector.matrix.android.internal.auth.data.ThreePidMedium
import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.network.RetrofitFactory
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
import kotlinx.coroutines.withContext
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import javax.inject.Inject
internal class DefaultAuthenticator @Inject constructor(private val retrofitBuilder: Retrofit.Builder,
internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
private val okHttpClient: OkHttpClient,
private val retrofitFactory: RetrofitFactory,
private val coroutineDispatchers: MatrixCoroutineDispatchers,
private val sessionParamsStore: SessionParamsStore,
private val sessionManager: SessionManager
) : Authenticator {
override fun hasActiveSessions(): Boolean {
return sessionParamsStore.get() != null
override fun hasAuthenticatedSessions(): Boolean {
return sessionParamsStore.getLast() != null
}
override fun getLastActiveSession(): Session? {
val sessionParams = sessionParamsStore.get()
override fun getLastAuthenticatedSession(): Session? {
val sessionParams = sessionParamsStore.getLast()
return sessionParams?.let {
sessionManager.createSession(it)
sessionManager.getOrCreateSession(it)
}
}
override fun getSession(sessionParams: SessionParams): Session? {
return sessionManager.getOrCreateSession(sessionParams)
}
override fun authenticate(homeServerConnectionConfig: HomeServerConnectionConfig,
login: String,
password: String,
@ -84,13 +93,13 @@ internal class DefaultAuthenticator @Inject constructor(private val retrofitBuil
sessionParamsStore.save(sessionParams)
sessionParams
}.map {
sessionManager.createSession(it)
sessionManager.getOrCreateSession(it)
}
}
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {
val retrofit = retrofitBuilder.baseUrl(homeServerConnectionConfig.homeServerUri.toString()).build()
val retrofit = retrofitFactory.create(okHttpClient, homeServerConnectionConfig.homeServerUri.toString())
return retrofit.create(AuthAPI::class.java)
}

View File

@ -21,9 +21,15 @@ import im.vector.matrix.android.api.auth.data.SessionParams
internal interface SessionParamsStore {
fun get(): SessionParams?
fun get(userId: String): SessionParams?
fun getLast(): SessionParams?
fun getAll(): List<SessionParams>
fun save(sessionParams: SessionParams): Try<SessionParams>
fun delete(): Try<Unit>
fun delete(userId: String): Try<Unit>
fun deleteAll(): Try<Unit>
}

View File

@ -20,15 +20,48 @@ import arrow.core.Try
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.AuthDatabase
import im.vector.matrix.android.internal.di.MatrixScope
import io.realm.Realm
import io.realm.RealmConfiguration
import javax.inject.Inject
internal class RealmSessionParamsStore @Inject constructor(private val mapper: SessionParamsMapper,
@AuthDatabase private val realmConfiguration: RealmConfiguration
@AuthDatabase
private val realmConfiguration: RealmConfiguration
) : SessionParamsStore {
override fun getLast(): SessionParams? {
val realm = Realm.getInstance(realmConfiguration)
val sessionParams = realm
.where(SessionParamsEntity::class.java)
.findAll()
.map { mapper.map(it) }
.lastOrNull()
realm.close()
return sessionParams
}
override fun get(userId: String): SessionParams? {
val realm = Realm.getInstance(realmConfiguration)
val sessionParams = realm
.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.map { mapper.map(it) }
.firstOrNull()
realm.close()
return sessionParams
}
override fun getAll(): List<SessionParams> {
val realm = Realm.getInstance(realmConfiguration)
val sessionParams = realm
.where(SessionParamsEntity::class.java)
.findAll()
.mapNotNull { mapper.map(it) }
realm.close()
return sessionParams
}
override fun save(sessionParams: SessionParams): Try<SessionParams> {
return Try {
val entity = mapper.map(sessionParams)
@ -43,18 +76,20 @@ internal class RealmSessionParamsStore @Inject constructor(private val mapper: S
}
}
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
override fun delete(userId: String): Try<Unit> {
return Try {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {
it.where(SessionParamsEntity::class.java)
.equalTo(SessionParamsEntityFields.USER_ID, userId)
.findAll()
.deleteAllFromRealm()
}
realm.close()
}
}
override fun delete(): Try<Unit> {
override fun deleteAll(): Try<Unit> {
return Try {
val realm = Realm.getInstance(realmConfiguration)
realm.executeTransaction {

View File

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

View File

@ -49,7 +49,7 @@ internal class SessionParamsMapper @Inject constructor(moshi: Moshi) {
if (credentialsJson == null || homeServerConnectionConfigJson == null) {
return null
}
return SessionParamsEntity(credentialsJson, homeServerConnectionConfigJson)
return SessionParamsEntity(sessionParams.credentials.userId, credentialsJson, homeServerConnectionConfigJson)
}

View File

@ -0,0 +1,29 @@
/*
*
* * Copyright 2019 New Vector Ltd
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package im.vector.matrix.android.internal.di
import javax.inject.Qualifier
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class Authenticated
@Qualifier
@Retention(AnnotationRetention.RUNTIME)
annotation class Unauthenticated

View File

@ -23,9 +23,11 @@ import dagger.BindsInstance
import dagger.Component
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.auth.Authenticator
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.AuthModule
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.util.BackgroundDetectionObserver
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
@ -42,8 +44,7 @@ internal interface MatrixComponent {
fun moshi(): Moshi
fun retrofitBuilder(): Retrofit.Builder
@Unauthenticated
fun okHttpClient(): OkHttpClient
fun authenticator(): Authenticator
@ -62,6 +63,8 @@ internal interface MatrixComponent {
fun backgroundDetectionObserver(): BackgroundDetectionObserver
fun sessionManager(): SessionManager
fun inject(matrix: Matrix)
@Component.Factory

View File

@ -66,9 +66,9 @@ internal object NetworkModule {
@MatrixScope
@Provides
@JvmStatic
@Unauthenticated
fun providesOkHttpClient(stethoInterceptor: StethoInterceptor,
userAgentInterceptor: UserAgentInterceptor,
accessTokenInterceptor: AccessTokenInterceptor,
httpLoggingInterceptor: HttpLoggingInterceptor,
curlLoggingInterceptor: CurlLoggingInterceptor,
okReplayInterceptor: OkReplayInterceptor): OkHttpClient {
@ -78,7 +78,6 @@ internal object NetworkModule {
.writeTimeout(30, TimeUnit.SECONDS)
.addNetworkInterceptor(stethoInterceptor)
.addInterceptor(userAgentInterceptor)
.addInterceptor(accessTokenInterceptor)
.addInterceptor(httpLoggingInterceptor)
.apply {
if (BuildConfig.LOG_PRIVATE_DATA) {
@ -94,15 +93,4 @@ internal object NetworkModule {
fun providesMoshi(): Moshi {
return MoshiProvider.providesMoshi()
}
@Provides
@JvmStatic
fun providesRetrofitBuilder(okHttpClient: OkHttpClient,
moshi: Moshi): Retrofit.Builder {
return Retrofit.Builder()
.client(okHttpClient)
.addConverterFactory(UnitConverterFactory)
.addConverterFactory(MoshiConverterFactory.create(moshi))
}
}

View File

@ -16,22 +16,19 @@
package im.vector.matrix.android.internal.network
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.session.SessionScope
import okhttp3.Interceptor
import okhttp3.Response
import javax.inject.Inject
internal class AccessTokenInterceptor @Inject constructor(private val sessionParamsStore: SessionParamsStore) : Interceptor {
internal class AccessTokenInterceptor @Inject constructor(private val credentials: Credentials) : Interceptor {
override fun intercept(chain: Interceptor.Chain): Response {
var request = chain.request()
val newRequestBuilder = request.newBuilder()
// Add the access token to all requests if it is set
val sessionParams = sessionParamsStore.get()
sessionParams?.let {
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer " + it.credentials.accessToken)
}
newRequestBuilder.addHeader(HttpHeaders.Authorization, "Bearer " + credentials.accessToken)
request = newRequestBuilder.build()
return chain.proceed(request)
}

View File

@ -0,0 +1,39 @@
/*
*
* * Copyright 2019 New Vector Ltd
* *
* * Licensed under the Apache License, Version 2.0 (the "License");
* * you may not use this file except in compliance with the License.
* * You may obtain a copy of the License at
* *
* * http://www.apache.org/licenses/LICENSE-2.0
* *
* * Unless required by applicable law or agreed to in writing, software
* * distributed under the License is distributed on an "AS IS" BASIS,
* * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* * See the License for the specific language governing permissions and
* * limitations under the License.
*
*/
package im.vector.matrix.android.internal.network
import com.squareup.moshi.Moshi
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory
import javax.inject.Inject
class RetrofitFactory @Inject constructor(private val moshi: Moshi) {
fun create(okHttpClient: OkHttpClient, baseUrl: String): Retrofit {
return Retrofit.Builder()
.baseUrl(baseUrl)
.client(okHttpClient)
.addConverterFactory(UnitConverterFactory)
.addConverterFactory(MoshiConverterFactory.create(moshi))
.build()
}
}

View File

@ -28,13 +28,18 @@ import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.database.model.SessionRealmModule
import im.vector.matrix.android.internal.di.Authenticated
import im.vector.matrix.android.internal.di.SessionDatabase
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.network.AccessTokenInterceptor
import im.vector.matrix.android.internal.network.RetrofitFactory
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
import im.vector.matrix.android.internal.session.room.EventRelationsAggregationUpdater
import im.vector.matrix.android.internal.session.room.prune.EventsPruner
import im.vector.matrix.android.internal.session.user.UserEntityUpdater
import im.vector.matrix.android.internal.util.md5
import io.realm.RealmConfiguration
import okhttp3.OkHttpClient
import retrofit2.Retrofit
import java.io.File
@ -86,13 +91,26 @@ internal abstract class SessionModule {
@JvmStatic
@Provides
@SessionScope
fun providesRetrofit(sessionParams: SessionParams, retrofitBuilder: Retrofit.Builder): Retrofit {
return retrofitBuilder
.baseUrl(sessionParams.homeServerConnectionConfig.homeServerUri.toString())
@Authenticated
fun providesOkHttpClient(@Unauthenticated okHttpClient: OkHttpClient,
accessTokenInterceptor: AccessTokenInterceptor): OkHttpClient {
return okHttpClient.newBuilder()
.addInterceptor(accessTokenInterceptor)
.build()
}
@JvmStatic
@Provides
@SessionScope
fun providesRetrofit(@Authenticated okHttpClient: OkHttpClient,
sessionParams: SessionParams,
retrofitFactory: RetrofitFactory): Retrofit {
return retrofitFactory
.create(okHttpClient, sessionParams.homeServerConnectionConfig.homeServerUri.toString())
}
}
@Binds
abstract fun bindSession(session: DefaultSession): Session

View File

@ -20,7 +20,9 @@ import arrow.core.Try
import arrow.core.Try.Companion.raise
import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.di.Authenticated
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.di.Unauthenticated
import im.vector.matrix.android.internal.network.ProgressRequestBody
import im.vector.matrix.android.internal.session.SessionScope
import okhttp3.*
@ -29,7 +31,8 @@ import java.io.IOException
import javax.inject.Inject
internal class FileUploader @Inject constructor(private val okHttpClient: OkHttpClient,
internal class FileUploader @Inject constructor(@Authenticated
private val okHttpClient: OkHttpClient,
private val sessionParams: SessionParams,
private val moshi: Moshi) {

View File

@ -31,6 +31,7 @@ import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmQuery
import timber.log.Timber
import javax.inject.Inject
internal class SenderRoomMemberExtractor @Inject constructor(private val roomId: String) {
@ -46,9 +47,8 @@ internal class SenderRoomMemberExtractor @Inject constructor(private val roomId:
event.stateIndex <= 0 -> baseQuery(chunkEntity.events, sender, unlinked).next(from = event.stateIndex)?.prevContent
else -> baseQuery(chunkEntity.events, sender, unlinked).prev(since = event.stateIndex)?.content
}
val fallbackContent = content
?: baseQuery(roomEntity.untimelinedStateEvents, sender, unlinked).prev(since = event.stateIndex)?.content
?: baseQuery(roomEntity.untimelinedStateEvents, sender, unlinked).prev(since = event.stateIndex)?.content
return ContentMapper.map(fallbackContent).toModel()
}

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.internal.session.signout
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith

View File

@ -17,6 +17,9 @@
package im.vector.matrix.android.internal.session.signout
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
@ -25,14 +28,20 @@ import javax.inject.Inject
internal interface SignOutTask : Task<Unit, Unit>
internal class DefaultSignOutTask @Inject constructor(private val signOutAPI: SignOutAPI,
internal class DefaultSignOutTask @Inject constructor(private val credentials: Credentials,
private val signOutAPI: SignOutAPI,
private val sessionManager: SessionManager,
private val sessionParamsStore: SessionParamsStore) : SignOutTask {
override suspend fun execute(params: Unit): Try<Unit> {
return executeRequest<Unit> {
apiCall = signOutAPI.signOut()
}.flatMap {
sessionParamsStore.delete()
sessionParamsStore.delete(credentials.userId)
}.flatMap {
Try {
sessionManager.releaseSession(credentials.userId)
}
}
}
}

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.sync
import arrow.core.Try
import arrow.core.failure
import arrow.core.recoverWith
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.internal.auth.SessionParamsStore
@ -36,6 +37,7 @@ internal interface SyncTask : Task<SyncTask.Params, SyncResponse> {
}
internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
private val credentials: Credentials,
private val filterRepository: FilterRepository,
private val syncResponseHandler: SyncResponseHandler,
private val sessionParamsStore: SessionParamsStore
@ -58,7 +60,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
// Intercept 401
if (throwable is Failure.ServerError
&& throwable.error.code == MatrixError.UNKNOWN_TOKEN) {
sessionParamsStore.delete()
sessionParamsStore.delete(credentials.userId)
}
// Transmit the throwable