Remove Try from suspending functions

This commit is contained in:
ganfra 2019-08-01 17:15:17 +02:00
parent c300c50093
commit fd09a1224e
101 changed files with 1183 additions and 1329 deletions

View File

@ -28,7 +28,7 @@ internal class MatrixCallbackCompletable<T>(private val completableEmitter: Comp
} }
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
completableEmitter.onError(failure) completableEmitter.tryOnError(failure)
} }
} }

View File

@ -27,7 +27,7 @@ internal class MatrixCallbackSingle<T>(private val singleEmitter: SingleEmitter<
} }
override fun onFailure(failure: Throwable) { override fun onFailure(failure: Throwable) {
singleEmitter.onError(failure) singleEmitter.tryOnError(failure)
} }
} }

View File

@ -41,7 +41,7 @@ class RxRoom(private val room: Room) {
return room.liveTimeLineEvent(eventId).asObservable() return room.liveTimeLineEvent(eventId).asObservable()
} }
fun loadRoomMembersIfNeeded(): Single<Boolean> = Single.create { fun loadRoomMembersIfNeeded(): Single<Unit> = Single.create {
room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it) room.loadRoomMembersIfNeeded(MatrixCallbackSingle(it)).toSingle(it)
} }

View File

@ -126,9 +126,6 @@ dependencies {
// FP // FP
implementation "io.arrow-kt:arrow-core:$arrow_version" implementation "io.arrow-kt:arrow-core:$arrow_version"
implementation "io.arrow-kt:arrow-instances-core:$arrow_version" implementation "io.arrow-kt:arrow-instances-core:$arrow_version"
implementation "io.arrow-kt:arrow-effects:$arrow_version"
implementation "io.arrow-kt:arrow-effects-instances:$arrow_version"
implementation "io.arrow-kt:arrow-integration-retrofit-adapter:$arrow_version"
// olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm // olm lib is now hosted by jitpack: https://jitpack.io/#org.matrix.gitlab.matrix-org/olm
implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2' implementation 'org.matrix.gitlab.matrix-org:olm:3.1.2'

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.session.room.timeline package im.vector.matrix.android.session.room.timeline
import arrow.core.Try
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
@ -24,7 +23,7 @@ import kotlin.random.Random
internal class FakeGetContextOfEventTask constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask { internal class FakeGetContextOfEventTask constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : GetContextOfEventTask {
override suspend fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> { override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result {
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30) val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
val tokenChunkEvent = FakeTokenChunkEvent( val tokenChunkEvent = FakeTokenChunkEvent(
Random.nextLong(System.currentTimeMillis()).toString(), Random.nextLong(System.currentTimeMillis()).toString(),

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.session.room.timeline package im.vector.matrix.android.session.room.timeline
import arrow.core.Try
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
import javax.inject.Inject import javax.inject.Inject
@ -24,7 +23,7 @@ import kotlin.random.Random
internal class FakePaginationTask @Inject constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask { internal class FakePaginationTask @Inject constructor(private val tokenChunkEventPersistor: TokenChunkEventPersistor) : PaginationTask {
override suspend fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> { override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result {
val fakeEvents = RoomDataHelper.createFakeListOfEvents(30) val fakeEvents = RoomDataHelper.createFakeListOfEvents(30)
val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents) val tokenChunkEvent = FakeTokenChunkEvent(params.from, Random.nextLong(System.currentTimeMillis()).toString(), fakeEvents)
return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction) return tokenChunkEventPersistor.insertInDb(tokenChunkEvent, params.roomId, params.direction)

View File

@ -38,4 +38,8 @@ interface MatrixCallback<in T> {
//no-op //no-op
} }
fun onCanceled() {
//no-op
}
} }

View File

@ -30,7 +30,7 @@ interface MembershipService {
* This methods load all room members if it was done yet. * This methods load all room members if it was done yet.
* @return a [Cancelable] * @return a [Cancelable]
*/ */
fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Boolean>): Cancelable fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable
/** /**
* Return the roomMember with userId or null. * Return the roomMember with userId or null.

View File

@ -19,5 +19,6 @@ package im.vector.matrix.android.api.util
class CancelableBag : Cancelable, MutableList<Cancelable> by ArrayList() { class CancelableBag : Cancelable, MutableList<Cancelable> by ArrayList() {
override fun cancel() { override fun cancel() {
forEach { it.cancel() } forEach { it.cancel() }
clear()
} }
} }

View File

@ -68,10 +68,12 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
callback: MatrixCallback<Session>): Cancelable { callback: MatrixCallback<Session>): Cancelable {
val job = GlobalScope.launch(coroutineDispatchers.main) { val job = GlobalScope.launch(coroutineDispatchers.main) {
val sessionOrFailure = authenticate(homeServerConnectionConfig, login, password) val sessionOrFailure = runCatching {
authenticate(homeServerConnectionConfig, login, password)
}
sessionOrFailure.foldToCallback(callback) sessionOrFailure.foldToCallback(callback)
} }
return CancelableCoroutine(job) return CancelableCoroutine(job, callback)
} }
@ -85,16 +87,12 @@ internal class DefaultAuthenticator @Inject constructor(@Unauthenticated
} else { } else {
PasswordLoginParams.userIdentifier(login, password, "Mobile") PasswordLoginParams.userIdentifier(login, password, "Mobile")
} }
executeRequest<Credentials> { val credentials = executeRequest<Credentials> {
apiCall = authAPI.login(loginParams) apiCall = authAPI.login(loginParams)
}.map {
val sessionParams = SessionParams(it, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)
sessionParams
}.map {
sessionManager.getOrCreateSession(it)
} }
val sessionParams = SessionParams(credentials, homeServerConnectionConfig)
sessionParamsStore.save(sessionParams)
sessionManager.getOrCreateSession(sessionParams)
} }
private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI { private fun buildAuthAPI(homeServerConnectionConfig: HomeServerConnectionConfig): AuthAPI {

View File

@ -254,35 +254,36 @@ internal class CryptoManager @Inject constructor(
private suspend fun internalStart(isInitialSync: Boolean) { private suspend fun internalStart(isInitialSync: Boolean) {
// Open the store // Open the store
cryptoStore.open() cryptoStore.open()
uploadDeviceKeys() runCatching {
.flatMap { oneTimeKeysUploader.maybeUploadOneTimeKeys() } uploadDeviceKeys()
.fold( oneTimeKeysUploader.maybeUploadOneTimeKeys()
{ outgoingRoomKeyRequestManager.start()
Timber.e("Start failed: $it") keysBackup.checkAndStartKeysBackup()
delay(1000) if (isInitialSync) {
isStarting.set(false) // refresh the devices list for each known room members
internalStart(isInitialSync) deviceListManager.invalidateAllDeviceLists()
}, deviceListManager.refreshOutdatedDeviceLists()
{ } else {
outgoingRoomKeyRequestManager.start() incomingRoomKeyRequestManager.processReceivedRoomKeyRequests()
keysBackup.checkAndStartKeysBackup() }
if (isInitialSync) { }.fold(
// refresh the devices list for each known room members {
deviceListManager.invalidateAllDeviceLists() isStarting.set(false)
deviceListManager.refreshOutdatedDeviceLists() isStarted.set(true)
} else { },
incomingRoomKeyRequestManager.processReceivedRoomKeyRequests() {
} Timber.e("Start failed: $it")
isStarting.set(false) delay(1000)
isStarted.set(true) isStarting.set(false)
} internalStart(isInitialSync)
) }
)
} }
/** /**
* Close the crypto * Close the crypto
*/ */
fun close() { fun close() = runBlocking(coroutineDispatchers.crypto){
olmDevice.release() olmDevice.release()
cryptoStore.close() cryptoStore.close()
outgoingRoomKeyRequestManager.stop() outgoingRoomKeyRequestManager.stop()
@ -556,13 +557,16 @@ internal class CryptoManager @Inject constructor(
if (safeAlgorithm != null) { if (safeAlgorithm != null) {
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()
Timber.v("## encryptEventContent() starts") Timber.v("## encryptEventContent() starts")
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds) runCatching {
safeAlgorithm.encryptEventContent(eventContent, eventType, userIds)
}
.fold( .fold(
{ callback.onFailure(it) },
{ {
Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms") Timber.v("## encryptEventContent() : succeeds after ${System.currentTimeMillis() - t0} ms")
callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED)) callback.onSuccess(MXEncryptEventContentResult(it, EventType.ENCRYPTED))
} },
{ callback.onFailure(it) }
) )
} else { } else {
val algorithm = getEncryptionAlgorithm(roomId) val algorithm = getEncryptionAlgorithm(roomId)
@ -584,10 +588,7 @@ internal class CryptoManager @Inject constructor(
@Throws(MXCryptoError::class) @Throws(MXCryptoError::class)
override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult { override fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return runBlocking { return runBlocking {
internalDecryptEvent(event, timeline).fold( internalDecryptEvent(event, timeline)
{ throw it },
{ it }
)
} }
} }
@ -600,8 +601,10 @@ internal class CryptoManager @Inject constructor(
*/ */
override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) { override fun decryptEventAsync(event: Event, timeline: String, callback: MatrixCallback<MXEventDecryptionResult>) {
GlobalScope.launch { GlobalScope.launch {
val result = withContext(coroutineDispatchers.crypto) { val result = runCatching {
internalDecryptEvent(event, timeline) withContext(coroutineDispatchers.crypto) {
internalDecryptEvent(event, timeline)
}
} }
result.foldToCallback(callback) result.foldToCallback(callback)
} }
@ -612,22 +615,22 @@ internal class CryptoManager @Inject constructor(
* *
* @param event the raw event. * @param event the raw event.
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the MXEventDecryptionResult data, or null in case of error wrapped into [Try] * @return the MXEventDecryptionResult data, or null in case of error
*/ */
private suspend fun internalDecryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> { private suspend fun internalDecryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
val eventContent = event.content val eventContent = event.content
return if (eventContent == null) { if (eventContent == null) {
Timber.e("## decryptEvent : empty event content") Timber.e("## decryptEvent : empty event content")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)) throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
} else { } else {
val algorithm = eventContent["algorithm"]?.toString() val algorithm = eventContent["algorithm"]?.toString()
val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm) val alg = roomDecryptorProvider.getOrCreateRoomDecryptor(event.roomId, algorithm)
if (alg == null) { if (alg == null) {
val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm) val reason = String.format(MXCryptoError.UNABLE_TO_DECRYPT_REASON, event.eventId, algorithm)
Timber.e("## decryptEvent() : $reason") Timber.e("## decryptEvent() : $reason")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)) throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, reason)
} else { } else {
alg.decryptEvent(event, timeline) return alg.decryptEvent(event, timeline)
} }
} }
} }
@ -689,12 +692,13 @@ internal class CryptoManager @Inject constructor(
private fun onRoomEncryptionEvent(roomId: String, event: Event) { private fun onRoomEncryptionEvent(roomId: String, event: Event) {
GlobalScope.launch(coroutineDispatchers.crypto) { GlobalScope.launch(coroutineDispatchers.crypto) {
val params = LoadRoomMembersTask.Params(roomId) val params = LoadRoomMembersTask.Params(roomId)
loadRoomMembersTask try {
.execute(params) loadRoomMembersTask.execute(params)
.map { _ -> val userIds = getRoomUserIds(roomId)
val userIds = getRoomUserIds(roomId) setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds)
setEncryptionInRoom(roomId, event.content?.get("algorithm")?.toString(), true, userIds) } catch (throwable: Throwable) {
} Timber.e(throwable)
}
} }
} }
@ -761,7 +765,7 @@ internal class CryptoManager @Inject constructor(
/** /**
* Upload my user's device keys. * Upload my user's device keys.
*/ */
private suspend fun uploadDeviceKeys(): Try<KeysUploadResponse> { private suspend fun uploadDeviceKeys(): KeysUploadResponse {
// Prepare the device keys data to send // Prepare the device keys data to send
// Sign it // Sign it
val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary()) val canonicalJson = JsonCanonicalizer.getCanonicalJson(Map::class.java, getMyDevice().signalableJSONDictionary())
@ -868,10 +872,8 @@ internal class CryptoManager @Inject constructor(
fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) { fun checkUnknownDevices(userIds: List<String>, callback: MatrixCallback<Unit>) {
// force the refresh to ensure that the devices list is up-to-date // force the refresh to ensure that the devices list is up-to-date
GlobalScope.launch(coroutineDispatchers.crypto) { GlobalScope.launch(coroutineDispatchers.crypto) {
deviceListManager runCatching { deviceListManager.downloadKeys(userIds, true) }
.downloadKeys(userIds, true)
.fold( .fold(
{ callback.onFailure(it) },
{ {
val unknownDevices = getUnknownDevices(it) val unknownDevices = getUnknownDevices(it)
if (unknownDevices.map.isEmpty()) { if (unknownDevices.map.isEmpty()) {
@ -880,7 +882,8 @@ internal class CryptoManager @Inject constructor(
// trigger an an unknown devices exception // trigger an an unknown devices exception
callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices))) callback.onFailure(Failure.CryptoError(MXCryptoError.UnknownDevice(unknownDevices)))
} }
} },
{ callback.onFailure(it) }
) )
} }
} }
@ -1035,9 +1038,9 @@ internal class CryptoManager @Inject constructor(
override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) { override fun downloadKeys(userIds: List<String>, forceDownload: Boolean, callback: MatrixCallback<MXUsersDevicesMap<MXDeviceInfo>>) {
GlobalScope.launch(coroutineDispatchers.crypto) { GlobalScope.launch(coroutineDispatchers.crypto) {
deviceListManager runCatching {
.downloadKeys(userIds, forceDownload) deviceListManager.downloadKeys(userIds, forceDownload)
.foldToCallback(callback) }.foldToCallback(callback)
} }
} }

View File

@ -18,14 +18,12 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.MatrixPatterns import im.vector.matrix.android.api.MatrixPatterns
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore import im.vector.matrix.android.internal.crypto.store.IMXCryptoStore
import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask import im.vector.matrix.android.internal.crypto.tasks.DownloadKeysForUsersTask
import im.vector.matrix.android.internal.extensions.onError
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import timber.log.Timber import timber.log.Timber
@ -237,7 +235,7 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* @param forceDownload Always download the keys even if cached. * @param forceDownload Always download the keys even if cached.
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): Try<MXUsersDevicesMap<MXDeviceInfo>> { suspend fun downloadKeys(userIds: List<String>?, forceDownload: Boolean): MXUsersDevicesMap<MXDeviceInfo> {
Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds") Timber.v("## downloadKeys() : forceDownload $forceDownload : $userIds")
// Map from userId -> deviceId -> MXDeviceInfo // Map from userId -> deviceId -> MXDeviceInfo
val stored = MXUsersDevicesMap<MXDeviceInfo>() val stored = MXUsersDevicesMap<MXDeviceInfo>()
@ -268,16 +266,15 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
} }
return if (downloadUsers.isEmpty()) { return if (downloadUsers.isEmpty()) {
Timber.v("## downloadKeys() : no new user device") Timber.v("## downloadKeys() : no new user device")
Try.just(stored) stored
} else { } else {
Timber.v("## downloadKeys() : starts") Timber.v("## downloadKeys() : starts")
val t0 = System.currentTimeMillis() val t0 = System.currentTimeMillis()
doKeyDownloadForUsers(downloadUsers) val result = doKeyDownloadForUsers(downloadUsers)
.map { Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms")
Timber.v("## downloadKeys() : doKeyDownloadForUsers succeeds after " + (System.currentTimeMillis() - t0) + " ms") result.also {
it.addEntriesFromMap(stored) it.addEntriesFromMap(stored)
it }
}
} }
} }
@ -286,60 +283,60 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
* *
* @param downloadUsers the user ids list * @param downloadUsers the user ids list
*/ */
private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): Try<MXUsersDevicesMap<MXDeviceInfo>> { private suspend fun doKeyDownloadForUsers(downloadUsers: MutableList<String>): MXUsersDevicesMap<MXDeviceInfo> {
Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers") Timber.v("## doKeyDownloadForUsers() : doKeyDownloadForUsers $downloadUsers")
// get the user ids which did not already trigger a keys download // get the user ids which did not already trigger a keys download
val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) } val filteredUsers = downloadUsers.filter { MatrixPatterns.isUserId(it) }
if (filteredUsers.isEmpty()) { if (filteredUsers.isEmpty()) {
// trigger nothing // trigger nothing
return Try.just(MXUsersDevicesMap()) return MXUsersDevicesMap()
} }
val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken()) val params = DownloadKeysForUsersTask.Params(filteredUsers, syncTokenStore.getLastToken())
return downloadKeysForUsersTask.execute(params) val response = try {
.map { response -> downloadKeysForUsersTask.execute(params)
Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users") } catch (throwable: Throwable) {
for (userId in filteredUsers) { Timber.e(throwable, "##doKeyDownloadForUsers(): error")
val devices = response.deviceKeys?.get(userId) onKeysDownloadFailed(filteredUsers)
Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices") throw throwable
if (devices != null) { }
val mutableDevices = HashMap(devices) Timber.v("## doKeyDownloadForUsers() : Got keys for " + filteredUsers.size + " users")
val deviceIds = ArrayList(mutableDevices.keys) for (userId in filteredUsers) {
for (deviceId in deviceIds) { val devices = response.deviceKeys?.get(userId)
// Get the potential previously store device keys for this device Timber.v("## doKeyDownloadForUsers() : Got keys for $userId : $devices")
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId) if (devices != null) {
val deviceInfo = mutableDevices[deviceId] val mutableDevices = HashMap(devices)
val deviceIds = ArrayList(mutableDevices.keys)
for (deviceId in deviceIds) {
// Get the potential previously store device keys for this device
val previouslyStoredDeviceKeys = cryptoStore.getUserDevice(deviceId, userId)
val deviceInfo = mutableDevices[deviceId]
// in some race conditions (like unit tests) // in some race conditions (like unit tests)
// the self device must be seen as verified // the self device must be seen as verified
if (TextUtils.equals(deviceInfo!!.deviceId, credentials.deviceId) && TextUtils.equals(userId, credentials.userId)) { if (TextUtils.equals(deviceInfo!!.deviceId, credentials.deviceId) && TextUtils.equals(userId, credentials.userId)) {
deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED deviceInfo.verified = MXDeviceInfo.DEVICE_VERIFICATION_VERIFIED
} }
// Validate received keys // Validate received keys
if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) { if (!validateDeviceKeys(deviceInfo, userId, deviceId, previouslyStoredDeviceKeys)) {
// New device keys are not valid. Do not store them // New device keys are not valid. Do not store them
mutableDevices.remove(deviceId) mutableDevices.remove(deviceId)
if (null != previouslyStoredDeviceKeys) { if (null != previouslyStoredDeviceKeys) {
// But keep old validated ones if any // But keep old validated ones if any
mutableDevices[deviceId] = previouslyStoredDeviceKeys mutableDevices[deviceId] = previouslyStoredDeviceKeys
} }
} else if (null != previouslyStoredDeviceKeys) { } else if (null != previouslyStoredDeviceKeys) {
// The verified status is not sync'ed with hs. // The verified status is not sync'ed with hs.
// This is a client side information, valid only for this client. // This is a client side information, valid only for this client.
// So, transfer its previous value // So, transfer its previous value
mutableDevices[deviceId]!!.verified = previouslyStoredDeviceKeys.verified mutableDevices[deviceId]!!.verified = previouslyStoredDeviceKeys.verified
}
}
// Update the store
// Note that devices which aren't in the response will be removed from the stores
cryptoStore.storeUserDevices(userId, mutableDevices)
}
} }
onKeysDownloadSucceed(filteredUsers, response.failures)
}
.onError {
Timber.e(it, "##doKeyDownloadForUsers(): error")
onKeysDownloadFailed(filteredUsers)
} }
// Update the store
// Note that devices which aren't in the response will be removed from the stores
cryptoStore.storeUserDevices(userId, mutableDevices)
}
}
return onKeysDownloadSucceed(filteredUsers, response.failures)
} }
/** /**
@ -465,15 +462,16 @@ internal class DeviceListManager @Inject constructor(private val cryptoStore: IM
} }
cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses) cryptoStore.saveDeviceTrackingStatuses(deviceTrackingStatuses)
doKeyDownloadForUsers(users) runCatching {
.fold( doKeyDownloadForUsers(users)
{ }.fold(
Timber.e(it, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users") {
}, Timber.v("## refreshOutdatedDeviceLists() : done")
{ },
Timber.v("## refreshOutdatedDeviceLists() : done") {
} Timber.e(it, "## refreshOutdatedDeviceLists() : ERROR updating device keys for users $users")
) }
)
} }
companion object { companion object {

View File

@ -18,7 +18,6 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE import im.vector.matrix.android.api.util.JSON_DICT_PARAMETERIZED_TYPE
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
@ -506,25 +505,25 @@ internal class MXOlmDevice @Inject constructor(
keysClaimed: Map<String, String>, keysClaimed: Map<String, String>,
exportFormat: Boolean): Boolean { exportFormat: Boolean): Boolean {
val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat) val session = OlmInboundGroupSessionWrapper(sessionKey, exportFormat)
runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
.fold(
{
// If we already have this session, consider updating it
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
getInboundGroupSession(sessionId, senderKey, roomId).fold( val existingFirstKnown = it.firstKnownIndex!!
{ val newKnownFirstIndex = session.firstKnownIndex
// Nothing to do in case of error
},
{
// If we already have this session, consider updating it
Timber.e("## addInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
val existingFirstKnown = it.firstKnownIndex!! //If our existing session is better we keep it
val newKnownFirstIndex = session.firstKnownIndex if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) {
session.olmInboundGroupSession?.releaseSession()
//If our existing session is better we keep it return false
if (newKnownFirstIndex != null && existingFirstKnown <= newKnownFirstIndex) { }
session.olmInboundGroupSession?.releaseSession() },
return false {
} // Nothing to do in case of error
} }
) )
// sanity check // sanity check
if (null == session.olmInboundGroupSession) { if (null == session.olmInboundGroupSession) {
@ -595,12 +594,8 @@ internal class MXOlmDevice @Inject constructor(
continue continue
} }
getInboundGroupSession(sessionId, senderKey, roomId) runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }
.fold( .fold(
{
// Session does not already exist, add it
sessions.add(session)
},
{ {
// If we already have this session, consider updating it // If we already have this session, consider updating it
Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId") Timber.e("## importInboundGroupSession() : Update for megolm session $senderKey/$sessionId")
@ -613,7 +608,12 @@ internal class MXOlmDevice @Inject constructor(
sessions.add(session) sessions.add(session)
} }
Unit Unit
},
{
// Session does not already exist, add it
sessions.add(session)
} }
) )
} }
@ -648,61 +648,57 @@ internal class MXOlmDevice @Inject constructor(
roomId: String, roomId: String,
timeline: String?, timeline: String?,
sessionId: String, sessionId: String,
senderKey: String): Try<OlmDecryptionResult> { senderKey: String): OlmDecryptionResult {
return getInboundGroupSession(sessionId, senderKey, roomId) val session = getInboundGroupSession(sessionId, senderKey, roomId)
.flatMap { session -> // Check that the room id matches the original one for the session. This stops
// Check that the room id matches the original one for the session. This stops // the HS pretending a message was targeting a different room.
// the HS pretending a message was targeting a different room. if (roomId == session.roomId) {
if (roomId == session.roomId) { var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null
var decryptResult: OlmInboundGroupSession.DecryptMessageResult? = null try {
try { decryptResult = session.olmInboundGroupSession!!.decryptMessage(body)
decryptResult = session.olmInboundGroupSession!!.decryptMessage(body) } catch (e: OlmException) {
} catch (e: OlmException) { Timber.e(e, "## decryptGroupMessage () : decryptMessage failed")
Timber.e(e, "## decryptGroupMessage () : decryptMessage failed") throw MXCryptoError.OlmError(e)
return@flatMap Try.Failure(MXCryptoError.OlmError(e)) }
}
if (null != timeline) { if (null != timeline) {
if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) { if (!inboundGroupSessionMessageIndexes.containsKey(timeline)) {
inboundGroupSessionMessageIndexes[timeline] = HashMap() inboundGroupSessionMessageIndexes[timeline] = HashMap()
}
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.e("## decryptGroupMessage() : $reason")
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason))
}
inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
}
store.storeInboundGroupSessions(listOf(session))
val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
adapter.fromJson(payloadString)
} catch (e: Exception) {
Timber.e("## decryptGroupMessage() : fails to parse the payload")
return@flatMap Try.Failure(
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
}
return@flatMap Try.just(
OlmDecryptionResult(
payload,
session.keysClaimed,
senderKey,
session.forwardingCurve25519KeyChain
)
)
} else {
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## decryptGroupMessage() : $reason")
return@flatMap Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason))
}
} }
val messageIndexKey = senderKey + "|" + sessionId + "|" + decryptResult.mIndex
if (inboundGroupSessionMessageIndexes[timeline]?.get(messageIndexKey) != null) {
val reason = String.format(MXCryptoError.DUPLICATE_MESSAGE_INDEX_REASON, decryptResult.mIndex)
Timber.e("## decryptGroupMessage() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.DUPLICATED_MESSAGE_INDEX, reason)
}
inboundGroupSessionMessageIndexes[timeline]!!.put(messageIndexKey, true)
}
store.storeInboundGroupSessions(listOf(session))
val payload = try {
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
val payloadString = convertFromUTF8(decryptResult.mDecryptedMessage)
adapter.fromJson(payloadString)
} catch (e: Exception) {
Timber.e("## decryptGroupMessage() : fails to parse the payload")
throw
MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
}
return OlmDecryptionResult(
payload,
session.keysClaimed,
senderKey,
session.forwardingCurve25519KeyChain
)
} else {
val reason = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## decryptGroupMessage() : $reason")
throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, reason)
}
} }
/** /**
@ -766,26 +762,26 @@ internal class MXOlmDevice @Inject constructor(
* @param senderKey the base64-encoded curve25519 key of the sender. * @param senderKey the base64-encoded curve25519 key of the sender.
* @return the inbound group session. * @return the inbound group session.
*/ */
fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): Try<OlmInboundGroupSessionWrapper> { fun getInboundGroupSession(sessionId: String?, senderKey: String?, roomId: String?): OlmInboundGroupSessionWrapper {
if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) { if (sessionId.isNullOrBlank() || senderKey.isNullOrBlank()) {
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)) throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, MXCryptoError.ERROR_MISSING_PROPERTY_REASON)
} }
val session = store.getInboundGroupSession(sessionId, senderKey) val session = store.getInboundGroupSession(sessionId, senderKey)
return if (null != session) { if (session != null) {
// Check that the room id matches the original one for the session. This stops // Check that the room id matches the original one for the session. This stops
// the HS pretending a message was targeting a different room. // the HS pretending a message was targeting a different room.
if (!TextUtils.equals(roomId, session.roomId)) { if (!TextUtils.equals(roomId, session.roomId)) {
val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId) val errorDescription = String.format(MXCryptoError.INBOUND_SESSION_MISMATCH_ROOM_ID_REASON, roomId, session.roomId)
Timber.e("## getInboundGroupSession() : $errorDescription") Timber.e("## getInboundGroupSession() : $errorDescription")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription)) throw MXCryptoError.Base(MXCryptoError.ErrorType.INBOUND_SESSION_MISMATCH_ROOM_ID, errorDescription)
} else { } else {
Try.just(session) return session
} }
} else { } else {
Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId") Timber.e("## getInboundGroupSession() : Cannot retrieve inbound group session $sessionId")
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)) throw MXCryptoError.Base(MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID, MXCryptoError.UNKNOWN_INBOUND_SESSION_ID_REASON)
} }
} }
@ -798,6 +794,6 @@ internal class MXOlmDevice @Inject constructor(
* @return true if the unbound session keys are known. * @return true if the unbound session keys are known.
*/ */
fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean { fun hasInboundSessionKeys(roomId: String, senderKey: String, sessionId: String): Boolean {
return getInboundGroupSession(sessionId, senderKey, roomId).isSuccess() return runCatching { getInboundGroupSession(sessionId, senderKey, roomId) }.isSuccess
} }
} }

