Merge pull request #107 from vector-im/feature/cache

Clear cache and rework Signout
This commit is contained in:
Benoit Marty 2019-04-17 16:26:48 +02:00 committed by GitHub
commit 0818c55b6d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
19 changed files with 289 additions and 44 deletions

View File

@ -19,7 +19,7 @@ package im.vector.matrix.android.api.failure
import java.io.IOException

/**
* This class allows to expose differents kind of error to be then handled by the application.
* This class allows to expose different kinds of error to be then handled by the application.
* As it is a sealed class, you typically use it like that :
* when(failure) {
* is NetworkConnection -> Unit

View File

@ -33,6 +33,7 @@ data class MatrixError(
const val FORBIDDEN = "M_FORBIDDEN"
const val UNKNOWN = "M_UNKNOWN"
const val UNKNOWN_TOKEN = "M_UNKNOWN_TOKEN"
const val MISSING_TOKEN = "M_MISSING_TOKEN"
const val BAD_JSON = "M_BAD_JSON"
const val NOT_JSON = "M_NOT_JSON"
const val NOT_FOUND = "M_NOT_FOUND"

View File

@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session

import androidx.annotation.MainThread
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.crypto.CryptoService
@ -36,6 +37,7 @@ interface Session :
GroupService,
UserService,
CryptoService,
CacheService,
SignOutService,
FilterService {

@ -93,7 +95,11 @@ interface Session :
/**
* A global session listener to get notified for some events.
*/
// Not used at the moment
interface Listener
interface Listener {
/**
* The access token is not valid anymore
*/
fun onInvalidToken()
}

}

View File

@ -0,0 +1,31 @@
/*
* 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.api.session.cache

import im.vector.matrix.android.api.MatrixCallback

/**
* This interface defines a method to sign out. It's implemented at the session level.
*/
interface CacheService {

/**
* Clear the whole cached data, except credentials. Once done, the session is closed and has to be opened again
*/
fun clearCache(callback: MatrixCallback<Unit>)

}

View File

@ -28,7 +28,7 @@ data class RoomSummary(
val displayName: String = "",
val topic: String = "",
val avatarUrl: String = "",
val isDirect: Boolean,
val isDirect: Boolean = false,
val lastMessage: Event? = null,
val otherMemberIds: List<String> = emptyList(),
var notificationCount: Int = 0,

View File

@ -0,0 +1,33 @@
/*
* 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.api.util

import im.vector.matrix.android.api.MatrixCallback

/**
* Simple MatrixCallback implementation which delegate its calls to another callback
*/
open class MatrixCallbackDelegate<T>(private val callback: MatrixCallback<T>) : MatrixCallback<T> {

override fun onSuccess(data: T) {
callback.onSuccess(data)
}

override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
}
}

View File

@ -23,6 +23,7 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.matrix.android.api.session.content.ContentUrlResolver
import im.vector.matrix.android.api.session.group.Group
@ -36,6 +37,7 @@ import im.vector.matrix.android.api.session.signout.SignOutService
import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.api.util.MatrixCallbackDelegate
import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.di.MatrixKoinHolder
@ -65,6 +67,7 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
private val groupService by inject<GroupService>()
private val userService by inject<UserService>()
private val filterService by inject<FilterService>()
private val cacheService by inject<CacheService>()
private val signOutService by inject<SignOutService>()
private val syncThread by inject<SyncThread>()
private val contentUrlResolver by inject<ContentUrlResolver>()
@ -118,17 +121,18 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
@MainThread
override fun signOut(callback: MatrixCallback<Unit>) {
assert(isOpen)
syncThread.kill()

return signOutService.signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
// Close the session
stopSync()
close()

callback.onSuccess(data)
// Clear the cache
cacheService.clearCache(object : MatrixCallbackDelegate<Unit>(callback) {})
}

override fun onFailure(failure: Throwable) {
callback.onFailure(failure)
// Ignore failure
onSuccess(Unit)
// callback.onFailure(failure)
}
})
}
@ -184,6 +188,19 @@ internal class DefaultSession(override val sessionParams: SessionParams) : Sessi
return filterService.setFilter(filterPreset)
}

override fun clearCache(callback: MatrixCallback<Unit>) {
assert(isOpen)
syncThread.pause()
cacheService.clearCache(object : MatrixCallbackDelegate<Unit>(callback) {
override fun onSuccess(data: Unit) {
// Restart the sync
syncThread.restart()

super.onSuccess(data)
}
})
}

// USER SERVICE

override fun getUser(userId: String): User? {

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session
import android.content.Context
import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.api.session.group.GroupService
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.signout.SignOutService
@ -26,6 +27,9 @@ import im.vector.matrix.android.api.session.sync.FilterService
import im.vector.matrix.android.api.session.user.UserService
import im.vector.matrix.android.internal.database.LiveEntityObserver
import im.vector.matrix.android.internal.database.model.SessionRealmModule
import im.vector.matrix.android.internal.session.cache.ClearCacheTask
import im.vector.matrix.android.internal.session.cache.RealmCacheService
import im.vector.matrix.android.internal.session.cache.RealmClearCacheTask
import im.vector.matrix.android.internal.session.filter.*
import im.vector.matrix.android.internal.session.group.DefaultGroupService
import im.vector.matrix.android.internal.session.group.GroupSummaryUpdater
@ -110,6 +114,14 @@ internal class SessionModule(private val sessionParams: SessionParams) {
DefaultSignOutService(get(), get()) as SignOutService
}

scope(DefaultSession.SCOPE) {
RealmCacheService(get(), get()) as CacheService
}

scope(DefaultSession.SCOPE) {
RealmClearCacheTask(get()) as ClearCacheTask
}

scope(DefaultSession.SCOPE) {
DefaultUserService(get()) as UserService
}

View File

@ -0,0 +1,33 @@
/*
* 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.session.cache

import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.cache.CacheService
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith

internal class RealmCacheService(private val clearCacheTask: ClearCacheTask,
private val taskExecutor: TaskExecutor) : CacheService {

override fun clearCache(callback: MatrixCallback<Unit>) {
clearCacheTask
.configureWith(Unit)
.dispatchTo(callback)
.executeBy(taskExecutor)
}
}

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.session.cache

import arrow.core.Try
import im.vector.matrix.android.internal.task.Task
import io.realm.Realm
import io.realm.RealmConfiguration

internal interface ClearCacheTask : Task<Unit, Unit>

internal class RealmClearCacheTask(val realmConfiguration: RealmConfiguration) : ClearCacheTask {

override fun execute(params: Unit): Try<Unit> {
return Try {
val realm = Realm.getInstance(realmConfiguration)

realm.executeTransaction {
it.deleteAll()
}

realm.close()
}
}
}

View File

@ -52,7 +52,14 @@ internal class DefaultRoom(
RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
}
Transformations.map(liveRealmData) { results ->
results.map { it.asDomain() }.first()
val roomSummaries = results.map { it.asDomain() }

if (roomSummaries.isEmpty()) {
// Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache
RoomSummary(roomId)
} else {
roomSummaries.first()
}
}
}


View File

@ -31,7 +31,6 @@ internal class DefaultSignOutTask(private val signOutAPI: SignOutAPI,
return executeRequest<Unit> {
apiCall = signOutAPI.signOut()
}.flatMap {
// TODO Clear DB, media cache, etc.
sessionParamsStore.delete()
}
}

View File

@ -56,7 +56,7 @@ internal class SyncModule {
}

scope(DefaultSession.SCOPE) {
DefaultSyncTask(get(), get(), get()) as SyncTask
DefaultSyncTask(get(), get(), get(), get()) as SyncTask
}

scope(DefaultSession.SCOPE) {

View File

@ -17,6 +17,11 @@
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.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.internal.auth.SessionParamsStore
import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.filter.FilterRepository
import im.vector.matrix.android.internal.session.sync.model.SyncResponse
@ -30,7 +35,8 @@ internal interface SyncTask : Task<SyncTask.Params, SyncResponse> {

internal class DefaultSyncTask(private val syncAPI: SyncAPI,
private val filterRepository: FilterRepository,
private val syncResponseHandler: SyncResponseHandler
private val syncResponseHandler: SyncResponseHandler,
private val sessionParamsStore: SessionParamsStore
) : SyncTask {


@ -46,6 +52,15 @@ internal class DefaultSyncTask(private val syncAPI: SyncAPI,

return executeRequest<SyncResponse> {
apiCall = syncAPI.sync(requestParams)
}.recoverWith { throwable ->
// Intercept 401
if (throwable is Failure.ServerError
&& throwable.error.code == MatrixError.UNKNOWN_TOKEN) {
sessionParamsStore.delete()
}

// Transmit the throwable
throwable.failure()
}.flatMap { syncResponse ->
syncResponseHandler.handleResponse(syncResponse, params.token, false)
}

View File

@ -19,6 +19,7 @@ package im.vector.matrix.android.internal.session.sync.job
import com.squareup.moshi.JsonEncodingException
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.failure.MatrixError
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.network.NetworkConnectivityChecker
import im.vector.matrix.android.internal.session.sync.SyncTask
@ -59,7 +60,11 @@ internal class SyncThread(private val syncTask: SyncTask,
if (state != State.PAUSED) {
return@synchronized
}
Timber.v("Unpause sync...")
Timber.v("Resume sync...")

// Retrieve the last token, it may have been deleted in case of a clear cache
nextBatch = syncTokenStore.getLastToken()

state = State.RUNNING
lock.notify()
}
@ -97,7 +102,7 @@ internal class SyncThread(private val syncTask: SyncTask,
lock.wait()
}
} else {
Timber.v("Execute sync request...")
Timber.v("Execute sync request with token $nextBatch")
val latch = CountDownLatch(1)
val params = SyncTask.Params(nextBatch)
cancelableTask = syncTask.configureWith(params)
@ -124,6 +129,13 @@ internal class SyncThread(private val syncTask: SyncTask,
// Wait 10s before retrying
sleep(RETRY_WAIT_TIME_MS)
}

if (failure is Failure.ServerError
&& (failure.error.code == MatrixError.UNKNOWN_TOKEN || failure.error.code == MatrixError.MISSING_TOKEN)) {
// No token or invalid token, stop the thread
state = State.KILLING
}

latch.countDown()
}


View File

@ -16,8 +16,11 @@

package im.vector.riotredesign.features

import android.app.Activity
import android.content.Intent
import android.os.Bundle
import im.vector.matrix.android.api.Matrix
import im.vector.matrix.android.api.MatrixCallback
import im.vector.riotredesign.core.platform.VectorBaseActivity
import im.vector.riotredesign.features.home.HomeActivity
import im.vector.riotredesign.features.login.LoginActivity
@ -25,10 +28,52 @@ import im.vector.riotredesign.features.login.LoginActivity

class MainActivity : VectorBaseActivity() {

companion object {
private const val EXTRA_CLEAR_CACHE = "EXTRA_CLEAR_CACHE"
private const val EXTRA_CLEAR_CREDENTIALS = "EXTRA_CLEAR_CREDENTIALS"

// Special action to clear cache and/or clear credentials
fun restartApp(activity: Activity, clearCache: Boolean = false, clearCredentials: Boolean = false) {
val intent = Intent(activity, MainActivity::class.java)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)

intent.putExtra(EXTRA_CLEAR_CACHE, clearCache)
intent.putExtra(EXTRA_CLEAR_CREDENTIALS, clearCredentials)
activity.startActivity(intent)
}
}

private val authenticator = Matrix.getInstance().authenticator()

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)

val session = Matrix.getInstance().currentSession

val clearCache = intent.getBooleanExtra(EXTRA_CLEAR_CACHE, false)
val clearCredentials = intent.getBooleanExtra(EXTRA_CLEAR_CREDENTIALS, false)

if (session == null) {
start()
} else {
// Handle some wanted cleanup
when {
clearCredentials -> session.signOut(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
start()
}
})
clearCache -> session.clearCache(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
start()
}
})
else -> start()
}
}
}

private fun start() {
val intent = if (authenticator.hasActiveSessions()) {
HomeActivity.newIntent(this)
} else {
@ -37,5 +82,4 @@ class MainActivity : VectorBaseActivity() {
startActivity(intent)
finish()
}

}

View File

@ -38,7 +38,7 @@ class EmptyState : MvRxState
class HomeActivityViewModel(state: EmptyState,
private val session: Session,
roomSelectionRepository: RoomSelectionRepository
) : VectorViewModel<EmptyState>(state) {
) : VectorViewModel<EmptyState>(state), Session.Listener {

companion object : MvRxViewModelFactory<HomeActivityViewModel, EmptyState> {

@ -59,6 +59,8 @@ class HomeActivityViewModel(state: EmptyState,
get() = _openRoomLiveData

init {
session.addListener(this)

// TODO Move this else where, it's too late when we are here to change the filter
session.setFilter(FilterService.FilterPreset.RiotFilter)

@ -100,5 +102,18 @@ class HomeActivityViewModel(state: EmptyState,
})
}

override fun onCleared() {
super.onCleared()

session.removeListener(this)
}

/* ==========================================================================================
* Session listener
* ========================================================================================== */

override fun onInvalidToken() {

}

}