View File

@ -16,8 +16,6 @@
package im.vector.matrix.android.internal.crypto package im.vector.matrix.android.internal.crypto
import arrow.core.Try
import arrow.instances.`try`.applicativeError.handleError
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.model.MXKey
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
@ -59,13 +57,13 @@ internal class OneTimeKeysUploader @Inject constructor(
/** /**
* Check if the OTK must be uploaded. * Check if the OTK must be uploaded.
*/ */
suspend fun maybeUploadOneTimeKeys(): Try<Unit> { suspend fun maybeUploadOneTimeKeys() {
if (oneTimeKeyCheckInProgress) { if (oneTimeKeyCheckInProgress) {
return Try.just(Unit) return
} }
if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) { if (System.currentTimeMillis() - lastOneTimeKeyCheck < ONE_TIME_KEY_UPLOAD_PERIOD) {
// we've done a key upload recently. // we've done a key upload recently.
return Try.just(Unit) return
} }
lastOneTimeKeyCheck = System.currentTimeMillis() lastOneTimeKeyCheck = System.currentTimeMillis()
@ -81,41 +79,31 @@ internal class OneTimeKeysUploader @Inject constructor(
// discard the oldest private keys first. This will eventually clean // discard the oldest private keys first. This will eventually clean
// out stale private keys that won't receive a message. // out stale private keys that won't receive a message.
val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt() val keyLimit = Math.floor(maxOneTimeKeys / 2.0).toInt()
val result = if (oneTimeKeyCount != null) { if (oneTimeKeyCount != null) {
uploadOTK(oneTimeKeyCount!!, keyLimit) uploadOTK(oneTimeKeyCount!!, keyLimit)
} else { } else {
// ask the server how many keys we have // ask the server how many keys we have
val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!) val uploadKeysParams = UploadKeysTask.Params(null, null, credentials.deviceId!!)
uploadKeysTask.execute(uploadKeysParams) val response = uploadKeysTask.execute(uploadKeysParams)
.flatMap { // We need to keep a pool of one time public keys on the server so that
// We need to keep a pool of one time public keys on the server so that // other devices can start conversations with us. But we can only store
// other devices can start conversations with us. But we can only store // a finite number of private keys in the olm Account object.
// a finite number of private keys in the olm Account object. // To complicate things further then can be a delay between a device
// To complicate things further then can be a delay between a device // claiming a public one time key from the server and it sending us a
// claiming a public one time key from the server and it sending us a // message. We need to keep the corresponding private key locally until
// message. We need to keep the corresponding private key locally until // we receive the message.
// we receive the message. // But that message might never arrive leaving us stuck with duff
// But that message might never arrive leaving us stuck with duff // private keys clogging up our local storage.
// private keys clogging up our local storage. // So we need some kind of engineering compromise to balance all of
// So we need some kind of engineering compromise to balance all of // these factors.
// these factors. // TODO Why we do not set oneTimeKeyCount here?
// TODO Why we do not set oneTimeKeyCount here? // TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also)
// TODO This is not needed anymore, see https://github.com/matrix-org/matrix-js-sdk/pull/493 (TODO on iOS also) val keyCount = response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)
val keyCount = it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE) uploadOTK(keyCount, keyLimit)
uploadOTK(keyCount, keyLimit)
}
} }
return result Timber.v("## uploadKeys() : success")
.map { oneTimeKeyCount = null
Timber.v("## uploadKeys() : success") oneTimeKeyCheckInProgress = false
oneTimeKeyCount = null
oneTimeKeyCheckInProgress = false
}
.handleError {
Timber.e(it, "## uploadKeys() : failed")
oneTimeKeyCount = null
oneTimeKeyCheckInProgress = false
}
} }
/** /**
@ -124,29 +112,26 @@ internal class OneTimeKeysUploader @Inject constructor(
* @param keyCount the key count * @param keyCount the key count
* @param keyLimit the limit * @param keyLimit the limit
*/ */
private suspend fun uploadOTK(keyCount: Int, keyLimit: Int): Try<Unit> { private suspend fun uploadOTK(keyCount: Int, keyLimit: Int) {
if (keyLimit <= keyCount) { if (keyLimit <= keyCount) {
// If we don't need to generate any more keys then we are done. // If we don't need to generate any more keys then we are done.
return Try.just(Unit) return
} }
val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER) val keysThisLoop = Math.min(keyLimit - keyCount, ONE_TIME_KEY_GENERATION_MAX_NUMBER)
olmDevice.generateOneTimeKeys(keysThisLoop) olmDevice.generateOneTimeKeys(keysThisLoop)
return uploadOneTimeKeys() val response = uploadOneTimeKeys()
.flatMap { if (response.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) {
if (it.hasOneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE)) { uploadOTK(response.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit)
uploadOTK(it.oneTimeKeyCountsForAlgorithm(MXKey.KEY_SIGNED_CURVE_25519_TYPE), keyLimit) } else {
} else { Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519")
Timber.e("## uploadLoop() : response for uploading keys does not contain one_time_key_counts.signed_curve25519") throw Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")
Try.raise(Exception("response for uploading keys does not contain one_time_key_counts.signed_curve25519")) }
}
}
} }
/** /**
* Upload my user's one time keys. * Upload my user's one time keys.
*/ */
private suspend fun uploadOneTimeKeys(): Try<KeysUploadResponse> { private suspend fun uploadOneTimeKeys(): KeysUploadResponse {
val oneTimeKeys = olmDevice.getOneTimeKeys() val oneTimeKeys = olmDevice.getOneTimeKeys()
val oneTimeJson = HashMap<String, Any>() val oneTimeJson = HashMap<String, Any>()
@ -169,13 +154,10 @@ internal class OneTimeKeysUploader @Inject constructor(
// For now, we set the device id explicitly, as we may not be using the // For now, we set the device id explicitly, as we may not be using the
// same one as used in login. // same one as used in login.
val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!) val uploadParams = UploadKeysTask.Params(null, oneTimeJson, credentials.deviceId!!)
return uploadKeysTask val response = uploadKeysTask.execute(uploadParams)
.execute(uploadParams) lastPublishedOneTimeKeys = oneTimeKeys
.map { olmDevice.markKeysAsPublished()
lastPublishedOneTimeKeys = oneTimeKeys return response
olmDevice.markKeysAsPublished()
it
}
} }
companion object { companion object {

View File

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.crypto.actions package im.vector.matrix.android.internal.crypto.actions
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.MXOlmDevice import im.vector.matrix.android.internal.crypto.MXOlmDevice
import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo import im.vector.matrix.android.internal.crypto.model.MXDeviceInfo
import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.model.MXKey
@ -32,7 +31,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) { private val oneTimeKeysForUsersDeviceTask: ClaimOneTimeKeysForUsersDeviceTask) {
suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): Try<MXUsersDevicesMap<MXOlmSessionResult>> { suspend fun handle(devicesByUser: Map<String, List<MXDeviceInfo>>): MXUsersDevicesMap<MXOlmSessionResult> {
val devicesWithoutSession = ArrayList<MXDeviceInfo>() val devicesWithoutSession = ArrayList<MXDeviceInfo>()
val results = MXUsersDevicesMap<MXOlmSessionResult>() val results = MXUsersDevicesMap<MXOlmSessionResult>()
@ -58,7 +57,7 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
} }
if (devicesWithoutSession.size == 0) { if (devicesWithoutSession.size == 0) {
return Try.just(results) return results
} }
// Prepare the request for claiming one-time keys // Prepare the request for claiming one-time keys
@ -79,39 +78,36 @@ internal class EnsureOlmSessionsForDevicesAction @Inject constructor(private val
Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim") Timber.v("## claimOneTimeKeysForUsersDevices() : $usersDevicesToClaim")
val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim) val claimParams = ClaimOneTimeKeysForUsersDeviceTask.Params(usersDevicesToClaim)
return oneTimeKeysForUsersDeviceTask val oneTimeKeys = oneTimeKeysForUsersDeviceTask.execute(claimParams)
.execute(claimParams) Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $oneTimeKeys")
.map { for (userId in userIds) {
Timber.v("## claimOneTimeKeysForUsersDevices() : keysClaimResponse.oneTimeKeys: $it") val deviceInfos = devicesByUser[userId]
for (userId in userIds) { for (deviceInfo in deviceInfos!!) {
val deviceInfos = devicesByUser[userId] var oneTimeKey: MXKey? = null
for (deviceInfo in deviceInfos!!) { val deviceIds = oneTimeKeys.getUserDeviceIds(userId)
var oneTimeKey: MXKey? = null if (null != deviceIds) {
val deviceIds = it.getUserDeviceIds(userId) for (deviceId in deviceIds) {
if (null != deviceIds) { val olmSessionResult = results.getObject(userId, deviceId)
for (deviceId in deviceIds) { if (olmSessionResult!!.sessionId != null) {
val olmSessionResult = results.getObject(userId, deviceId) // We already have a result for this device
if (olmSessionResult!!.sessionId != null) { continue
// We already have a result for this device
continue
}
val key = it.getObject(userId, deviceId)
if (key?.type == oneTimeKeyAlgorithm) {
oneTimeKey = key
}
if (oneTimeKey == null) {
Timber.v("## ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm
+ " for device " + userId + " : " + deviceId)
continue
}
// Update the result for this device in results
olmSessionResult.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
}
}
} }
val key = oneTimeKeys.getObject(userId, deviceId)
if (key?.type == oneTimeKeyAlgorithm) {
oneTimeKey = key
}
if (oneTimeKey == null) {
Timber.v("## ensureOlmSessionsForDevices() : No one-time keys " + oneTimeKeyAlgorithm
+ " for device " + userId + " : " + deviceId)
continue
}
// Update the result for this device in results
olmSessionResult.sessionId = verifyKeyAndStartSession(oneTimeKey, userId, deviceInfo)
} }
results
} }
}
}
return results
} }
private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? { private fun verifyKeyAndStartSession(oneTimeKey: MXKey, userId: String, deviceInfo: MXDeviceInfo): String? {

View File

@ -37,7 +37,7 @@ internal class EnsureOlmSessionsForUsersAction @Inject constructor(private val o
* Try to make sure we have established olm sessions for the given users. * Try to make sure we have established olm sessions for the given users.
* @param users a list of user ids. * @param users a list of user ids.
*/ */
suspend fun handle(users: List<String>) : Try<MXUsersDevicesMap<MXOlmSessionResult>> { suspend fun handle(users: List<String>) : MXUsersDevicesMap<MXOlmSessionResult> {
Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users") Timber.v("## ensureOlmSessionsForUsers() : ensureOlmSessionsForUsers $users")
val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>() val devicesByUser = HashMap<String /* userId */, MutableList<MXDeviceInfo>>()

View File

@ -35,7 +35,7 @@ internal interface IMXDecrypting {
* @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack. * @param timeline the id of the timeline where the event is decrypted. It is used to prevent replay attack.
* @return the decryption information, or an error * @return the decryption information, or an error
*/ */
suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult
/** /**
* Handle a key event. * Handle a key event.

View File

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.crypto.algorithms package im.vector.matrix.android.internal.crypto.algorithms
import arrow.core.Try
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
/** /**
@ -31,7 +30,7 @@ internal interface IMXEncrypting {
* @param eventContent the content of the event. * @param eventContent the content of the event.
* @param eventType the type of the event. * @param eventType the type of the event.
* @param userIds the room members the event will be sent to. * @param userIds the room members the event will be sent to.
* @return the encrypted content wrapped by [Try] * @return the encrypted content
*/ */
suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Try<Content> suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content
} }

View File

@ -18,7 +18,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm package im.vector.matrix.android.internal.crypto.algorithms.megolm
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
@ -40,7 +39,6 @@ import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import timber.log.Timber import timber.log.Timber
import kotlin.collections.HashMap
internal class MXMegolmDecryption(private val credentials: Credentials, internal class MXMegolmDecryption(private val credentials: Credentials,
private val olmDevice: MXOlmDevice, private val olmDevice: MXOlmDevice,
@ -61,30 +59,46 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
*/ */
private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap() private var pendingEvents: MutableMap<String /* senderKey|sessionId */, MutableMap<String /* timelineId */, MutableList<Event>>> = HashMap()
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> { override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
return decryptEvent(event, timeline, true) return decryptEvent(event, timeline, true)
} }
private fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): Try<MXEventDecryptionResult> { private suspend fun decryptEvent(event: Event, timeline: String, requestKeysOnFail: Boolean): MXEventDecryptionResult {
if (event.roomId.isNullOrBlank()) { if (event.roomId.isNullOrBlank()) {
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
} }
val encryptedEventContent = event.content.toModel<EncryptedEventContent>() val encryptedEventContent = event.content.toModel<EncryptedEventContent>()
?: return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) ?: throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
if (encryptedEventContent.senderKey.isNullOrBlank() if (encryptedEventContent.senderKey.isNullOrBlank()
|| encryptedEventContent.sessionId.isNullOrBlank() || encryptedEventContent.sessionId.isNullOrBlank()
|| encryptedEventContent.ciphertext.isNullOrBlank()) { || encryptedEventContent.ciphertext.isNullOrBlank()) {
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)) throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
} }
return olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext, return runCatching {
event.roomId, olmDevice.decryptGroupMessage(encryptedEventContent.ciphertext,
timeline, event.roomId,
encryptedEventContent.sessionId, timeline,
encryptedEventContent.senderKey) encryptedEventContent.sessionId,
encryptedEventContent.senderKey)
}
.fold( .fold(
{ olmDecryptionResult ->
// the decryption succeeds
if (olmDecryptionResult.payload != null) {
MXEventDecryptionResult(
clearEvent = olmDecryptionResult.payload,
senderCurve25519Key = olmDecryptionResult.senderKey,
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain
?: emptyList()
)
} else {
throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON)
}
},
{ throwable -> { throwable ->
if (throwable is MXCryptoError.OlmError) { if (throwable is MXCryptoError.OlmError) {
// TODO Check the value of .message // TODO Check the value of .message
@ -98,10 +112,10 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message) val reason = String.format(MXCryptoError.OLM_REASON, throwable.olmException.message)
val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason) val detailedReason = String.format(MXCryptoError.DETAILED_OLM_REASON, encryptedEventContent.ciphertext, reason)
Try.Failure(MXCryptoError.Base( throw MXCryptoError.Base(
MXCryptoError.ErrorType.OLM, MXCryptoError.ErrorType.OLM,
reason, reason,
detailedReason)) detailedReason)
} }
if (throwable is MXCryptoError.Base) { if (throwable is MXCryptoError.Base) {
if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) { if (throwable.errorType == MXCryptoError.ErrorType.UNKNOWN_INBOUND_SESSION_ID) {
@ -111,23 +125,7 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
} }
} }
} }
throw throwable
Try.Failure(throwable)
},
{ olmDecryptionResult ->
// the decryption succeeds
if (olmDecryptionResult.payload != null) {
Try.just(
MXEventDecryptionResult(
clearEvent = olmDecryptionResult.payload,
senderCurve25519Key = olmDecryptionResult.senderKey,
claimedEd25519Key = olmDecryptionResult.keysClaimed?.get("ed25519"),
forwardingCurve25519KeyChain = olmDecryptionResult.forwardingCurve25519KeyChain ?: emptyList()
)
)
} else {
Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_FIELDS, MXCryptoError.MISSING_FIELDS_REASON))
}
} }
) )
} }
@ -311,51 +309,48 @@ internal class MXMegolmDecryption(private val credentials: Credentials,
} }
val userId = request.userId ?: return val userId = request.userId ?: return
GlobalScope.launch(coroutineDispatchers.crypto) { GlobalScope.launch(coroutineDispatchers.crypto) {
deviceListManager runCatching { deviceListManager.downloadKeys(listOf(userId), false) }
.downloadKeys(listOf(userId), false) .mapCatching {
.flatMap {
val deviceId = request.deviceId val deviceId = request.deviceId
val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId) val deviceInfo = cryptoStore.getUserDevice(deviceId ?: "", userId)
if (deviceInfo == null) { if (deviceInfo == null) {
throw RuntimeException() throw RuntimeException()
} else { } else {
val devicesByUser = mapOf(userId to listOf(deviceInfo)) val devicesByUser = mapOf(userId to listOf(deviceInfo))
ensureOlmSessionsForDevicesAction val usersDeviceMap = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
.handle(devicesByUser) val body = request.requestBody
.flatMap { val olmSessionResult = usersDeviceMap.getObject(userId, deviceId)
val body = request.requestBody if (olmSessionResult?.sessionId == null) {
val olmSessionResult = it.getObject(userId, deviceId) // no session with this device, probably because there
if (olmSessionResult?.sessionId == null) { // were no one-time keys.
// no session with this device, probably because there return@mapCatching
// were no one-time keys. }
Try.just(Unit) Timber.v("## shareKeysWithDevice() : sharing keys for session" +
} " ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId")
Timber.v("## shareKeysWithDevice() : sharing keys for session" +
" ${body?.senderKey}|${body?.sessionId} with device $userId:$deviceId")
val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY) val payloadJson = mutableMapOf<String, Any>("type" to EventType.FORWARDED_ROOM_KEY)
runCatching { olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) }
.fold(
{
// TODO
payloadJson["content"] = it.exportKeys()
?: ""
},
{
// TODO
}
olmDevice.getInboundGroupSession(body?.sessionId, body?.senderKey, body?.roomId) )
.fold(
{
// TODO
},
{
// TODO
payloadJson["content"] = it.exportKeys() ?: ""
}
)
val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo)) val encodedPayload = messageEncrypter.encryptMessage(payloadJson, listOf(deviceInfo))
val sendToDeviceMap = MXUsersDevicesMap<Any>() val sendToDeviceMap = MXUsersDevicesMap<Any>()
sendToDeviceMap.setObject(userId, deviceId, encodedPayload) sendToDeviceMap.setObject(userId, deviceId, encodedPayload)
Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId") Timber.v("## shareKeysWithDevice() : sending to $userId:$deviceId")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap) val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, sendToDeviceMap)
sendToDeviceTask.execute(sendToDeviceParams) sendToDeviceTask.execute(sendToDeviceParams)
}
} }
} }
} }
} }
} }