View File

@ -53,6 +53,7 @@ import im.vector.riotredesign.core.preference.ProgressBarPreference
import im.vector.riotredesign.core.preference.UserAvatarPreference
import im.vector.riotredesign.core.preference.VectorPreference
import im.vector.riotredesign.core.utils.*
import im.vector.riotredesign.features.MainActivity
import im.vector.riotredesign.features.themes.ThemeUtils
import org.koin.android.ext.android.inject
import java.lang.ref.WeakReference
@ -751,9 +752,8 @@ class VectorSettingsPreferencesFragment : VectorPreferenceFragment(), SharedPref
*/

it.onPreferenceClickListener = Preference.OnPreferenceClickListener {
notImplemented()
// displayLoadingView()
// TODO Matrix.getInstance(appContext).reloadSessions(appContext)
displayLoadingView()
MainActivity.restartApp(activity!!, clearCache = true, clearCredentials = false)
false
}
}

View File

@ -16,9 +16,7 @@

package im.vector.riotredesign.features.workers.signout

import android.content.Intent
import androidx.appcompat.app.AlertDialog
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.session.Session
import im.vector.riotredesign.R
import im.vector.riotredesign.core.platform.VectorBaseActivity
@ -30,7 +28,7 @@ class SignOutUiWorker(val activity: VectorBaseActivity) {
if (SignOutViewModel.doYouNeedToBeDisplayed(session)) {
val signOutDialog = SignOutBottomSheetDialogFragment.newInstance(session.sessionParams.credentials.userId)
signOutDialog.onSignOut = Runnable {
doSignOut(session)
doSignOut()
}
signOutDialog.show(activity.supportFragmentManager, "SO")
} else {
@ -39,31 +37,14 @@ class SignOutUiWorker(val activity: VectorBaseActivity) {
.setTitle(R.string.action_sign_out)
.setMessage(R.string.action_sign_out_confirmation_simple)
.setPositiveButton(R.string.action_sign_out) { _, _ ->
doSignOut(session)
doSignOut()
}
.setNegativeButton(R.string.cancel, null)
.show()
}
}

private fun doSignOut(session: Session) {
// TODO showWaitingView()

session.signOut(object : MatrixCallback<Unit> {

override fun onSuccess(data: Unit) {
// Start MainActivity in a new task
val intent = Intent(activity, MainActivity::class.java)
intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK

activity.startActivity(intent)
}

override fun onFailure(failure: Throwable) {
// TODO Notify user, or ignore?
}

})

private fun doSignOut() {
MainActivity.restartApp(activity, clearCache = true, clearCredentials = true)
}
}