View File

@ -19,7 +19,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.megolm package im.vector.matrix.android.internal.crypto.algorithms.megolm
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
@ -69,12 +68,10 @@ internal class MXMegolmEncryption(
override suspend fun encryptEventContent(eventContent: Content, override suspend fun encryptEventContent(eventContent: Content,
eventType: String, eventType: String,
userIds: List<String>): Try<Content> { userIds: List<String>): Content {
return getDevicesInRoom(userIds) val devices = getDevicesInRoom(userIds)
.flatMap { ensureOutboundSession(it) } val outboundSession = ensureOutboundSession(devices)
.flatMap { return encryptContent(outboundSession, eventType, eventContent)
encryptContent(it, eventType, eventContent)
}
} }
/** /**
@ -101,7 +98,7 @@ internal class MXMegolmEncryption(
* *
* @param devicesInRoom the devices list * @param devicesInRoom the devices list
*/ */
private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): Try<MXOutboundSessionInfo> { private suspend fun ensureOutboundSession(devicesInRoom: MXUsersDevicesMap<MXDeviceInfo>): MXOutboundSessionInfo {
var session = outboundSession var session = outboundSession
if (session == null if (session == null
// Need to make a brand new session? // Need to make a brand new session?
@ -126,7 +123,8 @@ internal class MXMegolmEncryption(
} }
} }
} }
return shareKey(safeSession, shareMap).map { safeSession!! } shareKey(safeSession, shareMap)
return safeSession
} }
/** /**
@ -136,11 +134,11 @@ internal class MXMegolmEncryption(
* @param devicesByUsers the devices map * @param devicesByUsers the devices map
*/ */
private suspend fun shareKey(session: MXOutboundSessionInfo, private suspend fun shareKey(session: MXOutboundSessionInfo,
devicesByUsers: Map<String, List<MXDeviceInfo>>): Try<Unit> { devicesByUsers: Map<String, List<MXDeviceInfo>>) {
// nothing to send, the task is done // nothing to send, the task is done
if (devicesByUsers.isEmpty()) { if (devicesByUsers.isEmpty()) {
Timber.v("## shareKey() : nothing more to do") Timber.v("## shareKey() : nothing more to do")
return Try.just(Unit) return
} }
// reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user) // reduce the map size to avoid request timeout when there are too many devices (Users size * devices per user)
val subMap = HashMap<String, List<MXDeviceInfo>>() val subMap = HashMap<String, List<MXDeviceInfo>>()
@ -157,11 +155,9 @@ internal class MXMegolmEncryption(
} }
} }
Timber.v("## shareKey() ; userId $userIds") Timber.v("## shareKey() ; userId $userIds")
return shareUserDevicesKey(session, subMap) shareUserDevicesKey(session, subMap)
.flatMap { val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it).not() }
val remainingDevices = devicesByUsers.filterKeys { userIds.contains(it).not() } shareKey(session, remainingDevices)
shareKey(session, remainingDevices)
}
} }
/** /**
@ -172,7 +168,7 @@ internal class MXMegolmEncryption(
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo, private suspend fun shareUserDevicesKey(session: MXOutboundSessionInfo,
devicesByUser: Map<String, List<MXDeviceInfo>>): Try<Unit> { devicesByUser: Map<String, List<MXDeviceInfo>>) {
val sessionKey = olmDevice.getSessionKey(session.sessionId) val sessionKey = olmDevice.getSessionKey(session.sessionId)
val chainIndex = olmDevice.getMessageIndex(session.sessionId) val chainIndex = olmDevice.getMessageIndex(session.sessionId)
@ -190,94 +186,86 @@ internal class MXMegolmEncryption(
var t0 = System.currentTimeMillis() var t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : starts") Timber.v("## shareUserDevicesKey() : starts")
return ensureOlmSessionsForDevicesAction.handle(devicesByUser) val results = ensureOlmSessionsForDevicesAction.handle(devicesByUser)
.flatMap { Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after "
Timber.v("## shareUserDevicesKey() : ensureOlmSessionsForDevices succeeds after " + (System.currentTimeMillis() - t0) + " ms")
+ (System.currentTimeMillis() - t0) + " ms") val contentMap = MXUsersDevicesMap<Any>()
val contentMap = MXUsersDevicesMap<Any>() var haveTargets = false
var haveTargets = false val userIds = results.userIds
val userIds = it.userIds for (userId in userIds) {
for (userId in userIds) { val devicesToShareWith = devicesByUser[userId]
val devicesToShareWith = devicesByUser[userId] for ((deviceID) in devicesToShareWith!!) {
for ((deviceID) in devicesToShareWith!!) { val sessionResult = results.getObject(userId, deviceID)
val sessionResult = it.getObject(userId, deviceID) if (sessionResult?.sessionId == null) {
if (sessionResult?.sessionId == null) { // no session with this device, probably because there
// no session with this device, probably because there // were no one-time keys.
// were no one-time keys. //
// // we could send them a to_device message anyway, as a
// we could send them a to_device message anyway, as a // signal that they have missed out on the key sharing
// signal that they have missed out on the key sharing // message because of the lack of keys, but there's not
// message because of the lack of keys, but there's not // much point in that really; it will mostly serve to clog
// much point in that really; it will mostly serve to clog // up to_device inboxes.
// up to_device inboxes. //
// // ensureOlmSessionsForUsers has already done the logging,
// ensureOlmSessionsForUsers has already done the logging, // so just skip it.
// so just skip it. continue
continue
}
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.deviceInfo)))
haveTargets = true
}
}
if (haveTargets) {
t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : has target")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
sendToDeviceTask.execute(sendToDeviceParams)
.map {
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")
// Add the devices we have shared with to session.sharedWithDevices.
// we deliberately iterate over devicesByUser (ie, the devices we
// attempted to share with) rather than the contentMap (those we did
// share with), because we don't want to try to claim a one-time-key
// for dead devices on every message.
for (userId in devicesByUser.keys) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceId) in devicesToShareWith!!) {
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
}
}
Unit
}
} else {
Timber.v("## shareUserDevicesKey() : no need to sharekey")
Try.just(Unit)
}
} }
Timber.v("## shareUserDevicesKey() : Sharing keys with device $userId:$deviceID")
//noinspection ArraysAsListWithZeroOrOneArgument,ArraysAsListWithZeroOrOneArgument
contentMap.setObject(userId, deviceID, messageEncrypter.encryptMessage(payload, Arrays.asList(sessionResult.deviceInfo)))
haveTargets = true
}
}
if (haveTargets) {
t0 = System.currentTimeMillis()
Timber.v("## shareUserDevicesKey() : has target")
val sendToDeviceParams = SendToDeviceTask.Params(EventType.ENCRYPTED, contentMap)
sendToDeviceTask.execute(sendToDeviceParams)
Timber.v("## shareUserDevicesKey() : sendToDevice succeeds after "
+ (System.currentTimeMillis() - t0) + " ms")
// Add the devices we have shared with to session.sharedWithDevices.
// we deliberately iterate over devicesByUser (ie, the devices we
// attempted to share with) rather than the contentMap (those we did
// share with), because we don't want to try to claim a one-time-key
// for dead devices on every message.
for (userId in devicesByUser.keys) {
val devicesToShareWith = devicesByUser[userId]
for ((deviceId) in devicesToShareWith!!) {
session.sharedWithDevices.setObject(userId, deviceId, chainIndex)
}
}
} else {
Timber.v("## shareUserDevicesKey() : no need to sharekey")
}
} }
/** /**
* process the pending encryptions * process the pending encryptions
*/ */
private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Try<Content> { private fun encryptContent(session: MXOutboundSessionInfo, eventType: String, eventContent: Content): Content {
return Try<Content> { // Everything is in place, encrypt all pending events
// Everything is in place, encrypt all pending events val payloadJson = HashMap<String, Any>()
val payloadJson = HashMap<String, Any>() payloadJson["room_id"] = roomId
payloadJson["room_id"] = roomId payloadJson["type"] = eventType
payloadJson["type"] = eventType payloadJson["content"] = eventContent
payloadJson["content"] = eventContent
// Get canonical Json from // Get canonical Json from
val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson)) val payloadString = convertToUTF8(JsonCanonicalizer.getCanonicalJson(Map::class.java, payloadJson))
val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!) val ciphertext = olmDevice.encryptGroupMessage(session.sessionId, payloadString!!)
val map = HashMap<String, Any>() val map = HashMap<String, Any>()
map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM map["algorithm"] = MXCRYPTO_ALGORITHM_MEGOLM
map["sender_key"] = olmDevice.deviceCurve25519Key!! map["sender_key"] = olmDevice.deviceCurve25519Key!!
map["ciphertext"] = ciphertext!! map["ciphertext"] = ciphertext!!
map["session_id"] = session.sessionId map["session_id"] = session.sessionId
// Include our device ID so that recipients can send us a // Include our device ID so that recipients can send us a
// m.new_device message if they don't have our session key. // m.new_device message if they don't have our session key.
map["device_id"] = credentials.deviceId!! map["device_id"] = credentials.deviceId!!
session.useCount++ session.useCount++
map return map
}
} }
/** /**
@ -287,50 +275,47 @@ internal class MXMegolmEncryption(
* @param userIds the user ids whose devices must be checked. * @param userIds the user ids whose devices must be checked.
* @param callback the asynchronous callback * @param callback the asynchronous callback
*/ */
private suspend fun getDevicesInRoom(userIds: List<String>): Try<MXUsersDevicesMap<MXDeviceInfo>> { private suspend fun getDevicesInRoom(userIds: List<String>): MXUsersDevicesMap<MXDeviceInfo> {
// We are happy to use a cached version here: we assume that if we already // We are happy to use a cached version here: we assume that if we already
// have a list of the user's devices, then we already share an e2e room // have a list of the user's devices, then we already share an e2e room
// with them, which means that they will have announced any new devices via // with them, which means that they will have announced any new devices via
// an m.new_device. // an m.new_device.
return deviceListManager val keys = deviceListManager.downloadKeys(userIds, false)
.downloadKeys(userIds, false) val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
.flatMap { || cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
val encryptToVerifiedDevicesOnly = cryptoStore.getGlobalBlacklistUnverifiedDevices()
|| cryptoStore.getRoomsListBlacklistUnverifiedDevices().contains(roomId)
val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>() val devicesInRoom = MXUsersDevicesMap<MXDeviceInfo>()
val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>() val unknownDevices = MXUsersDevicesMap<MXDeviceInfo>()
for (userId in it.userIds) { for (userId in keys.userIds) {
val deviceIds = it.getUserDeviceIds(userId) ?: continue val deviceIds = keys.getUserDeviceIds(userId) ?: continue
for (deviceId in deviceIds) { for (deviceId in deviceIds) {
val deviceInfo = it.getObject(userId, deviceId) ?: continue val deviceInfo = keys.getObject(userId, deviceId) ?: continue
if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) { if (warnOnUnknownDevicesRepository.warnOnUnknownDevices() && deviceInfo.isUnknown) {
// The device is not yet known by the user // The device is not yet known by the user
unknownDevices.setObject(userId, deviceId, deviceInfo) unknownDevices.setObject(userId, deviceId, deviceInfo)
continue continue
}
if (deviceInfo.isBlocked) {
// Remove any blocked devices
continue
}
if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) {
continue
}
if (TextUtils.equals(deviceInfo.identityKey(), olmDevice.deviceCurve25519Key)) {
// Don't bother sending to ourself
continue
}
devicesInRoom.setObject(userId, deviceId, deviceInfo)
}
}
if (unknownDevices.isEmpty) {
Try.just(devicesInRoom)
} else {
Try.Failure(MXCryptoError.UnknownDevice(unknownDevices))
}
} }
if (deviceInfo.isBlocked) {
// Remove any blocked devices
continue
}
if (!deviceInfo.isVerified && encryptToVerifiedDevicesOnly) {
continue
}
if (TextUtils.equals(deviceInfo.identityKey(), olmDevice.deviceCurve25519Key)) {
// Don't bother sending to ourself
continue
}
devicesInRoom.setObject(userId, deviceId, deviceInfo)
}
}
if (unknownDevices.isEmpty) {
return devicesInRoom
} else {
throw MXCryptoError.UnknownDevice(unknownDevices)
}
} }
} }

View File

@ -17,7 +17,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm package im.vector.matrix.android.internal.crypto.algorithms.olm
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
@ -41,29 +40,28 @@ internal class MXOlmDecryption(
private val credentials: Credentials) private val credentials: Credentials)
: IMXDecrypting { : IMXDecrypting {
override suspend fun decryptEvent(event: Event, timeline: String): Try<MXEventDecryptionResult> { override suspend fun decryptEvent(event: Event, timeline: String): MXEventDecryptionResult {
val olmEventContent = event.content.toModel<OlmEventContent>() ?: run { val olmEventContent = event.content.toModel<OlmEventContent>() ?: run {
Timber.e("## decryptEvent() : bad event format") Timber.e("## decryptEvent() : bad event format")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_EVENT_FORMAT,
MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)) MXCryptoError.BAD_EVENT_FORMAT_TEXT_REASON)
} }
val cipherText = olmEventContent.ciphertext ?: run { val cipherText = olmEventContent.ciphertext ?: run {
Timber.e("## decryptEvent() : missing cipher text") Timber.e("## decryptEvent() : missing cipher text")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT, throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_CIPHER_TEXT,
MXCryptoError.MISSING_CIPHER_TEXT_REASON)) MXCryptoError.MISSING_CIPHER_TEXT_REASON)
} }
val senderKey = olmEventContent.senderKey ?: run { val senderKey = olmEventContent.senderKey ?: run {
Timber.e("## decryptEvent() : missing sender key") Timber.e("## decryptEvent() : missing sender key")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY, throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_SENDER_KEY,
MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)) MXCryptoError.MISSING_SENDER_KEY_TEXT_REASON)
} }
val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run { val messageAny = cipherText[olmDevice.deviceCurve25519Key] ?: run {
Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients") Timber.e("## decryptEvent() : our device ${olmDevice.deviceCurve25519Key} is not included in recipients")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS, throw MXCryptoError.Base(MXCryptoError.ErrorType.NOT_INCLUDE_IN_RECIPIENTS, MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON)
MXCryptoError.NOT_INCLUDED_IN_RECIPIENT_REASON))
} }
// The message for myUser // The message for myUser
@ -73,14 +71,12 @@ internal class MXOlmDecryption(
if (decryptedPayload == null) { if (decryptedPayload == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey") Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
} }
val payloadString = convertFromUTF8(decryptedPayload) val payloadString = convertFromUTF8(decryptedPayload)
if (payloadString == null) { if (payloadString == null) {
Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey") Timber.e("## decryptEvent() Failed to decrypt Olm event (id= ${event.eventId} from $senderKey")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ENCRYPTED_MESSAGE, MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON)
MXCryptoError.BAD_ENCRYPTED_MESSAGE_REASON))
} }
val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE) val adapter = MoshiProvider.providesMoshi().adapter<JsonDict>(JSON_DICT_PARAMETERIZED_TYPE)
@ -88,73 +84,70 @@ internal class MXOlmDecryption(
if (payload == null) { if (payload == null) {
Timber.e("## decryptEvent failed : null payload") Timber.e("## decryptEvent failed : null payload")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, MXCryptoError.MISSING_CIPHER_TEXT_REASON)
MXCryptoError.MISSING_CIPHER_TEXT_REASON))
} }
val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run { val olmPayloadContent = OlmPayloadContent.fromJsonString(payloadString) ?: run {
Timber.e("## decryptEvent() : bad olmPayloadContent format") Timber.e("## decryptEvent() : bad olmPayloadContent format")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_DECRYPTED_FORMAT, MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON)
MXCryptoError.BAD_DECRYPTED_FORMAT_TEXT_REASON))
} }
if (olmPayloadContent.recipient.isNullOrBlank()) { if (olmPayloadContent.recipient.isNullOrBlank()) {
val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient") val reason = String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient")
Timber.e("## decryptEvent() : $reason") Timber.e("## decryptEvent() : $reason")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, reason)
reason))
} }
if (olmPayloadContent.recipient != credentials.userId) { if (olmPayloadContent.recipient != credentials.userId) {
Timber.e("## decryptEvent() : Event ${event.eventId}:" + Timber.e("## decryptEvent() : Event ${event.eventId}:" +
" Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}") " Intended recipient ${olmPayloadContent.recipient} does not match our id ${credentials.userId}")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT,
String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))) String.format(MXCryptoError.BAD_RECIPIENT_REASON, olmPayloadContent.recipient))
} }
val recipientKeys = olmPayloadContent.recipient_keys ?: run { val recipientKeys = olmPayloadContent.recipient_keys ?: run {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack") Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'recipient_keys' property; cannot prevent unknown-key attack")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))) String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "recipient_keys"))
} }
val ed25519 = recipientKeys["ed25519"] val ed25519 = recipientKeys["ed25519"]
if (ed25519 != olmDevice.deviceEd25519Key) { if (ed25519 != olmDevice.deviceEd25519Key) {
Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours") Timber.e("## decryptEvent() : Event ${event.eventId}: Intended recipient ed25519 key $ed25519 did not match ours")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_RECIPIENT_KEY,
MXCryptoError.BAD_RECIPIENT_KEY_REASON)) MXCryptoError.BAD_RECIPIENT_KEY_REASON)
} }
if (olmPayloadContent.sender.isNullOrBlank()) { if (olmPayloadContent.sender.isNullOrBlank()) {
Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack") Timber.e("## decryptEvent() : Olm event (id=${event.eventId}) contains no 'sender' property; cannot prevent unknown-key attack")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY, throw MXCryptoError.Base(MXCryptoError.ErrorType.MISSING_PROPERTY,
String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))) String.format(MXCryptoError.ERROR_MISSING_PROPERTY_REASON, "sender"))
} }
if (olmPayloadContent.sender != event.senderId) { if (olmPayloadContent.sender != event.senderId) {
Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}") Timber.e("Event ${event.eventId}: original sender ${olmPayloadContent.sender} does not match reported sender ${event.senderId}")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE, throw MXCryptoError.Base(MXCryptoError.ErrorType.FORWARDED_MESSAGE,
String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))) String.format(MXCryptoError.FORWARDED_MESSAGE_REASON, olmPayloadContent.sender))
} }
if (olmPayloadContent.room_id != event.roomId) { if (olmPayloadContent.room_id != event.roomId) {
Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${event.roomId}") Timber.e("## decryptEvent() : Event ${event.eventId}: original room ${olmPayloadContent.room_id} does not match reported room ${event.roomId}")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM, throw MXCryptoError.Base(MXCryptoError.ErrorType.BAD_ROOM,
String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id))) String.format(MXCryptoError.BAD_ROOM_REASON, olmPayloadContent.room_id))
} }
val keys = olmPayloadContent.keys ?: run { val keys = olmPayloadContent.keys ?: run {
Timber.e("## decryptEvent failed : null keys") Timber.e("## decryptEvent failed : null keys")
return Try.Failure(MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT, throw MXCryptoError.Base(MXCryptoError.ErrorType.UNABLE_TO_DECRYPT,
MXCryptoError.MISSING_CIPHER_TEXT_REASON)) MXCryptoError.MISSING_CIPHER_TEXT_REASON)
} }
return Try.just(MXEventDecryptionResult( return MXEventDecryptionResult(
clearEvent = payload, clearEvent = payload,
senderCurve25519Key = senderKey, senderCurve25519Key = senderKey,
claimedEd25519Key = keys["ed25519"] claimedEd25519Key = keys["ed25519"]
)) )
} }
/** /**

View File

@ -19,7 +19,6 @@
package im.vector.matrix.android.internal.crypto.algorithms.olm package im.vector.matrix.android.internal.crypto.algorithms.olm
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.api.session.events.model.Content import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.toContent import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.internal.crypto.DeviceListManager import im.vector.matrix.android.internal.crypto.DeviceListManager
@ -40,37 +39,35 @@ internal class MXOlmEncryption(
private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction) private val ensureOlmSessionsForUsersAction: EnsureOlmSessionsForUsersAction)
: IMXEncrypting { : IMXEncrypting {
override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Try<Content> { override suspend fun encryptEventContent(eventContent: Content, eventType: String, userIds: List<String>): Content {
// pick the list of recipients based on the membership list. // pick the list of recipients based on the membership list.
// //
// TODO: there is a race condition here! What if a new user turns up // TODO: there is a race condition here! What if a new user turns up
return ensureSession(userIds) ensureSession(userIds)
.map { val deviceInfos = ArrayList<MXDeviceInfo>()
val deviceInfos = ArrayList<MXDeviceInfo>() for (userId in userIds) {
for (userId in userIds) { val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList()
val devices = cryptoStore.getUserDevices(userId)?.values ?: emptyList() for (device in devices) {
for (device in devices) { val key = device.identityKey()
val key = device.identityKey() if (TextUtils.equals(key, olmDevice.deviceCurve25519Key)) {
if (TextUtils.equals(key, olmDevice.deviceCurve25519Key)) { // Don't bother setting up session to ourself
// Don't bother setting up session to ourself continue
continue
}
if (device.isBlocked) {
// Don't bother setting up sessions with blocked users
continue
}
deviceInfos.add(device)
}
}
val messageMap = HashMap<String, Any>()
messageMap["room_id"] = roomId
messageMap["type"] = eventType
messageMap["content"] = eventContent
messageEncrypter.encryptMessage(messageMap, deviceInfos)
messageMap.toContent()!!
} }
if (device.isBlocked) {
// Don't bother setting up sessions with blocked users
continue
}
deviceInfos.add(device)
}
}
val messageMap = HashMap<String, Any>()
messageMap["room_id"] = roomId
messageMap["type"] = eventType
messageMap["content"] = eventContent
messageEncrypter.encryptMessage(messageMap, deviceInfos)
return messageMap.toContent()!!
} }
@ -78,13 +75,9 @@ internal class MXOlmEncryption(
* Ensure that the session * Ensure that the session
* *
* @param users the user ids list * @param users the user ids list
* @param callback the asynchronous callback
*/ */
private suspend fun ensureSession(users: List<String>): Try<Unit> { private suspend fun ensureSession(users: List<String>) {
return deviceListManager deviceListManager.downloadKeys(users, false)
.downloadKeys(users, false) ensureOlmSessionsForUsersAction.handle(users)
.flatMap { ensureOlmSessionsForUsersAction.handle(users) }
.map { Unit }
} }
} }

View File

@ -52,10 +52,6 @@ import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.* import im.vector.matrix.android.internal.task.*
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.JsonCanonicalizer import im.vector.matrix.android.internal.util.JsonCanonicalizer
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -355,10 +351,8 @@ internal class KeysBackup @Inject constructor(
callback: MatrixCallback<KeysBackupVersionTrust>) { callback: MatrixCallback<KeysBackupVersionTrust>) {
// TODO Validate with François that this is correct // TODO Validate with François that this is correct
object : Task<KeysVersionResult, KeysBackupVersionTrust> { object : Task<KeysVersionResult, KeysBackupVersionTrust> {
override suspend fun execute(params: KeysVersionResult): Try<KeysBackupVersionTrust> { override suspend fun execute(params: KeysVersionResult): KeysBackupVersionTrust {
return Try { return getKeysBackupTrustBg(params)
getKeysBackupTrustBg(params)
}
} }
} }
.configureWith(keysBackupVersion) .configureWith(keysBackupVersion)
@ -454,7 +448,8 @@ internal class KeysBackup @Inject constructor(
val myUserId = credentials.userId val myUserId = credentials.userId
// Get current signatures, or create an empty set // Get current signatures, or create an empty set
val myUserSignatures = authData.signatures?.get(myUserId)?.toMutableMap() ?: HashMap() val myUserSignatures = authData.signatures?.get(myUserId)?.toMutableMap()
?: HashMap()
if (trust) { if (trust) {
// Add current device signature // Add current device signature

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.CreateKeysBackupVersionBody
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersion
@ -30,7 +29,7 @@ internal class DefaultCreateKeysBackupVersionTask @Inject constructor(private va
: CreateKeysBackupVersionTask { : CreateKeysBackupVersionTask {
override suspend fun execute(params: CreateKeysBackupVersionBody): Try<KeysVersion> { override suspend fun execute(params: CreateKeysBackupVersionBody): KeysVersion {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.createKeysBackupVersion(params) apiCall = roomKeysApi.createKeysBackupVersion(params)
} }

View File

@ -16,10 +16,8 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -33,10 +31,9 @@ internal interface DeleteBackupTask : Task<DeleteBackupTask.Params, Unit> {
internal class DefaultDeleteBackupTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultDeleteBackupTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: DeleteBackupTask { : DeleteBackupTask {
override suspend fun execute(params: DeleteBackupTask.Params): Try<Unit> { override suspend fun execute(params: DeleteBackupTask.Params) {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.deleteBackup( apiCall = roomKeysApi.deleteBackup(params.version)
params.version)
} }
} }
} }

View File

@ -16,10 +16,8 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -34,7 +32,7 @@ internal interface DeleteRoomSessionDataTask : Task<DeleteRoomSessionDataTask.Pa
internal class DefaultDeleteRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultDeleteRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: DeleteRoomSessionDataTask { : DeleteRoomSessionDataTask {
override suspend fun execute(params: DeleteRoomSessionDataTask.Params): Try<Unit> { override suspend fun execute(params: DeleteRoomSessionDataTask.Params) {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.deleteRoomSessionData( apiCall = roomKeysApi.deleteRoomSessionData(
params.roomId, params.roomId,

View File

@ -16,10 +16,8 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -33,7 +31,7 @@ internal interface DeleteRoomSessionsDataTask : Task<DeleteRoomSessionsDataTask.
internal class DefaultDeleteRoomSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultDeleteRoomSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: DeleteRoomSessionsDataTask { : DeleteRoomSessionsDataTask {
override suspend fun execute(params: DeleteRoomSessionsDataTask.Params): Try<Unit> { override suspend fun execute(params: DeleteRoomSessionsDataTask.Params) {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.deleteRoomSessionsData( apiCall = roomKeysApi.deleteRoomSessionsData(
params.roomId, params.roomId,

View File

@ -16,10 +16,8 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -32,10 +30,9 @@ internal interface DeleteSessionsDataTask : Task<DeleteSessionsDataTask.Params,
internal class DefaultDeleteSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultDeleteSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: DeleteSessionsDataTask { : DeleteSessionsDataTask {
override suspend fun execute(params: DeleteSessionsDataTask.Params): Try<Unit> { override suspend fun execute(params: DeleteSessionsDataTask.Params) {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.deleteSessionsData( apiCall = roomKeysApi.deleteSessionsData(params.version)
params.version)
} }
} }
} }

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
@ -29,7 +28,7 @@ internal class DefaultGetKeysBackupLastVersionTask @Inject constructor(private v
: GetKeysBackupLastVersionTask { : GetKeysBackupLastVersionTask {
override suspend fun execute(params: Unit): Try<KeysVersionResult> { override suspend fun execute(params: Unit): KeysVersionResult {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.getKeysBackupLastVersion() apiCall = roomKeysApi.getKeysBackupLastVersion()
} }

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysVersionResult
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
@ -29,7 +28,7 @@ internal class DefaultGetKeysBackupVersionTask @Inject constructor(private val r
: GetKeysBackupVersionTask { : GetKeysBackupVersionTask {
override suspend fun execute(params: String): Try<KeysVersionResult> { override suspend fun execute(params: String): KeysVersionResult {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.getKeysBackupVersion(params) apiCall = roomKeysApi.getKeysBackupVersion(params)
} }

View File

@ -16,11 +16,9 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -35,7 +33,7 @@ internal interface GetRoomSessionDataTask : Task<GetRoomSessionDataTask.Params,
internal class DefaultGetRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultGetRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: GetRoomSessionDataTask { : GetRoomSessionDataTask {
override suspend fun execute(params: GetRoomSessionDataTask.Params): Try<KeyBackupData> { override suspend fun execute(params: GetRoomSessionDataTask.Params): KeyBackupData {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.getRoomSessionData( apiCall = roomKeysApi.getRoomSessionData(
params.roomId, params.roomId,

View File

@ -16,11 +16,9 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -35,7 +33,7 @@ internal interface GetRoomSessionsDataTask : Task<GetRoomSessionsDataTask.Params
internal class DefaultGetRoomSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultGetRoomSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: GetRoomSessionsDataTask { : GetRoomSessionsDataTask {
override suspend fun execute(params: GetRoomSessionsDataTask.Params): Try<RoomKeysBackupData> { override suspend fun execute(params: GetRoomSessionsDataTask.Params): RoomKeysBackupData {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.getRoomSessionsData( apiCall = roomKeysApi.getRoomSessionsData(
params.roomId, params.roomId,

View File

@ -16,11 +16,9 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -33,10 +31,9 @@ internal interface GetSessionsDataTask : Task<GetSessionsDataTask.Params, KeysBa
internal class DefaultGetSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultGetSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: GetSessionsDataTask { : GetSessionsDataTask {
override suspend fun execute(params: GetSessionsDataTask.Params): Try<KeysBackupData> { override suspend fun execute(params: GetSessionsDataTask.Params): KeysBackupData {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.getSessionsData( apiCall = roomKeysApi.getSessionsData(params.version)
params.version)
} }
} }
} }

View File

@ -16,12 +16,10 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeyBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -37,7 +35,7 @@ internal interface StoreRoomSessionDataTask : Task<StoreRoomSessionDataTask.Para
internal class DefaultStoreRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultStoreRoomSessionDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: StoreRoomSessionDataTask { : StoreRoomSessionDataTask {
override suspend fun execute(params: StoreRoomSessionDataTask.Params): Try<BackupKeysResult> { override suspend fun execute(params: StoreRoomSessionDataTask.Params): BackupKeysResult {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.storeRoomSessionData( apiCall = roomKeysApi.storeRoomSessionData(
params.roomId, params.roomId,

View File

@ -16,12 +16,10 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.RoomKeysBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -36,7 +34,7 @@ internal interface StoreRoomSessionsDataTask : Task<StoreRoomSessionsDataTask.Pa
internal class DefaultStoreRoomSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultStoreRoomSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: StoreRoomSessionsDataTask { : StoreRoomSessionsDataTask {
override suspend fun execute(params: StoreRoomSessionsDataTask.Params): Try<BackupKeysResult> { override suspend fun execute(params: StoreRoomSessionsDataTask.Params): BackupKeysResult {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.storeRoomSessionsData( apiCall = roomKeysApi.storeRoomSessionsData(
params.roomId, params.roomId,

View File

@ -16,12 +16,10 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.BackupKeysResult
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.KeysBackupData
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -35,7 +33,7 @@ internal interface StoreSessionsDataTask : Task<StoreSessionsDataTask.Params, Ba
internal class DefaultStoreSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi) internal class DefaultStoreSessionsDataTask @Inject constructor(private val roomKeysApi: RoomKeysApi)
: StoreSessionsDataTask { : StoreSessionsDataTask {
override suspend fun execute(params: StoreSessionsDataTask.Params): Try<BackupKeysResult> { override suspend fun execute(params: StoreSessionsDataTask.Params): BackupKeysResult {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.storeSessionsData( apiCall = roomKeysApi.storeSessionsData(
params.version, params.version,

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.keysbackup.tasks package im.vector.matrix.android.internal.crypto.keysbackup.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi import im.vector.matrix.android.internal.crypto.keysbackup.api.RoomKeysApi
import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody import im.vector.matrix.android.internal.crypto.keysbackup.model.rest.UpdateKeysBackupVersionBody
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
@ -34,7 +33,7 @@ internal class DefaultUpdateKeysBackupVersionTask @Inject constructor(private va
: UpdateKeysBackupVersionTask { : UpdateKeysBackupVersionTask {
override suspend fun execute(params: UpdateKeysBackupVersionTask.Params): Try<Unit> { override suspend fun execute(params: UpdateKeysBackupVersionTask.Params) {
return executeRequest { return executeRequest {
apiCall = roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody) apiCall = roomKeysApi.updateKeysBackupVersion(params.version, params.keysBackupVersionBody)
} }

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.MXKey import im.vector.matrix.android.internal.crypto.model.MXKey
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
@ -37,35 +36,30 @@ internal interface ClaimOneTimeKeysForUsersDeviceTask : Task<ClaimOneTimeKeysFor
internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultClaimOneTimeKeysForUsersDevice @Inject constructor(private val cryptoApi: CryptoApi)
: ClaimOneTimeKeysForUsersDeviceTask { : ClaimOneTimeKeysForUsersDeviceTask {
override suspend fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): Try<MXUsersDevicesMap<MXKey>> { override suspend fun execute(params: ClaimOneTimeKeysForUsersDeviceTask.Params): MXUsersDevicesMap<MXKey> {
val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map) val body = KeysClaimBody(oneTimeKeys = params.usersDevicesKeyTypesMap.map)
return executeRequest<KeysClaimResponse> { val keysClaimResponse = executeRequest<KeysClaimResponse> {
apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body) apiCall = cryptoApi.claimOneTimeKeysForUsersDevices(body)
}.flatMap { keysClaimResponse -> }
Try { val map = MXUsersDevicesMap<MXKey>()
val map = MXUsersDevicesMap<MXKey>() keysClaimResponse.oneTimeKeys?.let { oneTimeKeys ->
for (userId in oneTimeKeys.keys) {
val mapByUserId = oneTimeKeys[userId]
keysClaimResponse.oneTimeKeys?.let { oneTimeKeys -> if (mapByUserId != null) {
for (userId in oneTimeKeys.keys) { for (deviceId in mapByUserId.keys) {
val mapByUserId = oneTimeKeys[userId] val mxKey = MXKey.from(mapByUserId[deviceId])
if (mapByUserId != null) { if (mxKey != null) {
for (deviceId in mapByUserId.keys) { map.setObject(userId, deviceId, mxKey)
val mxKey = MXKey.from(mapByUserId[deviceId]) } else {
Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey")
if (mxKey != null) {
map.setObject(userId, deviceId, mxKey)
} else {
Timber.e("## claimOneTimeKeysForUsersDevices : fail to create a MXKey")
}
}
} }
} }
} }
map
} }
} }
return map
} }
} }

View File

@ -16,16 +16,12 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
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.Failure
import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse import im.vector.matrix.android.internal.auth.registration.RegistrationFlowResponse
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams import im.vector.matrix.android.internal.crypto.model.rest.DeleteDeviceParams
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -38,10 +34,12 @@ internal interface DeleteDeviceTask : Task<DeleteDeviceTask.Params, Unit> {
internal class DefaultDeleteDeviceTask @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultDeleteDeviceTask @Inject constructor(private val cryptoApi: CryptoApi)
: DeleteDeviceTask { : DeleteDeviceTask {
override suspend fun execute(params: DeleteDeviceTask.Params): Try<Unit> { override suspend fun execute(params: DeleteDeviceTask.Params) {
return executeRequest<Unit> { try {
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()) executeRequest<Unit> {
}.recoverWith { throwable -> apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams())
}
} catch (throwable: Throwable) {
if (throwable is Failure.OtherServerError && throwable.httpCode == 401) { if (throwable is Failure.OtherServerError && throwable.httpCode == 401) {
// Parse to get a RegistrationFlowResponse // Parse to get a RegistrationFlowResponse
val registrationFlowResponse = try { val registrationFlowResponse = try {
@ -51,17 +49,16 @@ internal class DefaultDeleteDeviceTask @Inject constructor(private val cryptoApi
} catch (e: Exception) { } catch (e: Exception) {
null null
} }
// check if the server response can be casted // check if the server response can be casted
if (registrationFlowResponse != null) { if (registrationFlowResponse != null) {
Failure.RegistrationFlowError(registrationFlowResponse).failure() throw Failure.RegistrationFlowError(registrationFlowResponse)
} else { } else {
throwable.failure() throw throwable
} }
} else { } else {
// Other error // Other error
throwable.failure() throw throwable
} }
} }
} }

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.auth.data.LoginFlowTypes import im.vector.matrix.android.internal.auth.data.LoginFlowTypes
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
@ -38,7 +37,7 @@ internal class DefaultDeleteDeviceWithUserPasswordTask @Inject constructor(priva
private val credentials: Credentials) private val credentials: Credentials)
: DeleteDeviceWithUserPasswordTask { : DeleteDeviceWithUserPasswordTask {
override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params): Try<Unit> { override suspend fun execute(params: DeleteDeviceWithUserPasswordTask.Params) {
return executeRequest { return executeRequest {
apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams() apiCall = cryptoApi.deleteDevice(params.deviceId, DeleteDeviceParams()
.apply { .apply {

View File

@ -17,12 +17,10 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryBody import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryBody
import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryResponse import im.vector.matrix.android.internal.crypto.model.rest.KeysQueryResponse
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -38,7 +36,7 @@ internal interface DownloadKeysForUsersTask : Task<DownloadKeysForUsersTask.Para
internal class DefaultDownloadKeysForUsers @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultDownloadKeysForUsers @Inject constructor(private val cryptoApi: CryptoApi)
: DownloadKeysForUsersTask { : DownloadKeysForUsersTask {
override suspend fun execute(params: DownloadKeysForUsersTask.Params): Try<KeysQueryResponse> { override suspend fun execute(params: DownloadKeysForUsersTask.Params): KeysQueryResponse {
val downloadQuery = HashMap<String, Map<String, Any>>() val downloadQuery = HashMap<String, Map<String, Any>>()
if (null != params.userIds) { if (null != params.userIds) {

View File

@ -16,11 +16,9 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse import im.vector.matrix.android.internal.crypto.model.rest.DevicesListResponse
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -29,7 +27,7 @@ internal interface GetDevicesTask : Task<Unit, DevicesListResponse>
internal class DefaultGetDevicesTask @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultGetDevicesTask @Inject constructor(private val cryptoApi: CryptoApi)
: GetDevicesTask { : GetDevicesTask {
override suspend fun execute(params: Unit): Try<DevicesListResponse> { override suspend fun execute(params: Unit): DevicesListResponse {
return executeRequest { return executeRequest {
apiCall = cryptoApi.getDevices() apiCall = cryptoApi.getDevices()
} }

View File

@ -16,11 +16,9 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.KeyChangesResponse import im.vector.matrix.android.internal.crypto.model.rest.KeyChangesResponse
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -36,10 +34,9 @@ internal interface GetKeyChangesTask : Task<GetKeyChangesTask.Params, KeyChanges
internal class DefaultGetKeyChangesTask @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultGetKeyChangesTask @Inject constructor(private val cryptoApi: CryptoApi)
: GetKeyChangesTask { : GetKeyChangesTask {
override suspend fun execute(params: GetKeyChangesTask.Params): Try<KeyChangesResponse> { override suspend fun execute(params: GetKeyChangesTask.Params): KeyChangesResponse {
return executeRequest { return executeRequest {
apiCall = cryptoApi.getKeyChanges(params.from, apiCall = cryptoApi.getKeyChanges(params.from, params.to)
params.to)
} }
} }
} }

View File

@ -16,12 +16,10 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap import im.vector.matrix.android.internal.crypto.model.MXUsersDevicesMap
import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody import im.vector.matrix.android.internal.crypto.model.rest.SendToDeviceBody
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import java.util.* import java.util.*
import javax.inject.Inject import javax.inject.Inject
@ -40,7 +38,7 @@ internal interface SendToDeviceTask : Task<SendToDeviceTask.Params, Unit> {
internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultSendToDeviceTask @Inject constructor(private val cryptoApi: CryptoApi)
: SendToDeviceTask { : SendToDeviceTask {
override suspend fun execute(params: SendToDeviceTask.Params): Try<Unit> { override suspend fun execute(params: SendToDeviceTask.Params) {
val sendToDeviceBody = SendToDeviceBody() val sendToDeviceBody = SendToDeviceBody()
sendToDeviceBody.messages = params.contentMap.map sendToDeviceBody.messages = params.contentMap.map

View File

@ -17,11 +17,9 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import android.text.TextUtils import android.text.TextUtils
import arrow.core.Try
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.UpdateDeviceInfoBody import im.vector.matrix.android.internal.crypto.model.rest.UpdateDeviceInfoBody
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -37,11 +35,10 @@ internal interface SetDeviceNameTask : Task<SetDeviceNameTask.Params, Unit> {
internal class DefaultSetDeviceNameTask @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultSetDeviceNameTask @Inject constructor(private val cryptoApi: CryptoApi)
: SetDeviceNameTask { : SetDeviceNameTask {
override suspend fun execute(params: SetDeviceNameTask.Params): Try<Unit> { override suspend fun execute(params: SetDeviceNameTask.Params) {
val body = UpdateDeviceInfoBody( val body = UpdateDeviceInfoBody(
displayName = if (TextUtils.isEmpty(params.deviceName)) "" else params.deviceName displayName = if (TextUtils.isEmpty(params.deviceName)) "" else params.deviceName
) )
return executeRequest { return executeRequest {
apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body) apiCall = cryptoApi.updateDeviceInfo(params.deviceId, body)
} }

View File

@ -16,14 +16,12 @@
package im.vector.matrix.android.internal.crypto.tasks package im.vector.matrix.android.internal.crypto.tasks
import arrow.core.Try
import im.vector.matrix.android.api.util.JsonDict import im.vector.matrix.android.api.util.JsonDict
import im.vector.matrix.android.internal.crypto.api.CryptoApi import im.vector.matrix.android.internal.crypto.api.CryptoApi
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.crypto.model.rest.DeviceKeys import im.vector.matrix.android.internal.crypto.model.rest.DeviceKeys
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadBody import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadBody
import im.vector.matrix.android.internal.crypto.model.rest.KeysUploadResponse
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.convertToUTF8 import im.vector.matrix.android.internal.util.convertToUTF8
import javax.inject.Inject import javax.inject.Inject
@ -41,7 +39,7 @@ internal interface UploadKeysTask : Task<UploadKeysTask.Params, KeysUploadRespon
internal class DefaultUploadKeysTask @Inject constructor(private val cryptoApi: CryptoApi) internal class DefaultUploadKeysTask @Inject constructor(private val cryptoApi: CryptoApi)
: UploadKeysTask { : UploadKeysTask {
override suspend fun execute(params: UploadKeysTask.Params): Try<KeysUploadResponse> { override suspend fun execute(params: UploadKeysTask.Params): KeysUploadResponse {
val encodedDeviceId = convertToUTF8(params.deviceId) val encodedDeviceId = convertToUTF8(params.deviceId)
val body = KeysUploadBody() val body = KeysUploadBody()

View File

@ -219,17 +219,20 @@ internal class DefaultSasVerificationService @Inject constructor(private val cre
startReq: KeyVerificationStart, startReq: KeyVerificationStart,
success: (MXUsersDevicesMap<MXDeviceInfo>) -> Unit, success: (MXUsersDevicesMap<MXDeviceInfo>) -> Unit,
error: () -> Unit) { error: () -> Unit) {
deviceListManager.downloadKeys(listOf(otherUserId), true) runCatching {
.fold( deviceListManager.downloadKeys(listOf(otherUserId), true)
{ error() }, }.fold(
{ {
if (it.getUserDeviceIds(otherUserId)?.contains(startReq.fromDevice) == true) { if (it.getUserDeviceIds(otherUserId)?.contains(startReq.fromDevice) == true) {
success(it) success(it)
} else { } else {
error() error()
} }
} },
) {
error()
}
)
} }
private suspend fun onCancelReceived(event: Event) { private suspend fun onCancelReceived(event: Event) {

View File

@ -0,0 +1,46 @@
/*
* 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.database
import io.realm.Realm
import kotlinx.coroutines.suspendCancellableCoroutine
import timber.log.Timber
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
suspend fun Realm.awaitTransaction(transaction: (realm: Realm) -> Unit) {
return suspendCancellableCoroutine { continuation ->
beginTransaction()
try {
transaction(this)
commitTransaction()
continuation.resume(Unit)
} catch (e: Throwable) {
if (isInTransaction) {
cancelTransaction()
} else {
Timber.w("Could not cancel transaction, not currently in a transaction.")
}
continuation.resumeWithException(e)
}
continuation.invokeOnCancellation {
if (isInTransaction) {
cancelTransaction()
}
}
}
}

View File

@ -0,0 +1,25 @@
/*
* 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.extensions
import im.vector.matrix.android.api.MatrixCallback
fun <A> Result<A>.foldToCallback(callback: MatrixCallback<A>): Unit = fold(
{ callback.onSuccess(it) },
{ callback.onFailure(it) }
)

View File

@ -16,9 +16,6 @@
package im.vector.matrix.android.internal.network package im.vector.matrix.android.internal.network
import arrow.core.Try
import arrow.core.failure
import arrow.core.recoverWith
import com.squareup.moshi.JsonDataException import com.squareup.moshi.JsonDataException
import com.squareup.moshi.Moshi import com.squareup.moshi.Moshi
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
@ -36,8 +33,8 @@ internal class Request<DATA> {
private val moshi: Moshi = MoshiProvider.providesMoshi() private val moshi: Moshi = MoshiProvider.providesMoshi()
lateinit var apiCall: Call<DATA> lateinit var apiCall: Call<DATA>
suspend fun execute(): Try<DATA> { suspend fun execute(): DATA {
return Try { return try {
val response = apiCall.awaitResponse() val response = apiCall.awaitResponse()
if (response.isSuccessful) { if (response.isSuccessful) {
response.body() response.body()
@ -45,13 +42,13 @@ internal class Request<DATA> {
} else { } else {
throw manageFailure(response.errorBody(), response.code()) throw manageFailure(response.errorBody(), response.code())
} }
}.recoverWith { } catch (exception: Throwable) {
when (it) { throw when (exception) {
is IOException -> Failure.NetworkConnection(it) is IOException -> Failure.NetworkConnection(exception)
is Failure.ServerError, is Failure.ServerError,
is Failure.OtherServerError -> it is Failure.OtherServerError -> exception
else -> Failure.Unknown(it) else -> Failure.Unknown(exception)
}.failure() }
} }
} }

View File

@ -47,6 +47,7 @@ import im.vector.matrix.android.internal.session.sync.job.SyncWorker
import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider
@SessionScope @SessionScope
internal class DefaultSession @Inject constructor(override val sessionParams: SessionParams, internal class DefaultSession @Inject constructor(override val sessionParams: SessionParams,
@ -64,7 +65,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
private val pushersService: Lazy<PushersService>, private val pushersService: Lazy<PushersService>,
private val cryptoService: Lazy<CryptoManager>, private val cryptoService: Lazy<CryptoManager>,
private val fileService: Lazy<FileService>, private val fileService: Lazy<FileService>,
private val syncThread: SyncThread, private val syncThreadProvider: Provider<SyncThread>,
private val contentUrlResolver: ContentUrlResolver, private val contentUrlResolver: ContentUrlResolver,
private val contentUploadProgressTracker: ContentUploadStateTracker, private val contentUploadProgressTracker: ContentUploadStateTracker,
private val initialSyncProgressService: Lazy<InitialSyncProgressService>) private val initialSyncProgressService: Lazy<InitialSyncProgressService>)
@ -84,6 +85,7 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
private var isOpen = false private var isOpen = false
private var syncThread: SyncThread? = null
@MainThread @MainThread
override fun open() { override fun open() {
@ -105,21 +107,23 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
SyncWorker.stopAnyBackgroundSync(context) SyncWorker.stopAnyBackgroundSync(context)
} }
override fun startSync(fromForeground : Boolean) { override fun startSync(fromForeground: Boolean) {
Timber.i("Starting sync thread") Timber.i("Starting sync thread")
assert(isOpen) assert(isOpen)
syncThread.setInitialForeground(fromForeground) val localSyncThread = getSyncThread()
if (!syncThread.isAlive) { localSyncThread.setInitialForeground(fromForeground)
syncThread.start() if (!localSyncThread.isAlive) {
localSyncThread.start()
} else { } else {
syncThread.restart() localSyncThread.restart()
Timber.w("Attempt to start an already started thread") Timber.w("Attempt to start an already started thread")
} }
} }
override fun stopSync() { override fun stopSync() {
assert(isOpen) assert(isOpen)
syncThread.kill() syncThread?.kill()
syncThread = null
} }
override fun close() { override fun close() {
@ -131,7 +135,13 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
} }
override fun syncState(): LiveData<SyncState> { override fun syncState(): LiveData<SyncState> {
return syncThread.liveState() return getSyncThread().liveState()
}
private fun getSyncThread(): SyncThread {
return syncThread ?: syncThreadProvider.get().also {
syncThread = it
}
} }
@MainThread @MainThread
@ -172,6 +182,22 @@ internal class DefaultSession @Inject constructor(override val sessionParams: Se
}) })
} }
override fun clearCache(callback: MatrixCallback<Unit>) {
stopSync()
stopAnyBackgroundSync()
cacheService.get().clearCache(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
startSync(true)
callback.onSuccess(data)
}
override fun onFailure(failure: Throwable) {
startSync(true)
callback.onFailure(failure)
}
})
}
override fun contentUrlResolver() = contentUrlResolver override fun contentUrlResolver() = contentUrlResolver
override fun contentUploadProgressTracker() = contentUploadProgressTracker override fun contentUploadProgressTracker() = contentUploadProgressTracker

View File

@ -16,27 +16,21 @@
package im.vector.matrix.android.internal.session.cache package im.vector.matrix.android.internal.session.cache
import arrow.core.Try import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import io.realm.Realm import io.realm.Realm
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Named
internal interface ClearCacheTask : Task<Unit, Unit> internal interface ClearCacheTask : Task<Unit, Unit>
internal class RealmClearCacheTask @Inject constructor(private val realmConfiguration: RealmConfiguration) : ClearCacheTask { internal class RealmClearCacheTask @Inject constructor(private val realmConfiguration: RealmConfiguration) : ClearCacheTask {
override suspend fun execute(params: Unit): Try<Unit> { override suspend fun execute(params: Unit) {
return Try { val realm = Realm.getInstance(realmConfiguration)
val realm = Realm.getInstance(realmConfiguration) realm.awaitTransaction {
it.deleteAll()
realm.executeTransaction {
it.deleteAll()
}
realm.close()
} }
realm.close()
} }
} }

View File

@ -23,10 +23,12 @@ import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.toConfigurableTask import im.vector.matrix.android.internal.task.toConfigurableTask
import javax.inject.Inject import javax.inject.Inject
internal class DefaultCacheService @Inject constructor(@SessionDatabase private val clearCacheTask: ClearCacheTask, internal class DefaultCacheService @Inject constructor(@SessionDatabase
private val clearCacheTask: ClearCacheTask,
private val taskExecutor: TaskExecutor) : CacheService { private val taskExecutor: TaskExecutor) : CacheService {
override fun clearCache(callback: MatrixCallback<Unit>) { override fun clearCache(callback: MatrixCallback<Unit>) {
taskExecutor.cancelAll()
clearCacheTask clearCacheTask
.toConfigurableTask() .toConfigurableTask()
.dispatchTo(callback) .dispatchTo(callback)

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.session.filter package im.vector.matrix.android.internal.session.filter
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
@ -39,15 +38,12 @@ internal class DefaultSaveFilterTask @Inject constructor(private val sessionPara
private val filterRepository: FilterRepository private val filterRepository: FilterRepository
) : SaveFilterTask { ) : SaveFilterTask {
override suspend fun execute(params: SaveFilterTask.Params): Try<Unit> { override suspend fun execute(params: SaveFilterTask.Params) {
return executeRequest<FilterResponse> { val filterResponse = executeRequest<FilterResponse> {
// TODO auto retry // TODO auto retry
apiCall = filterAPI.uploadFilter(sessionParams.credentials.userId, params.filter) apiCall = filterAPI.uploadFilter(sessionParams.credentials.userId, params.filter)
}.flatMap { filterResponse ->
Try {
filterRepository.storeFilterId(params.filter, filterResponse.filterId)
}
} }
filterRepository.storeFilterId(params.filter, filterResponse.filterId)
} }
} }

View File

@ -16,10 +16,6 @@
package im.vector.matrix.android.internal.session.group package im.vector.matrix.android.internal.session.group
import arrow.core.Try
import arrow.core.fix
import arrow.instances.`try`.monad.monad
import arrow.typeclasses.binding
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.GroupSummaryEntity import im.vector.matrix.android.internal.database.model.GroupSummaryEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
@ -28,8 +24,6 @@ import im.vector.matrix.android.internal.session.group.model.GroupRooms
import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse import im.vector.matrix.android.internal.session.group.model.GroupSummaryResponse
import im.vector.matrix.android.internal.session.group.model.GroupUsers import im.vector.matrix.android.internal.session.group.model.GroupUsers
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync
import io.realm.kotlin.createObject
import javax.inject.Inject import javax.inject.Inject
internal interface GetGroupDataTask : Task<GetGroupDataTask.Params, Unit> { internal interface GetGroupDataTask : Task<GetGroupDataTask.Params, Unit> {
@ -43,7 +37,7 @@ internal class DefaultGetGroupDataTask @Inject constructor(
private val monarchy: Monarchy private val monarchy: Monarchy
) : GetGroupDataTask { ) : GetGroupDataTask {
override suspend fun execute(params: GetGroupDataTask.Params): Try<Unit> { override suspend fun execute(params: GetGroupDataTask.Params) {
val groupId = params.groupId val groupId = params.groupId
val groupSummary = executeRequest<GroupSummaryResponse> { val groupSummary = executeRequest<GroupSummaryResponse> {
apiCall = groupAPI.getSummary(groupId) apiCall = groupAPI.getSummary(groupId)
@ -54,20 +48,18 @@ internal class DefaultGetGroupDataTask @Inject constructor(
val groupUsers = executeRequest<GroupUsers> { val groupUsers = executeRequest<GroupUsers> {
apiCall = groupAPI.getUsers(groupId) apiCall = groupAPI.getUsers(groupId)
} }
return Try.monad().binding { insertInDb(groupSummary, groupRooms, groupUsers, groupId)
insertInDb(groupSummary.bind(), groupRooms.bind(), groupUsers.bind(), groupId).bind()
}.fix()
} }
private fun insertInDb(groupSummary: GroupSummaryResponse, private fun insertInDb(groupSummary: GroupSummaryResponse,
groupRooms: GroupRooms, groupRooms: GroupRooms,
groupUsers: GroupUsers, groupUsers: GroupUsers,
groupId: String): Try<Unit> { groupId: String) {
return monarchy monarchy
.tryTransactionSync { realm -> .writeAsync { realm ->
val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst() val groupSummaryEntity = GroupSummaryEntity.where(realm, groupId).findFirst()
?: realm.createObject(groupId) ?: realm.createObject(GroupSummaryEntity::class.java, groupId)
groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: "" groupSummaryEntity.avatarUrl = groupSummary.profile?.avatarUrl ?: ""
val name = groupSummary.profile?.name val name = groupSummary.profile?.name
@ -82,7 +74,6 @@ internal class DefaultGetGroupDataTask @Inject constructor(
val userIds = groupUsers.users.map { it.userId } val userIds = groupUsers.users.map { it.userId }
groupSummaryEntity.userIds.clear() groupSummaryEntity.userIds.clear()
groupSummaryEntity.userIds.addAll(userIds) groupSummaryEntity.userIds.addAll(userIds)
} }
} }

View File

@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.group
import android.content.Context import android.content.Context
import androidx.work.CoroutineWorker import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import arrow.core.Try
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.internal.worker.SessionWorkerParams import im.vector.matrix.android.internal.worker.SessionWorkerParams
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
@ -43,16 +42,15 @@ internal class GetGroupDataWorker(context: Context, params: WorkerParameters) :
val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
sessionComponent.inject(this) sessionComponent.inject(this)
val results = params.groupIds.map { groupId -> val results = params.groupIds.map { groupId ->
fetchGroupData(groupId) runCatching { fetchGroupData(groupId) }
} }
val isSuccessful = results.none { it.isFailure() } val isSuccessful = results.none { it.isFailure }
return if (isSuccessful) Result.success() else Result.retry() return if (isSuccessful) Result.success() else Result.retry()
} }
private suspend fun fetchGroupData(groupId: String): Try<Unit> { private suspend fun fetchGroupData(groupId: String) {
return getGroupDataTask.execute(GetGroupDataTask.Params(groupId)) getGroupDataTask.execute(GetGroupDataTask.Params(groupId))
} }
} }

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.session.notification package im.vector.matrix.android.internal.session.notification
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
@ -41,48 +40,45 @@ internal class DefaultProcessEventForPushTask @Inject constructor(
private val sessionParams: SessionParams private val sessionParams: SessionParams
) : ProcessEventForPushTask { ) : ProcessEventForPushTask {
override suspend fun execute(params: ProcessEventForPushTask.Params) {
override suspend fun execute(params: ProcessEventForPushTask.Params): Try<Unit> { // Handle left rooms
return Try { params.syncResponse.leave.keys.forEach {
// Handle left rooms defaultPushRuleService.dispatchRoomLeft(it)
params.syncResponse.leave.keys.forEach {
defaultPushRuleService.dispatchRoomLeft(it)
}
val newJoinEvents = params.syncResponse.join
.map { entries ->
entries.value.timeline?.events?.map { it.copy(roomId = entries.key) }
}
.fold(emptyList<Event>(), { acc, next ->
acc + (next ?: emptyList())
})
val inviteEvents = params.syncResponse.invite
.map { entries ->
entries.value.inviteState?.events?.map { it.copy(roomId = entries.key) }
}
.fold(emptyList<Event>(), { acc, next ->
acc + (next ?: emptyList())
})
val allEvents = (newJoinEvents + inviteEvents).filter { event ->
when (event.type) {
EventType.MESSAGE,
EventType.REDACTION,
EventType.ENCRYPTED,
EventType.STATE_ROOM_MEMBER -> true
else -> false
}
}.filter {
it.senderId != sessionParams.credentials.userId
}
Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" +
" to check for push rules with ${params.rules.size} rules")
allEvents.forEach { event ->
fulfilledBingRule(event, params.rules)?.let {
Timber.v("[PushRules] Rule $it match for event ${event.eventId}")
defaultPushRuleService.dispatchBing(event, it)
}
}
defaultPushRuleService.dispatchFinish()
} }
val newJoinEvents = params.syncResponse.join
.map { entries ->
entries.value.timeline?.events?.map { it.copy(roomId = entries.key) }
}
.fold(emptyList<Event>(), { acc, next ->
acc + (next ?: emptyList())
})
val inviteEvents = params.syncResponse.invite
.map { entries ->
entries.value.inviteState?.events?.map { it.copy(roomId = entries.key) }
}
.fold(emptyList<Event>(), { acc, next ->
acc + (next ?: emptyList())
})
val allEvents = (newJoinEvents + inviteEvents).filter { event ->
when (event.type) {
EventType.MESSAGE,
EventType.REDACTION,
EventType.ENCRYPTED,
EventType.STATE_ROOM_MEMBER -> true
else -> false
}
}.filter {
it.senderId != sessionParams.credentials.userId
}
Timber.v("[PushRules] Found ${allEvents.size} out of ${(newJoinEvents + inviteEvents).size}" +
" to check for push rules with ${params.rules.size} rules")
allEvents.forEach { event ->
fulfilledBingRule(event, params.rules)?.let {
Timber.v("[PushRules] Rule $it match for event ${event.eventId}")
defaultPushRuleService.dispatchBing(event, it)
}
}
defaultPushRuleService.dispatchFinish()
} }
private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? { private fun fulfilledBingRule(event: Event, rules: List<PushRule>): PushRule? {

View File

@ -26,6 +26,7 @@ import im.vector.matrix.android.internal.database.mapper.toEntity
import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.util.awaitTransaction
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent import im.vector.matrix.android.internal.worker.getSessionComponent
import javax.inject.Inject import javax.inject.Inject
@ -55,15 +56,14 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
if (pusher.pushKey.isBlank()) { if (pusher.pushKey.isBlank()) {
return Result.failure() return Result.failure()
} }
return try {
val result = executeRequest<Unit> { setPusher(pusher, params.userId)
apiCall = pushersAPI.setPusher(pusher) Result.success()
} } catch (exception: Throwable) {
return result.fold({ when (exception) {
when (it) {
is Failure.NetworkConnection -> Result.retry() is Failure.NetworkConnection -> Result.retry()
else -> { else -> {
monarchy.runTransactionSync { realm -> monarchy.awaitTransaction { realm ->
PusherEntity.where(realm, params.userId, pusher.pushKey).findFirst()?.let { PusherEntity.where(realm, params.userId, pusher.pushKey).findFirst()?.let {
//update it //update it
it.state = PusherState.FAILED_TO_REGISTER it.state = PusherState.FAILED_TO_REGISTER
@ -73,28 +73,31 @@ internal class AddHttpPusherWorker(context: Context, params: WorkerParameters)
Result.failure() Result.failure()
} }
} }
}, { }
monarchy.runTransactionSync { realm -> }
val echo = PusherEntity.where(realm, params.userId, pusher.pushKey).findFirst()
if (echo != null) { private suspend fun setPusher(pusher: JsonPusher, userId: String) {
//update it executeRequest<Unit> {
echo.appDisplayName = pusher.appDisplayName apiCall = pushersAPI.setPusher(pusher)
echo.appId = pusher.appId }
echo.kind = pusher.kind monarchy.awaitTransaction { realm ->
echo.lang = pusher.lang val echo = PusherEntity.where(realm, userId, pusher.pushKey).findFirst()
echo.profileTag = pusher.profileTag if (echo != null) {
echo.data?.format = pusher.data?.format //update it
echo.data?.url = pusher.data?.url echo.appDisplayName = pusher.appDisplayName
echo.state = PusherState.REGISTERED echo.appId = pusher.appId
} else { echo.kind = pusher.kind
pusher.toEntity(params.userId).also { echo.lang = pusher.lang
it.state = PusherState.REGISTERED echo.profileTag = pusher.profileTag
realm.insertOrUpdate(it) echo.data?.format = pusher.data?.format
} echo.data?.url = pusher.data?.url
echo.state = PusherState.REGISTERED
} else {
pusher.toEntity(userId).also {
it.state = PusherState.REGISTERED
realm.insertOrUpdate(it)
} }
} }
Result.success() }
})
} }
} }

View File

@ -15,7 +15,6 @@
*/ */
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse import im.vector.matrix.android.api.pushrules.rest.GetPushRulesResponse
@ -24,7 +23,7 @@ import im.vector.matrix.android.internal.database.model.PushRulesEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields import im.vector.matrix.android.internal.database.model.PusherEntityFields
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.awaitTransaction
import javax.inject.Inject import javax.inject.Inject
@ -39,58 +38,57 @@ internal class DefaultGetPushRulesTask @Inject constructor(private val pushRules
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val sessionParams: SessionParams) : GetPushRulesTask { private val sessionParams: SessionParams) : GetPushRulesTask {
override suspend fun execute(params: GetPushRulesTask.Params): Try<Unit> { override suspend fun execute(params: GetPushRulesTask.Params) {
return executeRequest<GetPushRulesResponse> { val response = executeRequest<GetPushRulesResponse> {
apiCall = pushRulesApi.getAllRules() apiCall = pushRulesApi.getAllRules()
}.flatMap { response -> }
val scope = params.scope val scope = params.scope
return monarchy.tryTransactionSync { realm -> monarchy.awaitTransaction { realm ->
//clear existings? //clear existings?
//TODO //TODO
realm.where(PushRulesEntity::class.java) realm.where(PushRulesEntity::class.java)
.equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId) .equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId)
.findAll().deleteAllFromRealm() .findAll().deleteAllFromRealm()
val content = PushRulesEntity(sessionParams.credentials.userId, scope, "content") val content = PushRulesEntity(sessionParams.credentials.userId, scope, "content")
response.global.content?.forEach { rule -> response.global.content?.forEach { rule ->
PushRulesMapper.map(rule).also { PushRulesMapper.map(rule).also {
content.pushRules.add(it) content.pushRules.add(it)
}
} }
realm.insertOrUpdate(content)
val override = PushRulesEntity(sessionParams.credentials.userId, scope, "override")
response.global.override?.forEach { rule ->
PushRulesMapper.map(rule).also {
override.pushRules.add(it)
}
}
realm.insertOrUpdate(override)
val rooms = PushRulesEntity(sessionParams.credentials.userId, scope, "room")
response.global.room?.forEach { rule ->
PushRulesMapper.map(rule).also {
rooms.pushRules.add(it)
}
}
realm.insertOrUpdate(rooms)
val senders = PushRulesEntity(sessionParams.credentials.userId, scope, "sender")
response.global.sender?.forEach { rule ->
PushRulesMapper.map(rule).also {
senders.pushRules.add(it)
}
}
realm.insertOrUpdate(senders)
val underrides = PushRulesEntity(sessionParams.credentials.userId, scope, "underride")
response.global.underride?.forEach { rule ->
PushRulesMapper.map(rule).also {
underrides.pushRules.add(it)
}
}
realm.insertOrUpdate(underrides)
} }
realm.insertOrUpdate(content)
val override = PushRulesEntity(sessionParams.credentials.userId, scope, "override")
response.global.override?.forEach { rule ->
PushRulesMapper.map(rule).also {
override.pushRules.add(it)
}
}
realm.insertOrUpdate(override)
val rooms = PushRulesEntity(sessionParams.credentials.userId, scope, "room")
response.global.room?.forEach { rule ->
PushRulesMapper.map(rule).also {
rooms.pushRules.add(it)
}
}
realm.insertOrUpdate(rooms)
val senders = PushRulesEntity(sessionParams.credentials.userId, scope, "sender")
response.global.sender?.forEach { rule ->
PushRulesMapper.map(rule).also {
senders.pushRules.add(it)
}
}
realm.insertOrUpdate(senders)
val underrides = PushRulesEntity(sessionParams.credentials.userId, scope, "underride")
response.global.underride?.forEach { rule ->
PushRulesMapper.map(rule).also {
underrides.pushRules.add(it)
}
}
realm.insertOrUpdate(underrides)
} }
} }
} }

View File

@ -15,7 +15,6 @@
*/ */
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.SessionParams import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.pushers.PusherState import im.vector.matrix.android.api.session.pushers.PusherState
@ -24,7 +23,7 @@ import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.model.PusherEntityFields import im.vector.matrix.android.internal.database.model.PusherEntityFields
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.awaitTransaction
import javax.inject.Inject import javax.inject.Inject
internal interface GetPushersTask : Task<Unit, Unit> internal interface GetPushersTask : Task<Unit, Unit>
@ -33,20 +32,19 @@ internal class DefaultGetPusherTask @Inject constructor(private val pushersAPI:
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val sessionParams: SessionParams) : GetPushersTask { private val sessionParams: SessionParams) : GetPushersTask {
override suspend fun execute(params: Unit): Try<Unit> { override suspend fun execute(params: Unit) {
return executeRequest<GetPushersResponse> { val response = executeRequest<GetPushersResponse> {
apiCall = pushersAPI.getPushers() apiCall = pushersAPI.getPushers()
}.flatMap { response -> }
monarchy.tryTransactionSync { realm -> monarchy.awaitTransaction { realm ->
//clear existings? //clear existings?
realm.where(PusherEntity::class.java) realm.where(PusherEntity::class.java)
.equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId) .equalTo(PusherEntityFields.USER_ID, sessionParams.credentials.userId)
.findAll().deleteAllFromRealm() .findAll().deleteAllFromRealm()
response.pushers?.forEach { jsonPusher -> response.pushers?.forEach { jsonPusher ->
jsonPusher.toEntity(sessionParams.credentials.userId).also { jsonPusher.toEntity(sessionParams.credentials.userId).also {
it.state = PusherState.REGISTERED it.state = PusherState.REGISTERED
realm.insertOrUpdate(it) realm.insertOrUpdate(it)
}
} }
} }
} }

View File

@ -16,16 +16,16 @@
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.pushers.Pusher
import im.vector.matrix.android.api.session.pushers.PusherState import im.vector.matrix.android.api.session.pushers.PusherState
import im.vector.matrix.android.internal.database.awaitTransaction
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.PusherEntity import im.vector.matrix.android.internal.database.model.PusherEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm
import javax.inject.Inject import javax.inject.Inject
internal interface RemovePusherTask : Task<RemovePusherTask.Params, Unit> { internal interface RemovePusherTask : Task<RemovePusherTask.Params, Unit> {
@ -39,42 +39,32 @@ internal class DefaultRemovePusherTask @Inject constructor(
private val monarchy: Monarchy private val monarchy: Monarchy
) : RemovePusherTask { ) : RemovePusherTask {
override suspend fun execute(params: RemovePusherTask.Params): Try<Unit> { override suspend fun execute(params: RemovePusherTask.Params) {
return Try { val existing = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
var existing: Pusher? = null val existingEntity = PusherEntity.where(realm, params.userId, params.pushKey).findFirst()
monarchy.runTransactionSync { realm.awaitTransaction {
val existingEntity = PusherEntity.where(it, params.userId, params.pushKey).findFirst() existingEntity?.state = PusherState.UNREGISTERING
existingEntity?.state == PusherState.UNREGISTERING
existing = existingEntity?.asDomain()
}
if (existing == null) {
throw Exception("No existing pusher")
} else {
existing!!
}
}.flatMap {
executeRequest<Unit> {
val deleteBody = JsonPusher(
pushKey = params.pushKey,
appId = params.pushAppId,
// kind null deletes the pusher
kind = null,
appDisplayName = it.appDisplayName ?: "",
deviceDisplayName = it.deviceDisplayName ?: "",
profileTag = it.profileTag ?: "",
lang = it.lang,
data = JsonPusherData(it.data.url, it.data.format),
append = false
)
apiCall = pushersAPI.setPusher(deleteBody)
}
}.flatMap {
monarchy.tryTransactionSync {
val existing = PusherEntity.where(it, params.userId, params.pushKey).findFirst()
existing?.deleteFromRealm()
} }
existingEntity?.asDomain()
} ?: throw Exception("No existing pusher")
val deleteBody = JsonPusher(
pushKey = params.pushKey,
appId = params.pushAppId,
// kind null deletes the pusher
kind = null,
appDisplayName = existing.appDisplayName ?: "",
deviceDisplayName = existing.deviceDisplayName ?: "",
profileTag = existing.profileTag ?: "",
lang = existing.lang,
data = JsonPusherData(existing.data.url, existing.data.format),
append = false
)
executeRequest<Unit> {
apiCall = pushersAPI.setPusher(deleteBody)
}
monarchy.awaitTransaction {
PusherEntity.where(it, params.userId, params.pushKey).findFirst()?.deleteFromRealm()
} }
} }
} }

View File

@ -15,7 +15,6 @@
*/ */
package im.vector.matrix.android.internal.session.pushers package im.vector.matrix.android.internal.session.pushers
import arrow.core.Try
import im.vector.matrix.android.api.pushrules.rest.PushRule import im.vector.matrix.android.api.pushrules.rest.PushRule
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
@ -31,7 +30,7 @@ internal interface UpdatePushRuleEnableStatusTask : Task<UpdatePushRuleEnableSta
internal class DefaultUpdatePushRuleEnableStatusTask @Inject constructor(private val pushRulesApi: PushRulesApi) internal class DefaultUpdatePushRuleEnableStatusTask @Inject constructor(private val pushRulesApi: PushRulesApi)
: UpdatePushRuleEnableStatusTask { : UpdatePushRuleEnableStatusTask {
override suspend fun execute(params: UpdatePushRuleEnableStatusTask.Params): Try<Unit> { override suspend fun execute(params: UpdatePushRuleEnableStatusTask.Params) {
return executeRequest { return executeRequest {
apiCall = pushRulesApi.updateEnableRuleStatus(params.kind, params.pushRule.ruleId, params.enabled) apiCall = pushRulesApi.updateEnableRuleStatus(params.kind, params.pushRule.ruleId, params.enabled)
} }

View File

@ -34,7 +34,7 @@ import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask import im.vector.matrix.android.internal.session.room.membership.joining.JoinRoomTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.fetchManaged import io.realm.Realm
import javax.inject.Inject import javax.inject.Inject
internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy, internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy,
@ -52,8 +52,13 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
} }
override fun getRoom(roomId: String): Room? { override fun getRoom(roomId: String): Room? {
monarchy.fetchManaged { RoomEntity.where(it, roomId).findFirst() } ?: return null return Realm.getInstance(monarchy.realmConfiguration).use {
return roomFactory.create(roomId) if (RoomEntity.where(it, roomId).findFirst() != null) {
roomFactory.create(roomId)
} else {
null
}
}
} }
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> { override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {

View File

@ -15,7 +15,6 @@
*/ */
package im.vector.matrix.android.internal.session.room package im.vector.matrix.android.internal.session.room
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.crypto.MXCryptoError import im.vector.matrix.android.api.session.crypto.MXCryptoError
@ -31,7 +30,7 @@ import im.vector.matrix.android.internal.database.query.create
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm import io.realm.Realm
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -54,10 +53,10 @@ internal class DefaultEventRelationsAggregationTask @Inject constructor(
//OPT OUT serer aggregation until API mature enough //OPT OUT serer aggregation until API mature enough
private val SHOULD_HANDLE_SERVER_AGREGGATION = false private val SHOULD_HANDLE_SERVER_AGREGGATION = false
override suspend fun execute(params: EventRelationsAggregationTask.Params): Try<Unit> { override suspend fun execute(params: EventRelationsAggregationTask.Params) {
val events = params.events val events = params.events
val userId = params.userId val userId = params.userId
return monarchy.tryTransactionSync { realm -> monarchy.awaitTransaction { realm ->
Timber.v(">>> DefaultEventRelationsAggregationTask[${params.hashCode()}] called with ${events.size} events") Timber.v(">>> DefaultEventRelationsAggregationTask[${params.hashCode()}] called with ${events.size} events")
update(realm, events, userId) update(realm, events, userId)
Timber.v("<<< DefaultEventRelationsAggregationTask[${params.hashCode()}] finished") Timber.v("<<< DefaultEventRelationsAggregationTask[${params.hashCode()}] finished")

View File

@ -16,8 +16,6 @@
package im.vector.matrix.android.internal.session.room.create package im.vector.matrix.android.internal.session.room.create
import arrow.core.Try
import arrow.core.recoverWith
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
@ -34,7 +32,7 @@ import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper import im.vector.matrix.android.internal.session.user.accountdata.DirectChatsHelper
import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask import im.vector.matrix.android.internal.session.user.accountdata.UpdateUserAccountDataTask
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.RealmConfiguration import io.realm.RealmConfiguration
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -46,60 +44,49 @@ internal class DefaultCreateRoomTask @Inject constructor(private val roomAPI: Ro
private val directChatsHelper: DirectChatsHelper, private val directChatsHelper: DirectChatsHelper,
private val updateUserAccountDataTask: UpdateUserAccountDataTask, private val updateUserAccountDataTask: UpdateUserAccountDataTask,
private val readMarkersTask: SetReadMarkersTask, private val readMarkersTask: SetReadMarkersTask,
@SessionDatabase private val realmConfiguration: RealmConfiguration) : CreateRoomTask { @SessionDatabase
private val realmConfiguration: RealmConfiguration) : CreateRoomTask {
override suspend fun execute(params: CreateRoomParams): Try<String> { override suspend fun execute(params: CreateRoomParams): String {
return executeRequest<CreateRoomResponse> { val createRoomResponse = executeRequest<CreateRoomResponse> {
apiCall = roomAPI.createRoom(params) apiCall = roomAPI.createRoom(params)
}.flatMap { createRoomResponse ->
val roomId = createRoomResponse.roomId!!
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
realm.where(RoomEntity::class.java)
.equalTo(RoomEntityFields.ROOM_ID, roomId)
}
try {
rql.await(timeout = 1L, timeUnit = TimeUnit.MINUTES)
Try.just(roomId)
} catch (exception: Exception) {
Try.raise<String>(CreateRoomFailure.CreatedWithTimeout)
}
}.flatMap { roomId ->
if (params.isDirect()) {
handleDirectChatCreation(params, roomId)
} else {
Try.just(roomId)
}
}.flatMap { roomId ->
setReadMarkers(roomId)
} }
val roomId = createRoomResponse.roomId!!
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
realm.where(RoomEntity::class.java)
.equalTo(RoomEntityFields.ROOM_ID, roomId)
}
try {
rql.await(timeout = 1L, timeUnit = TimeUnit.MINUTES)
} catch (exception: Exception) {
throw CreateRoomFailure.CreatedWithTimeout
}
if (params.isDirect()) {
handleDirectChatCreation(params, roomId)
}
setReadMarkers(roomId)
return roomId
} }
private suspend fun handleDirectChatCreation(params: CreateRoomParams, roomId: String): Try<String> { private suspend fun handleDirectChatCreation(params: CreateRoomParams, roomId: String) {
val otherUserId = params.getFirstInvitedUserId() val otherUserId = params.getFirstInvitedUserId()
?: return Try.raise(IllegalStateException("You can't create a direct room without an invitedUser")) ?: throw IllegalStateException("You can't create a direct room without an invitedUser")
return monarchy.tryTransactionSync { realm -> monarchy.awaitTransaction { realm ->
RoomSummaryEntity.where(realm, roomId).findFirst()?.apply { RoomSummaryEntity.where(realm, roomId).findFirst()?.apply {
this.directUserId = otherUserId this.directUserId = otherUserId
this.isDirect = true this.isDirect = true
} }
}.flatMap {
val directChats = directChatsHelper.getLocalUserAccount()
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats))
}.flatMap {
Try.just(roomId)
} }
val directChats = directChatsHelper.getLocalUserAccount()
updateUserAccountDataTask.execute(UpdateUserAccountDataTask.DirectChatParams(directMessages = directChats))
} }
private suspend fun setReadMarkers(roomId: String): Try<String> { private suspend fun setReadMarkers(roomId: String) {
val setReadMarkerParams = SetReadMarkersTask.Params(roomId, markAllAsRead = true) val setReadMarkerParams = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
return readMarkersTask return readMarkersTask.execute(setReadMarkerParams)
.execute(setReadMarkerParams)
.flatMap {
Try.just(roomId)
}
} }
} }

View File

@ -16,11 +16,9 @@
package im.vector.matrix.android.internal.session.room.directory package im.vector.matrix.android.internal.session.room.directory
import arrow.core.Try
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsParams
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoomsResponse
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -34,7 +32,7 @@ internal interface GetPublicRoomTask : Task<GetPublicRoomTask.Params, PublicRoom
internal class DefaultGetPublicRoomTask @Inject constructor(private val roomAPI: RoomAPI) : GetPublicRoomTask { internal class DefaultGetPublicRoomTask @Inject constructor(private val roomAPI: RoomAPI) : GetPublicRoomTask {
override suspend fun execute(params: GetPublicRoomTask.Params): Try<PublicRoomsResponse> { override suspend fun execute(params: GetPublicRoomTask.Params): PublicRoomsResponse {
return executeRequest { return executeRequest {
apiCall = roomAPI.publicRooms(params.server, params.publicRoomsParams) apiCall = roomAPI.publicRooms(params.server, params.publicRoomsParams)
} }

View File

@ -16,19 +16,17 @@
package im.vector.matrix.android.internal.session.room.directory package im.vector.matrix.android.internal.session.room.directory
import arrow.core.Try
import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol import im.vector.matrix.android.api.session.room.model.thirdparty.ThirdPartyProtocol
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
internal interface GetThirdPartyProtocolsTask : Task<Unit, Map<String, ThirdPartyProtocol>> internal interface GetThirdPartyProtocolsTask : Task<Unit, Map<String, ThirdPartyProtocol>>
internal class DefaultGetThirdPartyProtocolsTask @Inject constructor (private val roomAPI: RoomAPI) : GetThirdPartyProtocolsTask { internal class DefaultGetThirdPartyProtocolsTask @Inject constructor(private val roomAPI: RoomAPI) : GetThirdPartyProtocolsTask {
override suspend fun execute(params: Unit): Try<Map<String, ThirdPartyProtocol>> { override suspend fun execute(params: Unit): Map<String, ThirdPartyProtocol> {
return executeRequest { return executeRequest {
apiCall = roomAPI.thirdPartyProtocols() apiCall = roomAPI.thirdPartyProtocols()
} }

View File

@ -42,7 +42,7 @@ internal class DefaultMembershipService @Inject constructor(private val roomId:
private val leaveRoomTask: LeaveRoomTask private val leaveRoomTask: LeaveRoomTask
) : MembershipService { ) : MembershipService {
override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Boolean>): Cancelable { override fun loadRoomMembersIfNeeded(matrixCallback: MatrixCallback<Unit>): Cancelable {
val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE) val params = LoadRoomMembersTask.Params(roomId, Membership.LEAVE)
return loadRoomMembersTask.configureWith(params) return loadRoomMembersTask.configureWith(params)
.dispatchTo(matrixCallback) .dispatchTo(matrixCallback)

View File

@ -16,8 +16,6 @@
package im.vector.matrix.android.internal.session.room.membership package im.vector.matrix.android.internal.session.room.membership
import arrow.core.Try
import com.squareup.moshi.JsonReader
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.internal.database.helper.addStateEvent import im.vector.matrix.android.internal.database.helper.addStateEvent
@ -30,14 +28,12 @@ import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.session.sync.SyncTokenStore import im.vector.matrix.android.internal.session.sync.SyncTokenStore
import im.vector.matrix.android.internal.session.user.UserEntityFactory import im.vector.matrix.android.internal.session.user.UserEntityFactory
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm import io.realm.Realm
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import okhttp3.ResponseBody
import okio.Okio
import javax.inject.Inject import javax.inject.Inject
internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Boolean> { internal interface LoadRoomMembersTask : Task<LoadRoomMembersTask.Params, Unit> {
data class Params( data class Params(
val roomId: String, val roomId: String,
@ -51,39 +47,36 @@ internal class DefaultLoadRoomMembersTask @Inject constructor(private val roomAP
private val roomSummaryUpdater: RoomSummaryUpdater private val roomSummaryUpdater: RoomSummaryUpdater
) : LoadRoomMembersTask { ) : LoadRoomMembersTask {
override suspend fun execute(params: LoadRoomMembersTask.Params): Try<Boolean> { override suspend fun execute(params: LoadRoomMembersTask.Params) {
return if (areAllMembersAlreadyLoaded(params.roomId)) { if (areAllMembersAlreadyLoaded(params.roomId)) {
Try.just(true) return
} else {
val lastToken = syncTokenStore.getLastToken()
executeRequest<RoomMembersResponse> {
apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value)
}.flatMap { response ->
insertInDb(response, params.roomId)
}.map { true }
} }
val lastToken = syncTokenStore.getLastToken()
val response = executeRequest<RoomMembersResponse> {
apiCall = roomAPI.getMembers(params.roomId, lastToken, null, params.excludeMembership?.value)
}
insertInDb(response, params.roomId)
} }
private fun insertInDb(response: RoomMembersResponse, roomId: String): Try<Unit> { private suspend fun insertInDb(response: RoomMembersResponse, roomId: String) {
return monarchy monarchy.awaitTransaction { realm ->
.tryTransactionSync { realm -> // We ignore all the already known members
// We ignore all the already known members val roomEntity = RoomEntity.where(realm, roomId).findFirst()
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId)
?: realm.createObject(roomId)
for (roomMemberEvent in response.roomMemberEvents) { for (roomMemberEvent in response.roomMemberEvents) {
roomEntity.addStateEvent(roomMemberEvent) roomEntity.addStateEvent(roomMemberEvent)
UserEntityFactory.createOrNull(roomMemberEvent)?.also { UserEntityFactory.createOrNull(roomMemberEvent)?.also {
realm.insertOrUpdate(it) realm.insertOrUpdate(it)
}
}
roomEntity.chunks.flatMap { it.timelineEvents }.forEach {
it.updateSenderData()
}
roomEntity.areAllMembersLoaded = true
roomSummaryUpdater.update(realm, roomId)
} }
}
roomEntity.chunks.flatMap { it.timelineEvents }.forEach {
it.updateSenderData()
}
roomEntity.areAllMembersLoaded = true
roomSummaryUpdater.update(realm, roomId)
}
} }
private fun areAllMembersAlreadyLoaded(roomId: String): Boolean { private fun areAllMembersAlreadyLoaded(roomId: String): Boolean {

View File

@ -16,9 +16,7 @@
package im.vector.matrix.android.internal.session.room.membership.joining package im.vector.matrix.android.internal.session.room.membership.joining
import arrow.core.Try
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -33,7 +31,7 @@ internal interface InviteTask : Task<InviteTask.Params, Unit> {
internal class DefaultInviteTask @Inject constructor(private val roomAPI: RoomAPI) : InviteTask { internal class DefaultInviteTask @Inject constructor(private val roomAPI: RoomAPI) : InviteTask {
override suspend fun execute(params: InviteTask.Params): Try<Unit> { override suspend fun execute(params: InviteTask.Params) {
return executeRequest { return executeRequest {
val body = InviteBody(params.userId) val body = InviteBody(params.userId)
apiCall = roomAPI.invite(params.roomId, body) apiCall = roomAPI.invite(params.roomId, body)

View File

@ -16,8 +16,6 @@
package im.vector.matrix.android.internal.session.room.membership.joining package im.vector.matrix.android.internal.session.room.membership.joining
import arrow.core.Try
import im.vector.matrix.android.api.session.room.failure.CreateRoomFailure
import im.vector.matrix.android.api.session.room.failure.JoinRoomFailure import im.vector.matrix.android.api.session.room.failure.JoinRoomFailure
import im.vector.matrix.android.internal.database.RealmQueryLatch import im.vector.matrix.android.internal.database.RealmQueryLatch
import im.vector.matrix.android.internal.database.model.RoomEntity import im.vector.matrix.android.internal.database.model.RoomEntity
@ -40,32 +38,30 @@ internal interface JoinRoomTask : Task<JoinRoomTask.Params, Unit> {
internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: RoomAPI, internal class DefaultJoinRoomTask @Inject constructor(private val roomAPI: RoomAPI,
private val readMarkersTask: SetReadMarkersTask, private val readMarkersTask: SetReadMarkersTask,
@SessionDatabase private val realmConfiguration: RealmConfiguration) : JoinRoomTask { @SessionDatabase
private val realmConfiguration: RealmConfiguration) : JoinRoomTask {
override suspend fun execute(params: JoinRoomTask.Params): Try<Unit> { override suspend fun execute(params: JoinRoomTask.Params) {
return executeRequest<Unit> { executeRequest<Unit> {
apiCall = roomAPI.join(params.roomId, params.viaServers, HashMap()) apiCall = roomAPI.join(params.roomId, params.viaServers, HashMap())
}.flatMap {
val roomId = params.roomId
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
realm.where(RoomEntity::class.java)
.equalTo(RoomEntityFields.ROOM_ID, roomId)
}
try {
rql.await(timeout = 1L, timeUnit = TimeUnit.MINUTES)
Try.just(roomId)
} catch (exception: Exception) {
Try.raise<String>(JoinRoomFailure.JoinedWithTimeout)
}
}.flatMap { roomId ->
setReadMarkers(roomId)
} }
val roomId = params.roomId
// Wait for room to come back from the sync (but it can maybe be in the DB is the sync response is received before)
val rql = RealmQueryLatch<RoomEntity>(realmConfiguration) { realm ->
realm.where(RoomEntity::class.java)
.equalTo(RoomEntityFields.ROOM_ID, roomId)
}
try {
rql.await(timeout = 1L, timeUnit = TimeUnit.MINUTES)
} catch (exception: Exception) {
throw JoinRoomFailure.JoinedWithTimeout
}
setReadMarkers(roomId)
} }
private suspend fun setReadMarkers(roomId: String): Try<Unit> { private suspend fun setReadMarkers(roomId: String) {
val setReadMarkerParams = SetReadMarkersTask.Params(roomId, markAllAsRead = true) val setReadMarkerParams = SetReadMarkersTask.Params(roomId, markAllAsRead = true)
return readMarkersTask.execute(setReadMarkerParams) readMarkersTask.execute(setReadMarkerParams)
} }
} }

View File

@ -16,9 +16,7 @@
package im.vector.matrix.android.internal.session.room.membership.leaving package im.vector.matrix.android.internal.session.room.membership.leaving
import arrow.core.Try
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -31,7 +29,7 @@ internal interface LeaveRoomTask : Task<LeaveRoomTask.Params, Unit> {
internal class DefaultLeaveRoomTask @Inject constructor(private val roomAPI: RoomAPI) : LeaveRoomTask { internal class DefaultLeaveRoomTask @Inject constructor(private val roomAPI: RoomAPI) : LeaveRoomTask {
override suspend fun execute(params: LeaveRoomTask.Params): Try<Unit> { override suspend fun execute(params: LeaveRoomTask.Params) {
return executeRequest { return executeRequest {
apiCall = roomAPI.leave(params.roomId, HashMap()) apiCall = roomAPI.leave(params.roomId, HashMap())
} }

View File

@ -15,12 +15,10 @@
*/ */
package im.vector.matrix.android.internal.session.room.prune package im.vector.matrix.android.internal.session.room.prune
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.UnsignedData import im.vector.matrix.android.api.session.events.model.UnsignedData
import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.helper.updateSenderData import im.vector.matrix.android.internal.database.helper.updateSenderData
import im.vector.matrix.android.internal.database.mapper.ContentMapper import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.mapper.EventMapper import im.vector.matrix.android.internal.database.mapper.EventMapper
@ -31,7 +29,7 @@ import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.di.MoshiProvider import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.Realm import io.realm.Realm
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -48,8 +46,8 @@ internal interface PruneEventTask : Task<PruneEventTask.Params, Unit> {
internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy) : PruneEventTask { internal class DefaultPruneEventTask @Inject constructor(private val monarchy: Monarchy) : PruneEventTask {
override suspend fun execute(params: PruneEventTask.Params): Try<Unit> { override suspend fun execute(params: PruneEventTask.Params) {
return monarchy.tryTransactionSync { realm -> monarchy.awaitTransaction { realm ->
params.redactionEvents.forEach { event -> params.redactionEvents.forEach { event ->
pruneEvent(realm, event, params.userId) pruneEvent(realm, event, params.userId)
} }

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.session.room.read package im.vector.matrix.android.internal.session.room.read
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
@ -31,7 +30,6 @@ import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory import im.vector.matrix.android.internal.session.room.send.LocalEchoEventFactory
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionAsync
import io.realm.Realm import io.realm.Realm
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -54,7 +52,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
private val monarchy: Monarchy private val monarchy: Monarchy
) : SetReadMarkersTask { ) : SetReadMarkersTask {
override suspend fun execute(params: SetReadMarkersTask.Params): Try<Unit> { override suspend fun execute(params: SetReadMarkersTask.Params) {
val markers = HashMap<String, String>() val markers = HashMap<String, String>()
val fullyReadEventId: String? val fullyReadEventId: String?
val readReceiptEventId: String? val readReceiptEventId: String?
@ -78,7 +76,7 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
} }
} }
if (readReceiptEventId != null if (readReceiptEventId != null
&& !isEventRead(params.roomId, readReceiptEventId)) { && !isEventRead(params.roomId, readReceiptEventId)) {
if (LocalEchoEventFactory.isLocalEchoId(readReceiptEventId)) { if (LocalEchoEventFactory.isLocalEchoId(readReceiptEventId)) {
Timber.w("Can't set read receipt for local event ${params.fullyReadEventId}") Timber.w("Can't set read receipt for local event ${params.fullyReadEventId}")
@ -87,21 +85,20 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
markers[READ_RECEIPT] = readReceiptEventId markers[READ_RECEIPT] = readReceiptEventId
} }
} }
return if (markers.isEmpty()) { if (markers.isEmpty()) {
Try.just(Unit) return
} else { }
executeRequest { executeRequest<Unit> {
apiCall = roomAPI.sendReadMarker(params.roomId, markers) apiCall = roomAPI.sendReadMarker(params.roomId, markers)
}
} }
} }
private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) { private fun updateNotificationCountIfNecessary(roomId: String, eventId: String) {
monarchy.tryTransactionAsync { realm -> monarchy.writeAsync { realm ->
val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == eventId val isLatestReceived = TimelineEventEntity.latestEvent(realm, roomId = roomId, includesSending = false)?.eventId == eventId
if (isLatestReceived) { if (isLatestReceived) {
val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst() val roomSummary = RoomSummaryEntity.where(realm, roomId).findFirst()
?: return@tryTransactionAsync ?: return@writeAsync
roomSummary.notificationCount = 0 roomSummary.notificationCount = 0
roomSummary.highlightCount = 0 roomSummary.highlightCount = 0
} }
@ -112,13 +109,13 @@ internal class DefaultSetReadMarkersTask @Inject constructor(private val roomAPI
var isEventRead = false var isEventRead = false
monarchy.doWithRealm { monarchy.doWithRealm {
val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst() val readReceipt = ReadReceiptEntity.where(it, roomId, credentials.userId).findFirst()
?: return@doWithRealm ?: return@doWithRealm
val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId) val liveChunk = ChunkEntity.findLastLiveChunkFromRoom(it, roomId)
?: return@doWithRealm ?: return@doWithRealm
val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex val readReceiptIndex = liveChunk.timelineEvents.find(readReceipt.eventId)?.root?.displayIndex
?: Int.MIN_VALUE ?: Int.MIN_VALUE
val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex val eventToCheckIndex = liveChunk.timelineEvents.find(eventId)?.root?.displayIndex
?: Int.MAX_VALUE ?: Int.MAX_VALUE
isEventRead = eventToCheckIndex <= readReceiptIndex isEventRead = eventToCheckIndex <= readReceiptIndex
} }
return isEventRead return isEventRead

View File

@ -43,7 +43,6 @@ import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEvent
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.CancelableWork import im.vector.matrix.android.internal.util.CancelableWork
import im.vector.matrix.android.internal.util.tryTransactionAsync
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -223,9 +222,9 @@ internal class DefaultRelationService @Inject constructor(private val context: C
* the same transaction id is received (in unsigned data) * the same transaction id is received (in unsigned data)
*/ */
private fun saveLocalEcho(event: Event) { private fun saveLocalEcho(event: Event) {
monarchy.tryTransactionAsync { realm -> monarchy.writeAsync { realm ->
val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId = roomId).findFirst()
?: return@tryTransactionAsync ?: return@writeAsync
roomEntity.addSendingEvent(event) roomEntity.addSendingEvent(event)
} }
} }

View File

@ -15,7 +15,6 @@
*/ */
package im.vector.matrix.android.internal.session.room.relation package im.vector.matrix.android.internal.session.room.relation
import arrow.core.Try
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.EventType import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.RelationType import im.vector.matrix.android.api.session.events.model.RelationType
@ -39,16 +38,16 @@ internal class DefaultFetchEditHistoryTask @Inject constructor(
private val roomAPI: RoomAPI private val roomAPI: RoomAPI
) : FetchEditHistoryTask { ) : FetchEditHistoryTask {
override suspend fun execute(params: FetchEditHistoryTask.Params): Try<List<Event>> { override suspend fun execute(params: FetchEditHistoryTask.Params): List<Event> {
return executeRequest<RelationsResponse> { val response = executeRequest<RelationsResponse> {
apiCall = roomAPI.getRelations(params.roomId, apiCall = roomAPI.getRelations(params.roomId,
params.eventId, params.eventId,
RelationType.REPLACE, RelationType.REPLACE,
if (params.isRoomEncrypted) EventType.ENCRYPTED else EventType.MESSAGE) if (params.isRoomEncrypted) EventType.ENCRYPTED else EventType.MESSAGE)
}.map { resp ->
val events = resp.chunks.toMutableList()
resp.originalEvent?.let { events.add(it) }
events
} }
val events = response.chunks.toMutableList()
response.originalEvent?.let { events.add(it) }
return events
} }
} }

View File

@ -15,13 +15,11 @@
*/ */
package im.vector.matrix.android.internal.session.room.relation package im.vector.matrix.android.internal.session.room.relation
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import io.realm.Realm import io.realm.Realm
import javax.inject.Inject import javax.inject.Inject
@ -44,14 +42,11 @@ internal interface FindReactionEventForUndoTask : Task<FindReactionEventForUndoT
internal class DefaultFindReactionEventForUndoTask @Inject constructor(private val monarchy: Monarchy) : FindReactionEventForUndoTask { internal class DefaultFindReactionEventForUndoTask @Inject constructor(private val monarchy: Monarchy) : FindReactionEventForUndoTask {
override suspend fun execute(params: FindReactionEventForUndoTask.Params): Try<FindReactionEventForUndoTask.Result> { override suspend fun execute(params: FindReactionEventForUndoTask.Params): FindReactionEventForUndoTask.Result {
return Try { val eventId = Realm.getInstance(monarchy.realmConfiguration).use { realm ->
var eventId: String? = null getReactionToRedact(realm, params.reaction, params.eventId, params.myUserId)?.eventId
monarchy.doWithRealm { realm ->
eventId = getReactionToRedact(realm, params.reaction, params.eventId, params.myUserId)?.eventId
}
FindReactionEventForUndoTask.Result(eventId)
} }
return FindReactionEventForUndoTask.Result(eventId)
} }
private fun getReactionToRedact(realm: Realm, reaction: String, eventId: String, userId: String): EventEntity? { private fun getReactionToRedact(realm: Realm, reaction: String, eventId: String, userId: String): EventEntity? {

View File

@ -66,18 +66,11 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) :
val relatedEventId = relationContent.relatesTo?.eventId ?: return Result.failure() val relatedEventId = relationContent.relatesTo?.eventId ?: return Result.failure()
val relationType = (relationContent.relatesTo as? ReactionInfo)?.type ?: params.relationType val relationType = (relationContent.relatesTo as? ReactionInfo)?.type ?: params.relationType
?: return Result.failure() ?: return Result.failure()
return try {
val result = executeRequest<SendResponse> { sendRelation(params.roomId, relationType, relatedEventId, localEvent)
apiCall = roomAPI.sendRelation( Result.success()
roomId = params.roomId, } catch (exception: Throwable) {
parent_id = relatedEventId, when (exception) {
relationType = relationType,
eventType = localEvent.type,
content = localEvent.content
)
}
return result.fold({
when (it) {
is Failure.NetworkConnection -> Result.retry() is Failure.NetworkConnection -> Result.retry()
else -> { else -> {
//TODO mark as failed to send? //TODO mark as failed to send?
@ -85,7 +78,19 @@ internal class SendRelationWorker(context: Context, params: WorkerParameters) :
Result.success() Result.success()
} }
} }
}, { Result.success() }) }
}
private suspend fun sendRelation(roomId: String, relationType: String, relatedEventId: String, localEvent: Event) {
executeRequest<SendResponse> {
apiCall = roomAPI.sendRelation(
roomId = roomId,
parent_id = relatedEventId,
relationType = relationType,
eventType = localEvent.type,
content = localEvent.content
)
}
} }
} }

View File

@ -15,13 +15,11 @@
*/ */
package im.vector.matrix.android.internal.session.room.relation package im.vector.matrix.android.internal.session.room.relation
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity import im.vector.matrix.android.internal.database.model.EventAnnotationsSummaryEntity
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields import im.vector.matrix.android.internal.database.model.ReactionAggregatedSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import io.realm.Realm import io.realm.Realm
import javax.inject.Inject import javax.inject.Inject
@ -44,14 +42,13 @@ internal interface UpdateQuickReactionTask : Task<UpdateQuickReactionTask.Params
} }
internal class DefaultUpdateQuickReactionTask @Inject constructor(private val monarchy: Monarchy) : UpdateQuickReactionTask { internal class DefaultUpdateQuickReactionTask @Inject constructor(private val monarchy: Monarchy) : UpdateQuickReactionTask {
override suspend fun execute(params: UpdateQuickReactionTask.Params): Try<UpdateQuickReactionTask.Result> {
return Try { override suspend fun execute(params: UpdateQuickReactionTask.Params): UpdateQuickReactionTask.Result {
var res: Pair<String?, List<String>?>? = null var res: Pair<String?, List<String>?>? = null
monarchy.doWithRealm { realm -> monarchy.doWithRealm { realm ->
res = updateQuickReaction(realm, params.reaction, params.oppositeReaction, params.eventId, params.myUserId) res = updateQuickReaction(realm, params.reaction, params.oppositeReaction, params.eventId, params.myUserId)
}
UpdateQuickReactionTask.Result(res?.first, res?.second ?: emptyList())
} }
return UpdateQuickReactionTask.Result(res?.first, res?.second ?: emptyList())
} }

View File

@ -41,7 +41,6 @@ import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.content.UploadContentWorker import im.vector.matrix.android.internal.session.content.UploadContentWorker
import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon import im.vector.matrix.android.internal.session.room.timeline.TimelineSendEventWorkCommon
import im.vector.matrix.android.internal.util.CancelableWork import im.vector.matrix.android.internal.util.CancelableWork
import im.vector.matrix.android.internal.util.tryTransactionAsync
import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
@ -149,7 +148,7 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
} }
override fun deleteFailedEcho(localEcho: TimelineEvent) { override fun deleteFailedEcho(localEcho: TimelineEvent) {
monarchy.tryTransactionAsync { realm -> monarchy.writeAsync { realm ->
TimelineEventEntity.where(realm, eventId = localEcho.root.eventId TimelineEventEntity.where(realm, eventId = localEcho.root.eventId
?: "").findFirst()?.let { ?: "").findFirst()?.let {
it.deleteFromRealm() it.deleteFromRealm()
@ -175,7 +174,7 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
.enqueue() .enqueue()
} }
monarchy.tryTransactionAsync { realm -> monarchy.writeAsync { realm ->
RoomEntity.where(realm, roomId).findFirst()?.let { room -> RoomEntity.where(realm, roomId).findFirst()?.let { room ->
room.sendingTimelineEvents.forEach { room.sendingTimelineEvents.forEach {
it.root?.sendState = SendState.UNDELIVERED it.root?.sendState = SendState.UNDELIVERED
@ -186,7 +185,7 @@ internal class DefaultSendService @Inject constructor(private val context: Conte
} }
override fun resendAllFailedMessages() { override fun resendAllFailedMessages() {
monarchy.tryTransactionAsync { realm -> monarchy.writeAsync { realm ->
RoomEntity.where(realm, roomId).findFirst()?.let { room -> RoomEntity.where(realm, roomId).findFirst()?.let { room ->
room.sendingTimelineEvents.filter { room.sendingTimelineEvents.filter {
it.root?.sendState?.hasFailed() ?: false it.root?.sendState?.hasFailed() ?: false

View File

@ -17,7 +17,7 @@
package im.vector.matrix.android.internal.session.room.send package im.vector.matrix.android.internal.session.room.send
import android.content.Context import android.content.Context
import androidx.work.Worker import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
@ -34,7 +34,7 @@ import java.util.concurrent.CountDownLatch
import javax.inject.Inject import javax.inject.Inject
internal class EncryptEventWorker(context: Context, params: WorkerParameters) internal class EncryptEventWorker(context: Context, params: WorkerParameters)
: Worker(context, params) { : CoroutineWorker(context, params) {
@JsonClass(generateAdapter = true) @JsonClass(generateAdapter = true)
internal data class Params( internal data class Params(
@ -49,7 +49,7 @@ internal class EncryptEventWorker(context: Context, params: WorkerParameters)
@Inject lateinit var crypto: CryptoService @Inject lateinit var crypto: CryptoService
@Inject lateinit var localEchoUpdater: LocalEchoUpdater @Inject lateinit var localEchoUpdater: LocalEchoUpdater
override fun doWork(): Result { override suspend fun doWork(): Result {
Timber.v("Start Encrypt work") Timber.v("Start Encrypt work")
val params = WorkerParamsFactory.fromData<Params>(inputData) val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.success().also { ?: return Result.success().also {

View File

@ -36,7 +36,6 @@ import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.util.StringProvider import im.vector.matrix.android.internal.util.StringProvider
import im.vector.matrix.android.internal.util.tryTransactionAsync
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
import org.commonmark.renderer.html.HtmlRenderer import org.commonmark.renderer.html.HtmlRenderer
import java.util.* import java.util.*
@ -380,9 +379,9 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
fun saveLocalEcho(monarchy: Monarchy, event: Event) { fun saveLocalEcho(monarchy: Monarchy, event: Event) {
if (event.roomId == null) throw IllegalStateException("Your event should have a roomId") if (event.roomId == null) throw IllegalStateException("Your event should have a roomId")
monarchy.tryTransactionAsync { realm -> monarchy.writeAsync { realm ->
val roomEntity = RoomEntity.where(realm, roomId = event.roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId = event.roomId).findFirst()
?: return@tryTransactionAsync ?: return@writeAsync
roomEntity.addSendingEvent(event) roomEntity.addSendingEvent(event)
roomSummaryUpdater.update(realm, event.roomId) roomSummaryUpdater.update(realm, event.roomId)
} }

View File

@ -20,15 +20,15 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.util.tryTransactionAsync import im.vector.matrix.android.internal.util.awaitTransaction
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
internal class LocalEchoUpdater @Inject constructor(private val monarchy: Monarchy) { internal class LocalEchoUpdater @Inject constructor(private val monarchy: Monarchy) {
fun updateSendState(eventId: String, sendState: SendState) { suspend fun updateSendState(eventId: String, sendState: SendState) {
Timber.v("Update local state of $eventId to ${sendState.name}") Timber.v("Update local state of $eventId to ${sendState.name}")
monarchy.tryTransactionAsync { realm -> monarchy.awaitTransaction { realm ->
val sendingEventEntity = EventEntity.where(realm, eventId).findFirst() val sendingEventEntity = EventEntity.where(realm, eventId).findFirst()
if (sendingEventEntity != null) { if (sendingEventEntity != null) {
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) { if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {

View File

@ -54,28 +54,32 @@ internal class RedactEventWorker(context: Context, params: WorkerParameters) : C
sessionComponent.inject(this) sessionComponent.inject(this)
val eventId = params.eventId val eventId = params.eventId
val result = executeRequest<SendResponse> { return runCatching {
apiCall = roomAPI.redactEvent( executeRequest<SendResponse> {
params.txID, apiCall = roomAPI.redactEvent(
params.roomId, params.txID,
eventId, params.roomId,
if (params.reason == null) emptyMap() else mapOf("reason" to params.reason) eventId,
) if (params.reason == null) emptyMap() else mapOf("reason" to params.reason)
} )
return result.fold({
when (it) {
is Failure.NetworkConnection -> Result.retry()
else -> {
//TODO mark as failed to send?
//always return success, or the chain will be stuck for ever!
Result.success(WorkerParamsFactory.toData(params.copy(
lastFailureMessage = it.localizedMessage
)))
}
} }
}, { }.fold(
Result.success() {
}) Result.success()
},
{
when (it) {
is Failure.NetworkConnection -> Result.retry()
else -> {
//TODO mark as failed to send?
//always return success, or the chain will be stuck for ever!
Result.success(WorkerParamsFactory.toData(params.copy(
lastFailureMessage = it.localizedMessage
)))
}
}
}
)
} }
} }

View File

@ -21,6 +21,7 @@ import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.failure.Failure import im.vector.matrix.android.api.failure.Failure
import im.vector.matrix.android.api.session.events.model.Content
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
@ -59,22 +60,14 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam
if (params.lastFailureMessage != null) { if (params.lastFailureMessage != null) {
localEchoUpdater.updateSendState(event.eventId, SendState.UNDELIVERED) localEchoUpdater.updateSendState(event.eventId, SendState.UNDELIVERED)
// Transmit the error // Transmit the error
return Result.success(inputData) return Result.success(inputData)
} }
return try {
localEchoUpdater.updateSendState(event.eventId, SendState.SENDING) sendEvent(event.eventId, event.type, event.content, params.roomId)
val result = executeRequest<SendResponse> { Result.success()
apiCall = roomAPI.send( } catch (exception: Throwable) {
event.eventId, when (exception) {
params.roomId,
event.type,
event.content
)
}
return result.fold({
when (it) {
is Failure.NetworkConnection -> Result.retry() is Failure.NetworkConnection -> Result.retry()
else -> { else -> {
localEchoUpdater.updateSendState(event.eventId, SendState.UNDELIVERED) localEchoUpdater.updateSendState(event.eventId, SendState.UNDELIVERED)
@ -82,10 +75,20 @@ internal class SendEventWorker constructor(context: Context, params: WorkerParam
Result.success() Result.success()
} }
} }
}, { }
localEchoUpdater.updateSendState(event.eventId, SendState.SENT) }
Result.success()
}) private suspend fun sendEvent(eventId: String, eventType: String, content: Content?, roomId: String) {
localEchoUpdater.updateSendState(eventId, SendState.SENDING)
executeRequest<SendResponse> {
apiCall = roomAPI.send(
eventId,
roomId,
eventType,
content
)
}
localEchoUpdater.updateSendState(eventId, SendState.SENT)
} }
} }

View File

@ -16,9 +16,7 @@
package im.vector.matrix.android.internal.session.room.state package im.vector.matrix.android.internal.session.room.state
import arrow.core.Try
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
@ -32,7 +30,7 @@ internal interface SendStateTask : Task<SendStateTask.Params, Unit> {
} }
internal class DefaultSendStateTask @Inject constructor(private val roomAPI: RoomAPI) : SendStateTask { internal class DefaultSendStateTask @Inject constructor(private val roomAPI: RoomAPI) : SendStateTask {
override suspend fun execute(params: SendStateTask.Params): Try<Unit> { override suspend fun execute(params: SendStateTask.Params) {
return executeRequest { return executeRequest {
apiCall = roomAPI.sendStateEvent(params.roomId, params.eventType, params.body) apiCall = roomAPI.sendStateEvent(params.roomId, params.eventType, params.body)
} }

View File

@ -38,13 +38,12 @@ internal class DefaultGetContextOfEventTask @Inject constructor(private val room
private val tokenChunkEventPersistor: TokenChunkEventPersistor private val tokenChunkEventPersistor: TokenChunkEventPersistor
) : GetContextOfEventTask { ) : GetContextOfEventTask {
override suspend fun execute(params: GetContextOfEventTask.Params): Try<TokenChunkEventPersistor.Result> { override suspend fun execute(params: GetContextOfEventTask.Params): TokenChunkEventPersistor.Result {
val filter = filterRepository.getRoomFilter() val filter = filterRepository.getRoomFilter()
return executeRequest<EventContextResponse> { val response = executeRequest<EventContextResponse> {
apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter) apiCall = roomAPI.getContextOfEvent(params.roomId, params.eventId, 0, filter)
}.flatMap { response ->
tokenChunkEventPersistor.insertInDb(response, params.roomId, PaginationDirection.BACKWARDS)
} }
return tokenChunkEventPersistor.insertInDb(response, params.roomId, PaginationDirection.BACKWARDS)
} }
} }

View File

@ -16,9 +16,7 @@
package im.vector.matrix.android.internal.session.room.timeline package im.vector.matrix.android.internal.session.room.timeline
import arrow.core.Try
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.filter.FilterRepository import im.vector.matrix.android.internal.session.filter.FilterRepository
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
@ -41,14 +39,12 @@ internal class DefaultPaginationTask @Inject constructor(private val roomAPI: Ro
private val tokenChunkEventPersistor: TokenChunkEventPersistor private val tokenChunkEventPersistor: TokenChunkEventPersistor
) : PaginationTask { ) : PaginationTask {
override suspend fun execute(params: PaginationTask.Params): Try<TokenChunkEventPersistor.Result> { override suspend fun execute(params: PaginationTask.Params): TokenChunkEventPersistor.Result {
val filter = filterRepository.getRoomFilter() val filter = filterRepository.getRoomFilter()
return executeRequest<PaginationResponse> { val chunk = executeRequest<PaginationResponse> {
apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter) apiCall = roomAPI.getRoomMessagesFrom(params.roomId, params.from, params.direction.value, params.limit, filter)
}.flatMap { chunk ->
tokenChunkEventPersistor
.insertInDb(chunk, params.roomId, params.direction)
} }
return tokenChunkEventPersistor.insertInDb(chunk, params.roomId, params.direction)
} }
} }

View File

@ -16,12 +16,10 @@
package im.vector.matrix.android.internal.session.room.timeline package im.vector.matrix.android.internal.session.room.timeline
import arrow.core.Try
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
import im.vector.matrix.android.internal.task.Task
import javax.inject.Inject import javax.inject.Inject
internal class GetEventTask @Inject constructor(private val roomAPI: RoomAPI internal class GetEventTask @Inject constructor(private val roomAPI: RoomAPI
@ -32,7 +30,7 @@ internal class GetEventTask @Inject constructor(private val roomAPI: RoomAPI
val eventId: String val eventId: String
) )
override suspend fun execute(params: Params): Try<Event> { override suspend fun execute(params: Params): Event {
return executeRequest { return executeRequest {
apiCall = roomAPI.getEvent(params.roomId, params.eventId) apiCall = roomAPI.getEvent(params.roomId, params.eventId)
} }

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.session.room.timeline package im.vector.matrix.android.internal.session.room.timeline
import arrow.core.Try
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.internal.database.helper.* import im.vector.matrix.android.internal.database.helper.*
import im.vector.matrix.android.internal.database.model.ChunkEntity import im.vector.matrix.android.internal.database.model.ChunkEntity
@ -26,7 +25,7 @@ import im.vector.matrix.android.internal.database.query.find
import im.vector.matrix.android.internal.database.query.findAllIncludingEvents import im.vector.matrix.android.internal.database.query.findAllIncludingEvents
import im.vector.matrix.android.internal.database.query.where import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.user.UserEntityFactory import im.vector.matrix.android.internal.session.user.UserEntityFactory
import im.vector.matrix.android.internal.util.tryTransactionSync import im.vector.matrix.android.internal.util.awaitTransaction
import io.realm.kotlin.createObject import io.realm.kotlin.createObject
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
@ -104,12 +103,12 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
SUCCESS SUCCESS
} }
fun insertInDb(receivedChunk: TokenChunkEvent, suspend fun insertInDb(receivedChunk: TokenChunkEvent,
roomId: String, roomId: String,
direction: PaginationDirection): Try<Result> { direction: PaginationDirection): Result {
return monarchy monarchy
.tryTransactionSync { realm -> .awaitTransaction { realm ->
Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction") Timber.v("Start persisting ${receivedChunk.events.size} events in $roomId towards $direction")
val roomEntity = RoomEntity.where(realm, roomId).findFirst() val roomEntity = RoomEntity.where(realm, roomId).findFirst()
@ -127,7 +126,7 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
if (ChunkEntity.find(realm, roomId, nextToken = nextToken) != null || ChunkEntity.find(realm, roomId, prevToken = prevToken) != null) { if (ChunkEntity.find(realm, roomId, nextToken = nextToken) != null || ChunkEntity.find(realm, roomId, prevToken = prevToken) != null) {
Timber.v("Already inserted - SKIP") Timber.v("Already inserted - SKIP")
return@tryTransactionSync return@awaitTransaction
} }
val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken) val prevChunk = ChunkEntity.find(realm, roomId, nextToken = prevToken)
@ -181,13 +180,11 @@ internal class TokenChunkEventPersistor @Inject constructor(private val monarchy
currentChunk.updateSenderDataFor(eventIds) currentChunk.updateSenderDataFor(eventIds)
} }
} }
.map { return if (receivedChunk.events.isEmpty() && receivedChunk.stateEvents.isEmpty() && receivedChunk.start != receivedChunk.end) {
if (receivedChunk.events.isEmpty() && receivedChunk.stateEvents.isEmpty() && receivedChunk.start != receivedChunk.end) { Result.SHOULD_FETCH_MORE
Result.SHOULD_FETCH_MORE } else {
} else { Result.SUCCESS
Result.SUCCESS }
}
}
} }
private fun handleMerge(roomEntity: RoomEntity, private fun handleMerge(roomEntity: RoomEntity,

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.session.signout 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.Credentials
import im.vector.matrix.android.internal.SessionManager import im.vector.matrix.android.internal.SessionManager
import im.vector.matrix.android.internal.auth.SessionParamsStore import im.vector.matrix.android.internal.auth.SessionParamsStore
@ -31,15 +30,11 @@ internal class DefaultSignOutTask @Inject constructor(private val credentials: C
private val sessionManager: SessionManager, private val sessionManager: SessionManager,
private val sessionParamsStore: SessionParamsStore) : SignOutTask { private val sessionParamsStore: SessionParamsStore) : SignOutTask {
override suspend fun execute(params: Unit): Try<Unit> { override suspend fun execute(params: Unit) {
return executeRequest<Unit> { executeRequest<Unit> {
apiCall = signOutAPI.signOut() apiCall = signOutAPI.signOut()
}.flatMap {
sessionParamsStore.delete(credentials.userId)
}.flatMap {
Try {
sessionManager.releaseSession(credentials.userId)
}
} }
sessionParamsStore.delete(credentials.userId)
sessionManager.releaseSession(credentials.userId)
} }
} }

View File

@ -16,9 +16,6 @@
package im.vector.matrix.android.internal.session.sync package im.vector.matrix.android.internal.session.sync
import arrow.core.Try
import arrow.core.failure
import arrow.core.recoverWith
import com.zhuinden.monarchy.Monarchy import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.R import im.vector.matrix.android.R
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
@ -30,7 +27,6 @@ import im.vector.matrix.android.internal.session.DefaultInitialSyncProgressServi
import im.vector.matrix.android.internal.session.filter.FilterRepository import im.vector.matrix.android.internal.session.filter.FilterRepository
import im.vector.matrix.android.internal.session.sync.model.SyncResponse import im.vector.matrix.android.internal.session.sync.model.SyncResponse
import im.vector.matrix.android.internal.task.Task import im.vector.matrix.android.internal.task.Task
import im.vector.matrix.android.internal.util.tryTransactionAsync
import javax.inject.Inject import javax.inject.Inject
internal interface SyncTask : Task<SyncTask.Params, Unit> { internal interface SyncTask : Task<SyncTask.Params, Unit> {
@ -50,7 +46,7 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
) : SyncTask { ) : SyncTask {
override suspend fun execute(params: SyncTask.Params): Try<Unit> { override suspend fun execute(params: SyncTask.Params) {
val requestParams = HashMap<String, String>() val requestParams = HashMap<String, String>()
var timeout = 0L var timeout = 0L
val token = syncTokenStore.getLastToken() val token = syncTokenStore.getLastToken()
@ -66,27 +62,22 @@ internal class DefaultSyncTask @Inject constructor(private val syncAPI: SyncAPI,
initialSyncProgressService.endAll() initialSyncProgressService.endAll()
initialSyncProgressService.startTask(R.string.initial_sync_start_importing_account, 100) initialSyncProgressService.startTask(R.string.initial_sync_start_importing_account, 100)
} }
return executeRequest<SyncResponse> { val syncResponse = try {
apiCall = syncAPI.sync(requestParams) executeRequest<SyncResponse> {
}.recoverWith { throwable -> apiCall = syncAPI.sync(requestParams)
}
} catch (throwable: Throwable) {
// Intercept 401 // Intercept 401
if (throwable is Failure.ServerError if (throwable is Failure.ServerError
&& throwable.error.code == MatrixError.UNKNOWN_TOKEN) { && throwable.error.code == MatrixError.UNKNOWN_TOKEN) {
sessionParamsStore.delete(credentials.userId) sessionParamsStore.delete(credentials.userId)
} }
throw throwable
// Transmit the throwable }
throwable.failure() syncResponseHandler.handleResponse(syncResponse, token, false)
}.flatMap { syncResponse -> syncTokenStore.saveToken(syncResponse.nextBatch)
syncResponseHandler.handleResponse(syncResponse, token, false).also { if (isInitialSync) {
if (isInitialSync) { initialSyncProgressService.endAll()
monarchy.tryTransactionAsync {
initialSyncProgressService.endAll()
}
}
}
}.map {
syncTokenStore.saveToken(it.nextBatch)
} }
} }
} }

View File

@ -134,9 +134,12 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
latch.countDown() latch.countDown()
} }
override fun onCanceled() {
latch.countDown()
}
}) })
.executeBy(taskExecutor) .executeBy(taskExecutor)
latch.await() latch.await()
if (state is SyncState.RUNNING) { if (state is SyncState.RUNNING) {
updateStateTo(SyncState.RUNNING(afterPause = false)) updateStateTo(SyncState.RUNNING(afterPause = false))

View File

@ -18,18 +18,15 @@ package im.vector.matrix.android.internal.session.sync.job
import android.content.Context import android.content.Context
import androidx.work.* import androidx.work.*
import com.squareup.moshi.JsonClass import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.internal.session.sync.SyncTask import im.vector.matrix.android.internal.session.sync.SyncTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.TaskThread import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.worker.WorkManagerUtil import im.vector.matrix.android.internal.worker.WorkManagerUtil
import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder import im.vector.matrix.android.internal.worker.WorkManagerUtil.matrixOneTimeWorkRequestBuilder
import im.vector.matrix.android.internal.worker.WorkerParamsFactory import im.vector.matrix.android.internal.worker.WorkerParamsFactory
import im.vector.matrix.android.internal.worker.getSessionComponent import im.vector.matrix.android.internal.worker.getSessionComponent
import kotlinx.coroutines.withContext
import timber.log.Timber import timber.log.Timber
import java.util.concurrent.CountDownLatch
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import javax.inject.Inject import javax.inject.Inject
@ -47,45 +44,26 @@ internal class SyncWorker(context: Context,
val automaticallyRetry: Boolean = false val automaticallyRetry: Boolean = false
) )
@Inject @Inject lateinit var syncTask: SyncTask
lateinit var syncTask: SyncTask @Inject lateinit var taskExecutor: TaskExecutor
@Inject @Inject lateinit var coroutineDispatchers: MatrixCoroutineDispatchers
lateinit var taskExecutor: TaskExecutor
override suspend fun doWork(): Result { override suspend fun doWork(): Result {
Timber.i("Sync work starting") Timber.i("Sync work starting")
val params = WorkerParamsFactory.fromData<Params>(inputData) ?: return Result.success() val params = WorkerParamsFactory.fromData<Params>(inputData) ?: return Result.success()
val sessionComponent = getSessionComponent(params.userId) ?: return Result.success() val sessionComponent = getSessionComponent(params.userId) ?: return Result.success()
sessionComponent.inject(this) sessionComponent.inject(this)
runCatching {
withContext(coroutineDispatchers.sync) {
val latch = CountDownLatch(1) val taskParams = SyncTask.Params(0)
val taskParams = SyncTask.Params(0) syncTask.execute(taskParams)
cancelableTask = syncTask.configureWith(taskParams) }
.callbackOn(TaskThread.SYNC) }
.executeOn(TaskThread.SYNC)
.dispatchTo(object : MatrixCallback<Unit> {
override fun onSuccess(data: Unit) {
latch.countDown()
}
override fun onFailure(failure: Throwable) {
Timber.e(failure)
latch.countDown()
}
})
.executeBy(taskExecutor)
latch.await()
return Result.success() return Result.success()
} }
companion object { companion object {
private var cancelableTask: Cancelable? = null
fun requireBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0) { fun requireBackgroundSync(context: Context, userId: String, serverTimeout: Long = 0) {
val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, false)) val data = WorkerParamsFactory.toData(Params(userId, serverTimeout, false))
val workRequest = matrixOneTimeWorkRequestBuilder<SyncWorker>() val workRequest = matrixOneTimeWorkRequestBuilder<SyncWorker>()
@ -107,7 +85,6 @@ internal class SyncWorker(context: Context,
} }
fun stopAnyBackgroundSync(context: Context) { fun stopAnyBackgroundSync(context: Context) {
cancelableTask?.cancel()
WorkManager.getInstance(context).cancelUniqueWork("BG_SYNCP") WorkManager.getInstance(context).cancelUniqueWork("BG_SYNCP")
} }
} }

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.session.user.accountdata package im.vector.matrix.android.internal.session.user.accountdata
import arrow.core.Try
import im.vector.matrix.android.api.auth.data.Credentials import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.sync.model.UserAccountData import im.vector.matrix.android.internal.session.sync.model.UserAccountData
@ -45,8 +44,7 @@ internal interface UpdateUserAccountDataTask : Task<UpdateUserAccountDataTask.Pa
internal class DefaultUpdateUserAcountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI, internal class DefaultUpdateUserAcountDataTask @Inject constructor(private val accountDataApi: AccountDataAPI,
private val credentials: Credentials) : UpdateUserAccountDataTask { private val credentials: Credentials) : UpdateUserAccountDataTask {
override suspend fun execute(params: UpdateUserAccountDataTask.Params): Try<Unit> { override suspend fun execute(params: UpdateUserAccountDataTask.Params) {
return executeRequest { return executeRequest {
apiCall = accountDataApi.setAccountData(credentials.userId, params.type, params.getData()) apiCall = accountDataApi.setAccountData(credentials.userId, params.type, params.getData())
} }

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.session.user.model package im.vector.matrix.android.internal.session.user.model
import arrow.core.Try
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.user.SearchUserAPI import im.vector.matrix.android.internal.session.user.SearchUserAPI
@ -34,13 +33,12 @@ internal interface SearchUserTask : Task<SearchUserTask.Params, List<User>> {
internal class DefaultSearchUserTask @Inject constructor(private val searchUserAPI: SearchUserAPI) : SearchUserTask { internal class DefaultSearchUserTask @Inject constructor(private val searchUserAPI: SearchUserAPI) : SearchUserTask {
override suspend fun execute(params: SearchUserTask.Params): Try<List<User>> { override suspend fun execute(params: SearchUserTask.Params): List<User> {
return executeRequest<SearchUsersRequestResponse> { val response = executeRequest<SearchUsersRequestResponse> {
apiCall = searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit)) apiCall = searchUserAPI.searchUsers(SearchUsersParams(params.search, params.limit))
}.map { response -> }
response.users.map { return response.users.map {
User(it.userId, it.displayName, it.avatarUrl) User(it.userId, it.displayName, it.avatarUrl)
}
} }
} }

View File

@ -16,7 +16,6 @@
package im.vector.matrix.android.internal.task package im.vector.matrix.android.internal.task
import arrow.core.Try
import im.vector.matrix.android.api.MatrixCallback import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
@ -41,7 +40,7 @@ internal data class ConfigurableTask<PARAMS, RESULT>(
) : Task<PARAMS, RESULT> { ) : Task<PARAMS, RESULT> {
override suspend fun execute(params: PARAMS): Try<RESULT> { override suspend fun execute(params: PARAMS): RESULT {
return task.execute(params) return task.execute(params)
} }

View File

@ -16,11 +16,9 @@
package im.vector.matrix.android.internal.task package im.vector.matrix.android.internal.task
import arrow.core.Try
internal interface Task<PARAMS, RESULT> { internal interface Task<PARAMS, RESULT> {
suspend fun execute(params: PARAMS): Try<RESULT> suspend fun execute(params: PARAMS): RESULT
} }

View File

@ -17,10 +17,10 @@
package im.vector.matrix.android.internal.task package im.vector.matrix.android.internal.task
import arrow.core.Try
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import im.vector.matrix.android.api.util.CancelableBag
import im.vector.matrix.android.internal.di.MatrixScope
import im.vector.matrix.android.internal.extensions.foldToCallback import im.vector.matrix.android.internal.extensions.foldToCallback
import im.vector.matrix.android.internal.extensions.onError
import im.vector.matrix.android.internal.util.CancelableCoroutine import im.vector.matrix.android.internal.util.CancelableCoroutine
import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers import im.vector.matrix.android.internal.util.MatrixCoroutineDispatchers
import kotlinx.coroutines.GlobalScope import kotlinx.coroutines.GlobalScope
@ -31,24 +31,35 @@ import timber.log.Timber
import javax.inject.Inject import javax.inject.Inject
import kotlin.coroutines.EmptyCoroutineContext import kotlin.coroutines.EmptyCoroutineContext
@MatrixScope
internal class TaskExecutor @Inject constructor(private val coroutineDispatchers: MatrixCoroutineDispatchers) { internal class TaskExecutor @Inject constructor(private val coroutineDispatchers: MatrixCoroutineDispatchers) {
private val cancelableBag = CancelableBag()
fun <PARAMS, RESULT> execute(task: ConfigurableTask<PARAMS, RESULT>): Cancelable { fun <PARAMS, RESULT> execute(task: ConfigurableTask<PARAMS, RESULT>): Cancelable {
val job = GlobalScope.launch(task.callbackThread.toDispatcher()) { val job = GlobalScope.launch(task.callbackThread.toDispatcher()) {
val resultOrFailure = withContext(task.executionThread.toDispatcher()) { val resultOrFailure = runCatching {
Timber.v("Executing $task on ${Thread.currentThread().name}") withContext(task.executionThread.toDispatcher()) {
retry(task.retryCount) { Timber.v("Executing $task on ${Thread.currentThread().name}")
task.execute(task.params) retry(task.retryCount) {
task.execute(task.params)
}
} }
} }
resultOrFailure resultOrFailure
.onError { .onFailure {
Timber.d(it, "Task failed") Timber.d(it, "Task failed")
} }
.foldToCallback(task.callback) .foldToCallback(task.callback)
} }
return CancelableCoroutine(job) return CancelableCoroutine(job, task.callback).also {
cancelableBag += it
}
}
fun cancelAll() = synchronized(this) {
cancelableBag.cancel()
} }
private suspend fun <T> retry( private suspend fun <T> retry(
@ -56,14 +67,13 @@ internal class TaskExecutor @Inject constructor(private val coroutineDispatchers
initialDelay: Long = 100, // 0.1 second initialDelay: Long = 100, // 0.1 second
maxDelay: Long = 10_000, // 10 second maxDelay: Long = 10_000, // 10 second
factor: Double = 2.0, factor: Double = 2.0,
block: suspend () -> Try<T>): Try<T> { block: suspend () -> T): T {
var currentDelay = initialDelay var currentDelay = initialDelay
repeat(times - 1) { repeat(times - 1) {
val blockResult = block() try {
if (blockResult.isSuccess()) { return block()
return blockResult } catch (e: Exception) {
} else {
delay(currentDelay) delay(currentDelay)
currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay) currentDelay = (currentDelay * factor).toLong().coerceAtMost(maxDelay)
} }

View File

@ -16,13 +16,17 @@
package im.vector.matrix.android.internal.util package im.vector.matrix.android.internal.util
import im.vector.matrix.android.api.MatrixCallback
import im.vector.matrix.android.api.util.Cancelable import im.vector.matrix.android.api.util.Cancelable
import kotlinx.coroutines.Job import kotlinx.coroutines.Job
internal class CancelableCoroutine(private val job: Job) : Cancelable { internal class CancelableCoroutine(private val job: Job, private val callback: MatrixCallback<*>) : Cancelable {
override fun cancel() { override fun cancel() {
job.cancel() if (!job.isCancelled) {
job.cancel()
callback.onCanceled()
}
} }
} }

Some files were not shown because too many files have changed in this diff Show More