Merge pull request #2235 from vector-im/feature/bca/perf_better_send_time
Feature/bca/perf better send time
This commit is contained in:
commit
c88c2a17ca
@ -5,7 +5,7 @@ Features ✨:
|
|||||||
-
|
-
|
||||||
|
|
||||||
Improvements 🙌:
|
Improvements 🙌:
|
||||||
-
|
- Rework sending Event management (#154)
|
||||||
|
|
||||||
Bugfix 🐛:
|
Bugfix 🐛:
|
||||||
-
|
-
|
||||||
|
@ -23,7 +23,7 @@ import com.squareup.moshi.JsonClass
|
|||||||
data class ReactionInfo(
|
data class ReactionInfo(
|
||||||
@Json(name = "rel_type") override val type: String?,
|
@Json(name = "rel_type") override val type: String?,
|
||||||
@Json(name = "event_id") override val eventId: String,
|
@Json(name = "event_id") override val eventId: String,
|
||||||
val key: String,
|
@Json(name = "key") val key: String,
|
||||||
// always null for reaction
|
// always null for reaction
|
||||||
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
|
@Json(name = "m.in_reply_to") override val inReplyTo: ReplyToContent? = null,
|
||||||
@Json(name = "option") override val option: Int? = null
|
@Json(name = "option") override val option: Int? = null
|
||||||
|
@ -123,11 +123,6 @@ interface SendService {
|
|||||||
*/
|
*/
|
||||||
fun deleteFailedEcho(localEcho: TimelineEvent)
|
fun deleteFailedEcho(localEcho: TimelineEvent)
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete all the events in one of the sending states
|
|
||||||
*/
|
|
||||||
fun clearSendingQueue()
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Cancel sending a specific event. It has to be in one of the sending states
|
* Cancel sending a specific event. It has to be in one of the sending states
|
||||||
*/
|
*/
|
||||||
|
@ -420,7 +420,7 @@ internal class MXMegolmEncryption(
|
|||||||
sendToDeviceTask.execute(sendToDeviceParams)
|
sendToDeviceTask.execute(sendToDeviceParams)
|
||||||
true
|
true
|
||||||
} catch (failure: Throwable) {
|
} catch (failure: Throwable) {
|
||||||
Timber.v("## CRYPTO | CRYPTO | reshareKey() : fail to send <$sessionId> to $userId:$deviceId")
|
Timber.e(failure, "## CRYPTO | CRYPTO | reshareKey() : fail to send <$sessionId> to $userId:$deviceId")
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,8 +17,13 @@ package org.matrix.android.sdk.internal.crypto.tasks
|
|||||||
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.toContent
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
||||||
|
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
||||||
|
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
import org.matrix.android.sdk.internal.util.awaitCallback
|
||||||
@ -38,13 +43,14 @@ internal class DefaultEncryptEventTask @Inject constructor(
|
|||||||
private val localEchoRepository: LocalEchoRepository
|
private val localEchoRepository: LocalEchoRepository
|
||||||
) : EncryptEventTask {
|
) : EncryptEventTask {
|
||||||
override suspend fun execute(params: EncryptEventTask.Params): Event {
|
override suspend fun execute(params: EncryptEventTask.Params): Event {
|
||||||
if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event
|
// don't want to wait for any query
|
||||||
|
// if (!params.crypto.isRoomEncrypted(params.roomId)) return params.event
|
||||||
val localEvent = params.event
|
val localEvent = params.event
|
||||||
if (localEvent.eventId == null) {
|
if (localEvent.eventId == null) {
|
||||||
throw IllegalArgumentException()
|
throw IllegalArgumentException()
|
||||||
}
|
}
|
||||||
|
|
||||||
localEchoRepository.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
|
localEchoRepository.updateSendState(localEvent.eventId, localEvent.roomId, SendState.ENCRYPTING)
|
||||||
|
|
||||||
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
||||||
params.keepKeys?.forEach {
|
params.keepKeys?.forEach {
|
||||||
@ -52,6 +58,7 @@ internal class DefaultEncryptEventTask @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
// try {
|
// try {
|
||||||
|
// let it throws
|
||||||
awaitCallback<MXEncryptEventContentResult> {
|
awaitCallback<MXEncryptEventContentResult> {
|
||||||
params.crypto.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it)
|
params.crypto.encryptEventContent(localMutableContent, localEvent.type, params.roomId, it)
|
||||||
}.let { result ->
|
}.let { result ->
|
||||||
@ -63,18 +70,34 @@ internal class DefaultEncryptEventTask @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
val safeResult = result.copy(eventContent = modifiedContent)
|
val safeResult = result.copy(eventContent = modifiedContent)
|
||||||
|
// Better handling of local echo, to avoid decrypting transition on remote echo
|
||||||
|
// Should I only do it for text messages?
|
||||||
|
val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
|
||||||
|
MXEventDecryptionResult(
|
||||||
|
clearEvent = Event(
|
||||||
|
type = localEvent.type,
|
||||||
|
content = localEvent.content,
|
||||||
|
roomId = localEvent.roomId
|
||||||
|
).toContent(),
|
||||||
|
forwardingCurve25519KeyChain = emptyList(),
|
||||||
|
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
||||||
|
claimedEd25519Key = params.crypto.getMyDevice().fingerprint()
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
null
|
||||||
|
}
|
||||||
|
|
||||||
|
localEchoRepository.updateEcho(localEvent.eventId) { _, localEcho ->
|
||||||
|
localEcho.type = EventType.ENCRYPTED
|
||||||
|
localEcho.content = ContentMapper.map(modifiedContent)
|
||||||
|
decryptionLocalEcho?.also {
|
||||||
|
localEcho.setDecryptionResult(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
return localEvent.copy(
|
return localEvent.copy(
|
||||||
type = safeResult.eventType,
|
type = safeResult.eventType,
|
||||||
content = safeResult.eventContent
|
content = safeResult.eventContent
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// } catch (throwable: Throwable) {
|
|
||||||
// val sendState = when (throwable) {
|
|
||||||
// is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES
|
|
||||||
// else -> SendState.UNDELIVERED
|
|
||||||
// }
|
|
||||||
// localEchoUpdater.updateSendState(localEvent.eventId, sendState)
|
|
||||||
// throw throwable
|
|
||||||
// }
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,49 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.crypto.tasks
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
|
import org.matrix.android.sdk.internal.network.executeRequest
|
||||||
|
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||||
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal interface RedactEventTask : Task<RedactEventTask.Params, String> {
|
||||||
|
data class Params(
|
||||||
|
val txID: String,
|
||||||
|
val roomId: String,
|
||||||
|
val eventId: String,
|
||||||
|
val reason: String?
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
internal class DefaultRedactEventTask @Inject constructor(
|
||||||
|
private val roomAPI: RoomAPI,
|
||||||
|
private val eventBus: EventBus) : RedactEventTask {
|
||||||
|
|
||||||
|
override suspend fun execute(params: RedactEventTask.Params): String {
|
||||||
|
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
||||||
|
apiCall = roomAPI.redactEvent(
|
||||||
|
txId = params.txID,
|
||||||
|
roomId = params.roomId,
|
||||||
|
eventId = params.eventId,
|
||||||
|
reason = if (params.reason == null) emptyMap() else mapOf("reason" to params.reason)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return executeRequest.eventId
|
||||||
|
}
|
||||||
|
}
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package org.matrix.android.sdk.internal.crypto.tasks
|
package org.matrix.android.sdk.internal.crypto.tasks
|
||||||
|
|
||||||
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
@ -23,12 +24,12 @@ import org.matrix.android.sdk.internal.session.room.RoomAPI
|
|||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
import org.matrix.android.sdk.internal.session.room.send.SendResponse
|
||||||
import org.matrix.android.sdk.internal.task.Task
|
import org.matrix.android.sdk.internal.task.Task
|
||||||
import org.greenrobot.eventbus.EventBus
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
internal interface SendEventTask : Task<SendEventTask.Params, String> {
|
internal interface SendEventTask : Task<SendEventTask.Params, String> {
|
||||||
data class Params(
|
data class Params(
|
||||||
val event: Event,
|
val event: Event,
|
||||||
|
val encrypt: Boolean,
|
||||||
val cryptoService: CryptoService?
|
val cryptoService: CryptoService?
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -40,11 +41,11 @@ internal class DefaultSendEventTask @Inject constructor(
|
|||||||
private val eventBus: EventBus) : SendEventTask {
|
private val eventBus: EventBus) : SendEventTask {
|
||||||
|
|
||||||
override suspend fun execute(params: SendEventTask.Params): String {
|
override suspend fun execute(params: SendEventTask.Params): String {
|
||||||
|
try {
|
||||||
val event = handleEncryption(params)
|
val event = handleEncryption(params)
|
||||||
val localId = event.eventId!!
|
val localId = event.eventId!!
|
||||||
|
|
||||||
try {
|
localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENDING)
|
||||||
localEchoRepository.updateSendState(localId, SendState.SENDING)
|
|
||||||
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
||||||
apiCall = roomAPI.send(
|
apiCall = roomAPI.send(
|
||||||
localId,
|
localId,
|
||||||
@ -53,26 +54,23 @@ internal class DefaultSendEventTask @Inject constructor(
|
|||||||
eventType = event.type
|
eventType = event.type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
localEchoRepository.updateSendState(localId, SendState.SENT)
|
localEchoRepository.updateSendState(localId, params.event.roomId, SendState.SENT)
|
||||||
return executeRequest.eventId
|
return executeRequest.eventId
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
localEchoRepository.updateSendState(localId, SendState.UNDELIVERED)
|
// localEchoRepository.updateSendState(params.event.eventId!!, SendState.UNDELIVERED)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Throws
|
||||||
private suspend fun handleEncryption(params: SendEventTask.Params): Event {
|
private suspend fun handleEncryption(params: SendEventTask.Params): Event {
|
||||||
if (params.cryptoService?.isRoomEncrypted(params.event.roomId ?: "") == true) {
|
if (params.encrypt && !params.event.isEncrypted()) {
|
||||||
try {
|
|
||||||
return encryptEventTask.execute(EncryptEventTask.Params(
|
return encryptEventTask.execute(EncryptEventTask.Params(
|
||||||
params.event.roomId ?: "",
|
params.event.roomId ?: "",
|
||||||
params.event,
|
params.event,
|
||||||
listOf("m.relates_to"),
|
listOf("m.relates_to"),
|
||||||
params.cryptoService
|
params.cryptoService!!
|
||||||
))
|
))
|
||||||
} catch (throwable: Throwable) {
|
|
||||||
// We said it's ok to send verification request in clear
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return params.event
|
return params.event
|
||||||
}
|
}
|
||||||
|
@ -44,7 +44,7 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||||||
val localId = event.eventId!!
|
val localId = event.eventId!!
|
||||||
|
|
||||||
try {
|
try {
|
||||||
localEchoRepository.updateSendState(localId, SendState.SENDING)
|
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENDING)
|
||||||
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
val executeRequest = executeRequest<SendResponse>(eventBus) {
|
||||||
apiCall = roomAPI.send(
|
apiCall = roomAPI.send(
|
||||||
localId,
|
localId,
|
||||||
@ -53,10 +53,10 @@ internal class DefaultSendVerificationMessageTask @Inject constructor(
|
|||||||
eventType = event.type
|
eventType = event.type
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
localEchoRepository.updateSendState(localId, SendState.SENT)
|
localEchoRepository.updateSendState(localId, event.roomId, SendState.SENT)
|
||||||
return executeRequest.eventId
|
return executeRequest.eventId
|
||||||
} catch (e: Throwable) {
|
} catch (e: Throwable) {
|
||||||
localEchoRepository.updateSendState(localId, SendState.UNDELIVERED)
|
localEchoRepository.updateSendState(localId, event.roomId, SendState.UNDELIVERED)
|
||||||
throw e
|
throw e
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -64,6 +64,7 @@ import org.matrix.android.sdk.internal.di.SessionId
|
|||||||
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
import org.matrix.android.sdk.internal.di.UnauthenticatedWithCertificate
|
||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
|
import org.matrix.android.sdk.internal.session.identity.DefaultIdentityService
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDecryptor
|
import org.matrix.android.sdk.internal.session.room.timeline.TimelineEventDecryptor
|
||||||
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
|
import org.matrix.android.sdk.internal.session.sync.SyncTokenStore
|
||||||
import org.matrix.android.sdk.internal.session.sync.job.SyncThread
|
import org.matrix.android.sdk.internal.session.sync.job.SyncThread
|
||||||
@ -121,7 +122,8 @@ internal class DefaultSession @Inject constructor(
|
|||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val callSignalingService: Lazy<CallSignalingService>,
|
private val callSignalingService: Lazy<CallSignalingService>,
|
||||||
@UnauthenticatedWithCertificate
|
@UnauthenticatedWithCertificate
|
||||||
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>
|
private val unauthenticatedWithCertificateOkHttpClient: Lazy<OkHttpClient>,
|
||||||
|
private val eventSenderProcessor: EventSenderProcessor
|
||||||
) : Session,
|
) : Session,
|
||||||
RoomService by roomService.get(),
|
RoomService by roomService.get(),
|
||||||
RoomDirectoryService by roomDirectoryService.get(),
|
RoomDirectoryService by roomDirectoryService.get(),
|
||||||
@ -161,6 +163,7 @@ internal class DefaultSession @Inject constructor(
|
|||||||
}
|
}
|
||||||
eventBus.register(this)
|
eventBus.register(this)
|
||||||
timelineEventDecryptor.start()
|
timelineEventDecryptor.start()
|
||||||
|
eventSenderProcessor.start()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun requireBackgroundSync() {
|
override fun requireBackgroundSync() {
|
||||||
@ -204,6 +207,7 @@ internal class DefaultSession @Inject constructor(
|
|||||||
cryptoService.get().close()
|
cryptoService.get().close()
|
||||||
isOpen = false
|
isOpen = false
|
||||||
eventBus.unregister(this)
|
eventBus.unregister(this)
|
||||||
|
eventSenderProcessor.interrupt()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getSyncStateLive() = getSyncThread().liveState()
|
override fun getSyncStateLive() = getSyncThread().liveState()
|
||||||
|
@ -45,7 +45,6 @@ import org.matrix.android.sdk.internal.session.pushers.AddHttpPusherWorker
|
|||||||
import org.matrix.android.sdk.internal.session.pushers.PushersModule
|
import org.matrix.android.sdk.internal.session.pushers.PushersModule
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomModule
|
import org.matrix.android.sdk.internal.session.room.RoomModule
|
||||||
import org.matrix.android.sdk.internal.session.room.relation.SendRelationWorker
|
import org.matrix.android.sdk.internal.session.room.relation.SendRelationWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.send.EncryptEventWorker
|
|
||||||
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
import org.matrix.android.sdk.internal.session.room.send.MultipleEventSendingDispatcherWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
|
import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
|
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
|
||||||
@ -109,8 +108,6 @@ internal interface SessionComponent {
|
|||||||
|
|
||||||
fun inject(worker: SendRelationWorker)
|
fun inject(worker: SendRelationWorker)
|
||||||
|
|
||||||
fun inject(worker: EncryptEventWorker)
|
|
||||||
|
|
||||||
fun inject(worker: MultipleEventSendingDispatcherWorker)
|
fun inject(worker: MultipleEventSendingDispatcherWorker)
|
||||||
|
|
||||||
fun inject(worker: RedactEventWorker)
|
fun inject(worker: RedactEventWorker)
|
||||||
|
@ -43,6 +43,8 @@ import org.matrix.android.sdk.api.session.securestorage.SharedSecretStorageServi
|
|||||||
import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
|
import org.matrix.android.sdk.api.session.typing.TypingUsersTracker
|
||||||
import org.matrix.android.sdk.internal.crypto.crosssigning.ShieldTrustUpdater
|
import org.matrix.android.sdk.internal.crypto.crosssigning.ShieldTrustUpdater
|
||||||
import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
|
import org.matrix.android.sdk.internal.crypto.secrets.DefaultSharedSecretStorageService
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.DefaultRedactEventTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
|
||||||
import org.matrix.android.sdk.internal.crypto.verification.VerificationMessageProcessor
|
import org.matrix.android.sdk.internal.crypto.verification.VerificationMessageProcessor
|
||||||
import org.matrix.android.sdk.internal.database.DatabaseCleaner
|
import org.matrix.android.sdk.internal.database.DatabaseCleaner
|
||||||
import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
|
import org.matrix.android.sdk.internal.database.EventInsertLiveObserver
|
||||||
@ -367,4 +369,7 @@ internal abstract class SessionModule {
|
|||||||
|
|
||||||
@Binds
|
@Binds
|
||||||
abstract fun bindTypingUsersTracker(tracker: DefaultTypingUsersTracker): TypingUsersTracker
|
abstract fun bindTypingUsersTracker(tracker: DefaultTypingUsersTracker): TypingUsersTracker
|
||||||
|
|
||||||
|
@Binds
|
||||||
|
abstract fun bindRedactEventTask(task: DefaultRedactEventTask): RedactEventTask
|
||||||
}
|
}
|
||||||
|
@ -36,8 +36,8 @@ import org.matrix.android.sdk.api.util.NoOpCancellable
|
|||||||
import org.matrix.android.sdk.internal.di.UserId
|
import org.matrix.android.sdk.internal.di.UserId
|
||||||
import org.matrix.android.sdk.internal.session.SessionScope
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
|
import org.matrix.android.sdk.internal.session.call.model.MxCallImpl
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||||
import org.matrix.android.sdk.internal.session.room.send.RoomEventSender
|
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
import org.matrix.android.sdk.internal.task.configureWith
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -50,7 +50,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||||||
private val userId: String,
|
private val userId: String,
|
||||||
private val activeCallHandler: ActiveCallHandler,
|
private val activeCallHandler: ActiveCallHandler,
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
private val roomEventSender: RoomEventSender,
|
private val eventSenderProcessor: EventSenderProcessor,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val turnServerTask: GetTurnServerTask
|
private val turnServerTask: GetTurnServerTask
|
||||||
) : CallSignalingService {
|
) : CallSignalingService {
|
||||||
@ -103,7 +103,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||||||
otherUserId = otherUserId,
|
otherUserId = otherUserId,
|
||||||
isVideoCall = isVideoCall,
|
isVideoCall = isVideoCall,
|
||||||
localEchoEventFactory = localEchoEventFactory,
|
localEchoEventFactory = localEchoEventFactory,
|
||||||
roomEventSender = roomEventSender
|
eventSenderProcessor = eventSenderProcessor
|
||||||
)
|
)
|
||||||
activeCallHandler.addCall(call).also {
|
activeCallHandler.addCall(call).also {
|
||||||
return call
|
return call
|
||||||
@ -165,7 +165,7 @@ internal class DefaultCallSignalingService @Inject constructor(
|
|||||||
otherUserId = event.senderId ?: return@let,
|
otherUserId = event.senderId ?: return@let,
|
||||||
isVideoCall = content.isVideo(),
|
isVideoCall = content.isVideo(),
|
||||||
localEchoEventFactory = localEchoEventFactory,
|
localEchoEventFactory = localEchoEventFactory,
|
||||||
roomEventSender = roomEventSender
|
eventSenderProcessor = eventSenderProcessor
|
||||||
)
|
)
|
||||||
activeCallHandler.addCall(incomingCall)
|
activeCallHandler.addCall(incomingCall)
|
||||||
onCallInvite(incomingCall, content)
|
onCallInvite(incomingCall, content)
|
||||||
|
@ -29,8 +29,8 @@ import org.matrix.android.sdk.api.session.room.model.call.CallCandidatesContent
|
|||||||
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallHangupContent
|
||||||
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
import org.matrix.android.sdk.api.session.room.model.call.CallInviteContent
|
||||||
import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
|
import org.matrix.android.sdk.internal.session.call.DefaultCallSignalingService
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||||
import org.matrix.android.sdk.internal.session.room.send.RoomEventSender
|
|
||||||
import org.webrtc.IceCandidate
|
import org.webrtc.IceCandidate
|
||||||
import org.webrtc.SessionDescription
|
import org.webrtc.SessionDescription
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -43,7 +43,7 @@ internal class MxCallImpl(
|
|||||||
override val otherUserId: String,
|
override val otherUserId: String,
|
||||||
override val isVideoCall: Boolean,
|
override val isVideoCall: Boolean,
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
private val roomEventSender: RoomEventSender
|
private val eventSenderProcessor: EventSenderProcessor
|
||||||
) : MxCall {
|
) : MxCall {
|
||||||
|
|
||||||
override var state: CallState = CallState.Idle
|
override var state: CallState = CallState.Idle
|
||||||
@ -91,7 +91,7 @@ internal class MxCallImpl(
|
|||||||
offer = CallInviteContent.Offer(sdp = sdp.description)
|
offer = CallInviteContent.Offer(sdp = sdp.description)
|
||||||
)
|
)
|
||||||
.let { createEventAndLocalEcho(type = EventType.CALL_INVITE, roomId = roomId, content = it.toContent()) }
|
.let { createEventAndLocalEcho(type = EventType.CALL_INVITE, roomId = roomId, content = it.toContent()) }
|
||||||
.also { roomEventSender.sendEvent(it) }
|
.also { eventSenderProcessor.postEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendLocalIceCandidates(candidates: List<IceCandidate>) {
|
override fun sendLocalIceCandidates(candidates: List<IceCandidate>) {
|
||||||
@ -106,7 +106,7 @@ internal class MxCallImpl(
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
.let { createEventAndLocalEcho(type = EventType.CALL_CANDIDATES, roomId = roomId, content = it.toContent()) }
|
.let { createEventAndLocalEcho(type = EventType.CALL_CANDIDATES, roomId = roomId, content = it.toContent()) }
|
||||||
.also { roomEventSender.sendEvent(it) }
|
.also { eventSenderProcessor.postEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun sendLocalIceCandidateRemovals(candidates: List<IceCandidate>) {
|
override fun sendLocalIceCandidateRemovals(candidates: List<IceCandidate>) {
|
||||||
@ -119,7 +119,7 @@ internal class MxCallImpl(
|
|||||||
callId = callId
|
callId = callId
|
||||||
)
|
)
|
||||||
.let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
|
.let { createEventAndLocalEcho(type = EventType.CALL_HANGUP, roomId = roomId, content = it.toContent()) }
|
||||||
.also { roomEventSender.sendEvent(it) }
|
.also { eventSenderProcessor.postEvent(it) }
|
||||||
state = CallState.Terminated
|
state = CallState.Terminated
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +132,7 @@ internal class MxCallImpl(
|
|||||||
answer = CallAnswerContent.Answer(sdp = sdp.description)
|
answer = CallAnswerContent.Answer(sdp = sdp.description)
|
||||||
)
|
)
|
||||||
.let { createEventAndLocalEcho(type = EventType.CALL_ANSWER, roomId = roomId, content = it.toContent()) }
|
.let { createEventAndLocalEcho(type = EventType.CALL_ANSWER, roomId = roomId, content = it.toContent()) }
|
||||||
.also { roomEventSender.sendEvent(it) }
|
.also { eventSenderProcessor.postEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
|
private fun createEventAndLocalEcho(localId: String = LocalEcho.createLocalEchoId(), type: String, roomId: String, content: Content): Event {
|
||||||
|
@ -17,7 +17,6 @@ package org.matrix.android.sdk.internal.session.room.relation
|
|||||||
|
|
||||||
import androidx.lifecycle.LiveData
|
import androidx.lifecycle.LiveData
|
||||||
import androidx.lifecycle.Transformations
|
import androidx.lifecycle.Transformations
|
||||||
import androidx.work.OneTimeWorkRequest
|
|
||||||
import com.squareup.inject.assisted.Assisted
|
import com.squareup.inject.assisted.Assisted
|
||||||
import com.squareup.inject.assisted.AssistedInject
|
import com.squareup.inject.assisted.AssistedInject
|
||||||
import com.zhuinden.monarchy.Monarchy
|
import com.zhuinden.monarchy.Monarchy
|
||||||
@ -39,21 +38,18 @@ import org.matrix.android.sdk.internal.database.model.TimelineEventEntity
|
|||||||
import org.matrix.android.sdk.internal.database.query.where
|
import org.matrix.android.sdk.internal.database.query.where
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.di.SessionId
|
import org.matrix.android.sdk.internal.di.SessionId
|
||||||
import org.matrix.android.sdk.internal.session.room.send.EncryptEventWorker
|
|
||||||
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoEventFactory
|
||||||
import org.matrix.android.sdk.internal.session.room.send.RedactEventWorker
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
import org.matrix.android.sdk.internal.session.room.send.SendEventWorker
|
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.TimelineSendEventWorkCommon
|
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.task.configureWith
|
import org.matrix.android.sdk.internal.task.configureWith
|
||||||
import org.matrix.android.sdk.internal.util.fetchCopyMap
|
import org.matrix.android.sdk.internal.util.fetchCopyMap
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
|
|
||||||
internal class DefaultRelationService @AssistedInject constructor(
|
internal class DefaultRelationService @AssistedInject constructor(
|
||||||
@Assisted private val roomId: String,
|
@Assisted private val roomId: String,
|
||||||
@SessionId private val sessionId: String,
|
@SessionId private val sessionId: String,
|
||||||
private val timeLineSendEventWorkCommon: TimelineSendEventWorkCommon,
|
// private val timeLineSendEventWorkCommon: TimelineSendEventWorkCommon,
|
||||||
|
private val eventSenderProcessor: EventSenderProcessor,
|
||||||
private val eventFactory: LocalEchoEventFactory,
|
private val eventFactory: LocalEchoEventFactory,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
private val findReactionEventForUndoTask: FindReactionEventForUndoTask,
|
||||||
@ -83,8 +79,7 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||||||
.none { it.addedByMe && it.key == reaction }) {
|
.none { it.addedByMe && it.key == reaction }) {
|
||||||
val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
|
val event = eventFactory.createReactionEvent(roomId, targetEventId, reaction)
|
||||||
.also { saveLocalEcho(it) }
|
.also { saveLocalEcho(it) }
|
||||||
val sendRelationWork = createSendEventWork(event, true)
|
return eventSenderProcessor.postEvent(event, false /* reaction are not encrypted*/)
|
||||||
timeLineSendEventWorkCommon.postWork(roomId, sendRelationWork)
|
|
||||||
} else {
|
} else {
|
||||||
Timber.w("Reaction already added")
|
Timber.w("Reaction already added")
|
||||||
NoOpCancellable
|
NoOpCancellable
|
||||||
@ -107,9 +102,7 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||||||
data.redactEventId?.let { toRedact ->
|
data.redactEventId?.let { toRedact ->
|
||||||
val redactEvent = eventFactory.createRedactEvent(roomId, toRedact, null)
|
val redactEvent = eventFactory.createRedactEvent(roomId, toRedact, null)
|
||||||
.also { saveLocalEcho(it) }
|
.also { saveLocalEcho(it) }
|
||||||
val redactWork = createRedactEventWork(redactEvent, toRedact, null)
|
eventSenderProcessor.postRedaction(redactEvent, null)
|
||||||
|
|
||||||
timeLineSendEventWorkCommon.postWork(roomId, redactWork)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -121,18 +114,6 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||||||
.executeBy(taskExecutor)
|
.executeBy(taskExecutor)
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO duplicate with send service?
|
|
||||||
private fun createRedactEventWork(localEvent: Event, eventId: String, reason: String?): OneTimeWorkRequest {
|
|
||||||
val sendContentWorkerParams = RedactEventWorker.Params(
|
|
||||||
sessionId,
|
|
||||||
localEvent.eventId!!,
|
|
||||||
roomId,
|
|
||||||
eventId,
|
|
||||||
reason)
|
|
||||||
val redactWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
|
||||||
return timeLineSendEventWorkCommon.createWork<RedactEventWorker>(redactWorkData, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun editTextMessage(targetEventId: String,
|
override fun editTextMessage(targetEventId: String,
|
||||||
msgType: String,
|
msgType: String,
|
||||||
newBodyText: CharSequence,
|
newBodyText: CharSequence,
|
||||||
@ -141,14 +122,7 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||||||
val event = eventFactory
|
val event = eventFactory
|
||||||
.createReplaceTextEvent(roomId, targetEventId, newBodyText, newBodyAutoMarkdown, msgType, compatibilityBodyText)
|
.createReplaceTextEvent(roomId, targetEventId, newBodyText, newBodyAutoMarkdown, msgType, compatibilityBodyText)
|
||||||
.also { saveLocalEcho(it) }
|
.also { saveLocalEcho(it) }
|
||||||
return if (cryptoService.isRoomEncrypted(roomId)) {
|
return eventSenderProcessor.postEvent(event, cryptoService.isRoomEncrypted(roomId))
|
||||||
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
|
|
||||||
val workRequest = createSendEventWork(event, false)
|
|
||||||
timeLineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, workRequest)
|
|
||||||
} else {
|
|
||||||
val workRequest = createSendEventWork(event, true)
|
|
||||||
timeLineSendEventWorkCommon.postWork(roomId, workRequest)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun editReply(replyToEdit: TimelineEvent,
|
override fun editReply(replyToEdit: TimelineEvent,
|
||||||
@ -165,14 +139,7 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||||||
compatibilityBodyText
|
compatibilityBodyText
|
||||||
)
|
)
|
||||||
.also { saveLocalEcho(it) }
|
.also { saveLocalEcho(it) }
|
||||||
return if (cryptoService.isRoomEncrypted(roomId)) {
|
return eventSenderProcessor.postEvent(event, cryptoService.isRoomEncrypted(roomId))
|
||||||
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
|
|
||||||
val workRequest = createSendEventWork(event, false)
|
|
||||||
timeLineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, workRequest)
|
|
||||||
} else {
|
|
||||||
val workRequest = createSendEventWork(event, true)
|
|
||||||
timeLineSendEventWorkCommon.postWork(roomId, workRequest)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
|
override fun fetchEditHistory(eventId: String, callback: MatrixCallback<List<Event>>) {
|
||||||
@ -189,27 +156,7 @@ internal class DefaultRelationService @AssistedInject constructor(
|
|||||||
?.also { saveLocalEcho(it) }
|
?.also { saveLocalEcho(it) }
|
||||||
?: return null
|
?: return null
|
||||||
|
|
||||||
return if (cryptoService.isRoomEncrypted(roomId)) {
|
return eventSenderProcessor.postEvent(event, cryptoService.isRoomEncrypted(roomId))
|
||||||
val encryptWork = createEncryptEventWork(event, listOf("m.relates_to"))
|
|
||||||
val workRequest = createSendEventWork(event, false)
|
|
||||||
timeLineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, workRequest)
|
|
||||||
} else {
|
|
||||||
val workRequest = createSendEventWork(event, true)
|
|
||||||
timeLineSendEventWorkCommon.postWork(roomId, workRequest)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createEncryptEventWork(event: Event, keepKeys: List<String>?): OneTimeWorkRequest {
|
|
||||||
// Same parameter
|
|
||||||
val params = EncryptEventWorker.Params(sessionId, event.eventId!!, keepKeys)
|
|
||||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
|
||||||
return timeLineSendEventWorkCommon.createWork<EncryptEventWorker>(sendWorkData, true)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, eventId = event.eventId!!)
|
|
||||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
|
||||||
return timeLineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? {
|
override fun getEventAnnotationsSummary(eventId: String): EventAnnotationsSummary? {
|
||||||
|
@ -48,10 +48,9 @@ import org.matrix.android.sdk.api.util.NoOpCancellable
|
|||||||
import org.matrix.android.sdk.internal.di.SessionId
|
import org.matrix.android.sdk.internal.di.SessionId
|
||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.TimelineSendEventWorkCommon
|
import org.matrix.android.sdk.internal.session.room.send.queue.EventSenderProcessor
|
||||||
import org.matrix.android.sdk.internal.task.TaskExecutor
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
import org.matrix.android.sdk.internal.util.CancelableWork
|
import org.matrix.android.sdk.internal.util.CancelableWork
|
||||||
import org.matrix.android.sdk.internal.worker.AlwaysSuccessfulWorker
|
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import org.matrix.android.sdk.internal.worker.startChain
|
import org.matrix.android.sdk.internal.worker.startChain
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -63,13 +62,12 @@ private const val UPLOAD_WORK = "UPLOAD_WORK"
|
|||||||
internal class DefaultSendService @AssistedInject constructor(
|
internal class DefaultSendService @AssistedInject constructor(
|
||||||
@Assisted private val roomId: String,
|
@Assisted private val roomId: String,
|
||||||
private val workManagerProvider: WorkManagerProvider,
|
private val workManagerProvider: WorkManagerProvider,
|
||||||
private val timelineSendEventWorkCommon: TimelineSendEventWorkCommon,
|
|
||||||
@SessionId private val sessionId: String,
|
@SessionId private val sessionId: String,
|
||||||
private val localEchoEventFactory: LocalEchoEventFactory,
|
private val localEchoEventFactory: LocalEchoEventFactory,
|
||||||
private val cryptoService: CryptoService,
|
private val cryptoService: CryptoService,
|
||||||
private val taskExecutor: TaskExecutor,
|
private val taskExecutor: TaskExecutor,
|
||||||
private val localEchoRepository: LocalEchoRepository,
|
private val localEchoRepository: LocalEchoRepository,
|
||||||
private val roomEventSender: RoomEventSender,
|
private val eventSenderProcessor: EventSenderProcessor,
|
||||||
private val cancelSendTracker: CancelSendTracker
|
private val cancelSendTracker: CancelSendTracker
|
||||||
) : SendService {
|
) : SendService {
|
||||||
|
|
||||||
@ -92,19 +90,6 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
.let { sendEvent(it) }
|
.let { sendEvent(it) }
|
||||||
}
|
}
|
||||||
|
|
||||||
// For test only
|
|
||||||
private fun sendTextMessages(text: CharSequence, msgType: String, autoMarkdown: Boolean, times: Int): Cancelable {
|
|
||||||
return CancelableBag().apply {
|
|
||||||
// Send the event several times
|
|
||||||
repeat(times) { i ->
|
|
||||||
localEchoEventFactory.createTextEvent(roomId, msgType, "$text - $i", autoMarkdown)
|
|
||||||
.also { createLocalEcho(it) }
|
|
||||||
.let { sendEvent(it) }
|
|
||||||
.also { add(it) }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String): Cancelable {
|
override fun sendFormattedTextMessage(text: String, formattedText: String, msgType: String): Cancelable {
|
||||||
return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType)
|
return localEchoEventFactory.createFormattedTextEvent(roomId, TextContent(text, formattedText), msgType)
|
||||||
.also { createLocalEcho(it) }
|
.also { createLocalEcho(it) }
|
||||||
@ -133,13 +118,14 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
|
|
||||||
override fun redactEvent(event: Event, reason: String?): Cancelable {
|
override fun redactEvent(event: Event, reason: String?): Cancelable {
|
||||||
// TODO manage media/attachements?
|
// TODO manage media/attachements?
|
||||||
return createRedactEventWork(event, reason)
|
val redactionEcho = localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason)
|
||||||
.let { timelineSendEventWorkCommon.postWork(roomId, it) }
|
.also { createLocalEcho(it) }
|
||||||
|
return eventSenderProcessor.postRedaction(redactionEcho, reason)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable {
|
override fun resendTextMessage(localEcho: TimelineEvent): Cancelable {
|
||||||
if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) {
|
if (localEcho.root.isTextMessage() && localEcho.root.sendState.hasFailed()) {
|
||||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT)
|
||||||
return sendEvent(localEcho.root)
|
return sendEvent(localEcho.root)
|
||||||
}
|
}
|
||||||
return NoOpCancellable
|
return NoOpCancellable
|
||||||
@ -153,7 +139,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
val url = messageContent.getFileUrl() ?: return NoOpCancellable
|
val url = messageContent.getFileUrl() ?: return NoOpCancellable
|
||||||
if (url.startsWith("mxc://")) {
|
if (url.startsWith("mxc://")) {
|
||||||
// We need to resend only the message as the attachment is ok
|
// We need to resend only the message as the attachment is ok
|
||||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT)
|
||||||
return sendEvent(localEcho.root)
|
return sendEvent(localEcho.root)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,7 +156,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
queryUri = Uri.parse(messageContent.url),
|
queryUri = Uri.parse(messageContent.url),
|
||||||
type = ContentAttachmentData.Type.IMAGE
|
type = ContentAttachmentData.Type.IMAGE
|
||||||
)
|
)
|
||||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT)
|
||||||
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
||||||
}
|
}
|
||||||
is MessageVideoContent -> {
|
is MessageVideoContent -> {
|
||||||
@ -184,7 +170,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
queryUri = Uri.parse(messageContent.url),
|
queryUri = Uri.parse(messageContent.url),
|
||||||
type = ContentAttachmentData.Type.VIDEO
|
type = ContentAttachmentData.Type.VIDEO
|
||||||
)
|
)
|
||||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT)
|
||||||
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
||||||
}
|
}
|
||||||
is MessageFileContent -> {
|
is MessageFileContent -> {
|
||||||
@ -195,7 +181,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
queryUri = Uri.parse(messageContent.url),
|
queryUri = Uri.parse(messageContent.url),
|
||||||
type = ContentAttachmentData.Type.FILE
|
type = ContentAttachmentData.Type.FILE
|
||||||
)
|
)
|
||||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT)
|
||||||
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
||||||
}
|
}
|
||||||
is MessageAudioContent -> {
|
is MessageAudioContent -> {
|
||||||
@ -207,7 +193,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
queryUri = Uri.parse(messageContent.url),
|
queryUri = Uri.parse(messageContent.url),
|
||||||
type = ContentAttachmentData.Type.AUDIO
|
type = ContentAttachmentData.Type.AUDIO
|
||||||
)
|
)
|
||||||
localEchoRepository.updateSendState(localEcho.eventId, SendState.UNSENT)
|
localEchoRepository.updateSendState(localEcho.eventId, roomId, SendState.UNSENT)
|
||||||
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
internalSendMedia(listOf(localEcho.root), attachmentData, true)
|
||||||
}
|
}
|
||||||
else -> NoOpCancellable
|
else -> NoOpCancellable
|
||||||
@ -222,25 +208,6 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun clearSendingQueue() {
|
|
||||||
timelineSendEventWorkCommon.cancelAllWorks(roomId)
|
|
||||||
workManagerProvider.workManager.cancelUniqueWork(buildWorkName(UPLOAD_WORK))
|
|
||||||
|
|
||||||
// Replace the worker chains with a AlwaysSuccessfulWorker, to ensure the queues are well emptied
|
|
||||||
workManagerProvider.matrixOneTimeWorkRequestBuilder<AlwaysSuccessfulWorker>()
|
|
||||||
.build().let {
|
|
||||||
timelineSendEventWorkCommon.postWork(roomId, it, ExistingWorkPolicy.REPLACE)
|
|
||||||
|
|
||||||
// need to clear also image sending queue
|
|
||||||
workManagerProvider.workManager
|
|
||||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.REPLACE, it)
|
|
||||||
.enqueue()
|
|
||||||
}
|
|
||||||
taskExecutor.executorScope.launch {
|
|
||||||
localEchoRepository.clearSendingQueue(roomId)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun cancelSend(eventId: String) {
|
override fun cancelSend(eventId: String) {
|
||||||
cancelSendTracker.markLocalEchoForCancel(eventId, roomId)
|
cancelSendTracker.markLocalEchoForCancel(eventId, roomId)
|
||||||
taskExecutor.executorScope.launch {
|
taskExecutor.executorScope.launch {
|
||||||
@ -262,13 +229,6 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// override fun failAllPendingMessages() {
|
|
||||||
// taskExecutor.executorScope.launch {
|
|
||||||
// val eventsToResend = localEchoRepository.getAllEventsWithStates(roomId, SendState.PENDING_STATES)
|
|
||||||
// localEchoRepository.updateSendState(roomId, eventsToResend.map { it.eventId }, SendState.UNDELIVERED)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
override fun sendMedia(attachment: ContentAttachmentData,
|
override fun sendMedia(attachment: ContentAttachmentData,
|
||||||
compressBeforeSending: Boolean,
|
compressBeforeSending: Boolean,
|
||||||
roomIds: Set<String>): Cancelable {
|
roomIds: Set<String>): Cancelable {
|
||||||
@ -301,7 +261,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
val dispatcherWork = createMultipleEventDispatcherWork(isRoomEncrypted)
|
val dispatcherWork = createMultipleEventDispatcherWork(isRoomEncrypted)
|
||||||
|
|
||||||
workManagerProvider.workManager
|
workManagerProvider.workManager
|
||||||
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND, uploadWork)
|
.beginUniqueWork(buildWorkName(UPLOAD_WORK), ExistingWorkPolicy.APPEND_OR_REPLACE, uploadWork)
|
||||||
.then(dispatcherWork)
|
.then(dispatcherWork)
|
||||||
.enqueue()
|
.enqueue()
|
||||||
.also { operation ->
|
.also { operation ->
|
||||||
@ -322,7 +282,7 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
private fun sendEvent(event: Event): Cancelable {
|
private fun sendEvent(event: Event): Cancelable {
|
||||||
return roomEventSender.sendEvent(event)
|
return eventSenderProcessor.postEvent(event, cryptoService.isRoomEncrypted(event.roomId!!))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createLocalEcho(event: Event) {
|
private fun createLocalEcho(event: Event) {
|
||||||
@ -333,28 +293,6 @@ internal class DefaultSendService @AssistedInject constructor(
|
|||||||
return "${roomId}_$identifier"
|
return "${roomId}_$identifier"
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
|
||||||
// Same parameter
|
|
||||||
return EncryptEventWorker.Params(sessionId, event.eventId ?: "")
|
|
||||||
.let { WorkerParamsFactory.toData(it) }
|
|
||||||
.let {
|
|
||||||
workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
|
||||||
.setConstraints(WorkManagerProvider.workConstraints)
|
|
||||||
.setInputData(it)
|
|
||||||
.startChain(startChain)
|
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createRedactEventWork(event: Event, reason: String?): OneTimeWorkRequest {
|
|
||||||
return localEchoEventFactory.createRedactEvent(roomId, event.eventId!!, reason)
|
|
||||||
.also { createLocalEcho(it) }
|
|
||||||
.let { RedactEventWorker.Params(sessionId, it.eventId!!, roomId, event.eventId, reason) }
|
|
||||||
.let { WorkerParamsFactory.toData(it) }
|
|
||||||
.let { timelineSendEventWorkCommon.createWork<RedactEventWorker>(it, true) }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createUploadMediaWork(allLocalEchos: List<Event>,
|
private fun createUploadMediaWork(allLocalEchos: List<Event>,
|
||||||
attachment: ContentAttachmentData,
|
attachment: ContentAttachmentData,
|
||||||
isRoomEncrypted: Boolean,
|
isRoomEncrypted: Boolean,
|
||||||
|
@ -1,146 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* 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 org.matrix.android.sdk.internal.session.room.send
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import androidx.work.WorkerParameters
|
|
||||||
import com.squareup.moshi.JsonClass
|
|
||||||
import org.matrix.android.sdk.api.failure.Failure
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.toContent
|
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
|
||||||
import org.matrix.android.sdk.internal.crypto.MXCRYPTO_ALGORITHM_MEGOLM
|
|
||||||
import org.matrix.android.sdk.internal.crypto.MXEventDecryptionResult
|
|
||||||
import org.matrix.android.sdk.internal.crypto.model.MXEncryptEventContentResult
|
|
||||||
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
|
|
||||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
|
||||||
import org.matrix.android.sdk.internal.util.awaitCallback
|
|
||||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
|
||||||
import timber.log.Timber
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Possible previous worker: None
|
|
||||||
* Possible next worker : Always [SendEventWorker]
|
|
||||||
*/
|
|
||||||
internal class EncryptEventWorker(context: Context, params: WorkerParameters)
|
|
||||||
: SessionSafeCoroutineWorker<EncryptEventWorker.Params>(context, params, Params::class.java) {
|
|
||||||
|
|
||||||
@JsonClass(generateAdapter = true)
|
|
||||||
internal data class Params(
|
|
||||||
override val sessionId: String,
|
|
||||||
val eventId: String,
|
|
||||||
/** Do not encrypt these keys, keep them as is in encrypted content (e.g. m.relates_to) */
|
|
||||||
val keepKeys: List<String>? = null,
|
|
||||||
override val lastFailureMessage: String? = null
|
|
||||||
) : SessionWorkerParams
|
|
||||||
|
|
||||||
@Inject lateinit var crypto: CryptoService
|
|
||||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
|
||||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
|
||||||
|
|
||||||
override fun injectWith(injector: SessionComponent) {
|
|
||||||
injector.inject(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
override suspend fun doSafeWork(params: Params): Result {
|
|
||||||
Timber.v("## SendEvent: Start Encrypt work for event ${params.eventId}")
|
|
||||||
|
|
||||||
val localEvent = localEchoRepository.getUpToDateEcho(params.eventId)
|
|
||||||
if (localEvent?.eventId == null) {
|
|
||||||
return Result.success()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (cancelSendTracker.isCancelRequestedFor(localEvent.eventId, localEvent.roomId)) {
|
|
||||||
return Result.success()
|
|
||||||
.also { Timber.e("## SendEvent: Event sending has been cancelled ${localEvent.eventId}") }
|
|
||||||
}
|
|
||||||
|
|
||||||
localEchoRepository.updateSendState(localEvent.eventId, SendState.ENCRYPTING)
|
|
||||||
|
|
||||||
val localMutableContent = localEvent.content?.toMutableMap() ?: mutableMapOf()
|
|
||||||
params.keepKeys?.forEach {
|
|
||||||
localMutableContent.remove(it)
|
|
||||||
}
|
|
||||||
|
|
||||||
var error: Throwable? = null
|
|
||||||
var result: MXEncryptEventContentResult? = null
|
|
||||||
try {
|
|
||||||
result = awaitCallback {
|
|
||||||
crypto.encryptEventContent(localMutableContent, localEvent.type, localEvent.roomId!!, it)
|
|
||||||
}
|
|
||||||
} catch (throwable: Throwable) {
|
|
||||||
error = throwable
|
|
||||||
}
|
|
||||||
if (result != null) {
|
|
||||||
val modifiedContent = HashMap(result.eventContent)
|
|
||||||
params.keepKeys?.forEach { toKeep ->
|
|
||||||
localEvent.content?.get(toKeep)?.let {
|
|
||||||
// put it back in the encrypted thing
|
|
||||||
modifiedContent[toKeep] = it
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// Better handling of local echo, to avoid decrypting transition on remote echo
|
|
||||||
// Should I only do it for text messages?
|
|
||||||
val decryptionLocalEcho = if (result.eventContent["algorithm"] == MXCRYPTO_ALGORITHM_MEGOLM) {
|
|
||||||
MXEventDecryptionResult(
|
|
||||||
clearEvent = Event(
|
|
||||||
type = localEvent.type,
|
|
||||||
content = localEvent.content,
|
|
||||||
roomId = localEvent.roomId
|
|
||||||
).toContent(),
|
|
||||||
forwardingCurve25519KeyChain = emptyList(),
|
|
||||||
senderCurve25519Key = result.eventContent["sender_key"] as? String,
|
|
||||||
claimedEd25519Key = crypto.getMyDevice().fingerprint()
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
localEchoRepository.updateEcho(localEvent.eventId) { _, localEcho ->
|
|
||||||
localEcho.type = EventType.ENCRYPTED
|
|
||||||
localEcho.content = ContentMapper.map(modifiedContent)
|
|
||||||
decryptionLocalEcho?.also {
|
|
||||||
localEcho.setDecryptionResult(it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
val nextWorkerParams = SendEventWorker.Params(sessionId = params.sessionId, eventId = params.eventId)
|
|
||||||
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
|
|
||||||
} else {
|
|
||||||
val sendState = when (error) {
|
|
||||||
is Failure.CryptoError -> SendState.FAILED_UNKNOWN_DEVICES
|
|
||||||
else -> SendState.UNDELIVERED
|
|
||||||
}
|
|
||||||
localEchoRepository.updateSendState(localEvent.eventId, sendState)
|
|
||||||
// always return success, or the chain will be stuck for ever!
|
|
||||||
val nextWorkerParams = SendEventWorker.Params(
|
|
||||||
sessionId = params.sessionId,
|
|
||||||
eventId = localEvent.eventId,
|
|
||||||
lastFailureMessage = error?.localizedMessage ?: "Error"
|
|
||||||
)
|
|
||||||
return Result.success(WorkerParamsFactory.toData(nextWorkerParams))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun buildErrorParams(params: Params, message: String): Params {
|
|
||||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
|
||||||
}
|
|
||||||
}
|
|
@ -88,8 +88,9 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateSendState(eventId: String, sendState: SendState) {
|
fun updateSendState(eventId: String, roomId: String?, sendState: SendState) {
|
||||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}")
|
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Update local state of $eventId to ${sendState.name}")
|
||||||
|
eventBus.post(DefaultTimeline.OnLocalEchoUpdated(roomId ?: "", eventId, sendState))
|
||||||
updateEchoAsync(eventId) { realm, sendingEventEntity ->
|
updateEchoAsync(eventId) { realm, sendingEventEntity ->
|
||||||
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
|
if (sendState == SendState.SENT && sendingEventEntity.sendState == SendState.SYNCED) {
|
||||||
// If already synced, do not put as sent
|
// If already synced, do not put as sent
|
||||||
@ -137,6 +138,14 @@ internal class LocalEchoRepository @Inject constructor(@SessionDatabase private
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fun deleteFailedEchoAsync(roomId: String, eventId: String?) {
|
||||||
|
monarchy.runTransactionSync { realm ->
|
||||||
|
TimelineEventEntity.where(realm, roomId = roomId, eventId = eventId ?: "").findFirst()?.deleteFromRealm()
|
||||||
|
EventEntity.where(realm, eventId = eventId ?: "").findFirst()?.deleteFromRealm()
|
||||||
|
roomSummaryUpdater.updateSendingInformation(realm, roomId)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
suspend fun clearSendingQueue(roomId: String) {
|
suspend fun clearSendingQueue(roomId: String) {
|
||||||
monarchy.awaitTransaction { realm ->
|
monarchy.awaitTransaction { realm ->
|
||||||
TimelineEventEntity
|
TimelineEventEntity
|
||||||
|
@ -17,7 +17,6 @@
|
|||||||
package org.matrix.android.sdk.internal.session.room.send
|
package org.matrix.android.sdk.internal.session.room.send
|
||||||
|
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import androidx.work.BackoffPolicy
|
|
||||||
import androidx.work.OneTimeWorkRequest
|
import androidx.work.OneTimeWorkRequest
|
||||||
import androidx.work.WorkerParameters
|
import androidx.work.WorkerParameters
|
||||||
import com.squareup.moshi.JsonClass
|
import com.squareup.moshi.JsonClass
|
||||||
@ -31,7 +30,6 @@ import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
|||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
||||||
import org.matrix.android.sdk.internal.worker.startChain
|
import org.matrix.android.sdk.internal.worker.startChain
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -57,7 +55,7 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
|||||||
|
|
||||||
override fun doOnError(params: Params): Result {
|
override fun doOnError(params: Params): Result {
|
||||||
params.localEchoIds.forEach { localEchoIds ->
|
params.localEchoIds.forEach { localEchoIds ->
|
||||||
localEchoRepository.updateSendState(localEchoIds.eventId, SendState.UNDELIVERED)
|
localEchoRepository.updateSendState(localEchoIds.eventId, localEchoIds.roomId, SendState.UNDELIVERED)
|
||||||
}
|
}
|
||||||
|
|
||||||
return super.doOnError(params)
|
return super.doOnError(params)
|
||||||
@ -73,20 +71,11 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
|||||||
params.localEchoIds.forEach { localEchoIds ->
|
params.localEchoIds.forEach { localEchoIds ->
|
||||||
val roomId = localEchoIds.roomId
|
val roomId = localEchoIds.roomId
|
||||||
val eventId = localEchoIds.eventId
|
val eventId = localEchoIds.eventId
|
||||||
if (params.isEncrypted) {
|
localEchoRepository.updateSendState(eventId, roomId, SendState.SENDING)
|
||||||
localEchoRepository.updateSendState(eventId, SendState.ENCRYPTING)
|
|
||||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule encrypt and send event $eventId")
|
|
||||||
val encryptWork = createEncryptEventWork(params.sessionId, eventId, true)
|
|
||||||
// Note that event will be replaced by the result of the previous work
|
|
||||||
val sendWork = createSendEventWork(params.sessionId, eventId, false)
|
|
||||||
timelineSendEventWorkCommon.postSequentialWorks(roomId, encryptWork, sendWork)
|
|
||||||
} else {
|
|
||||||
localEchoRepository.updateSendState(eventId, SendState.SENDING)
|
|
||||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event $eventId")
|
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event $eventId")
|
||||||
val sendWork = createSendEventWork(params.sessionId, eventId, true)
|
val sendWork = createSendEventWork(params.sessionId, eventId, true)
|
||||||
timelineSendEventWorkCommon.postWork(roomId, sendWork)
|
timelineSendEventWorkCommon.postWork(roomId, sendWork)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
return Result.success()
|
return Result.success()
|
||||||
}
|
}
|
||||||
@ -95,18 +84,6 @@ internal class MultipleEventSendingDispatcherWorker(context: Context, params: Wo
|
|||||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun createEncryptEventWork(sessionId: String, eventId: String, startChain: Boolean): OneTimeWorkRequest {
|
|
||||||
val params = EncryptEventWorker.Params(sessionId, eventId)
|
|
||||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
|
||||||
|
|
||||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
|
||||||
.setConstraints(WorkManagerProvider.workConstraints)
|
|
||||||
.setInputData(sendWorkData)
|
|
||||||
.startChain(startChain)
|
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createSendEventWork(sessionId: String, eventId: String, startChain: Boolean): OneTimeWorkRequest {
|
private fun createSendEventWork(sessionId: String, eventId: String, startChain: Boolean): OneTimeWorkRequest {
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, eventId = eventId)
|
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, eventId = eventId)
|
||||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
||||||
|
@ -1,75 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
|
||||||
*
|
|
||||||
* 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 org.matrix.android.sdk.internal.session.room.send
|
|
||||||
|
|
||||||
import androidx.work.BackoffPolicy
|
|
||||||
import androidx.work.OneTimeWorkRequest
|
|
||||||
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
|
||||||
import org.matrix.android.sdk.api.session.events.model.Event
|
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
|
||||||
import org.matrix.android.sdk.internal.di.SessionId
|
|
||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
|
||||||
import org.matrix.android.sdk.internal.session.room.timeline.TimelineSendEventWorkCommon
|
|
||||||
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
|
|
||||||
import org.matrix.android.sdk.internal.worker.startChain
|
|
||||||
import timber.log.Timber
|
|
||||||
import java.util.concurrent.TimeUnit
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
internal class RoomEventSender @Inject constructor(
|
|
||||||
private val workManagerProvider: WorkManagerProvider,
|
|
||||||
private val timelineSendEventWorkCommon: TimelineSendEventWorkCommon,
|
|
||||||
@SessionId private val sessionId: String,
|
|
||||||
private val cryptoService: CryptoService
|
|
||||||
) {
|
|
||||||
fun sendEvent(event: Event): Cancelable {
|
|
||||||
// Encrypted room handling
|
|
||||||
return if (cryptoService.isRoomEncrypted(event.roomId ?: "")
|
|
||||||
&& !event.isEncrypted() // In case of resend where it's already encrypted so skip to send
|
|
||||||
) {
|
|
||||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule encrypt and send event ${event.eventId}")
|
|
||||||
val encryptWork = createEncryptEventWork(event, true)
|
|
||||||
// Note that event will be replaced by the result of the previous work
|
|
||||||
val sendWork = createSendEventWork(event, false)
|
|
||||||
timelineSendEventWorkCommon.postSequentialWorks(event.roomId ?: "", encryptWork, sendWork)
|
|
||||||
} else {
|
|
||||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Schedule send event ${event.eventId}")
|
|
||||||
val sendWork = createSendEventWork(event, true)
|
|
||||||
timelineSendEventWorkCommon.postWork(event.roomId ?: "", sendWork)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createEncryptEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
|
||||||
// Same parameter
|
|
||||||
val params = EncryptEventWorker.Params(sessionId, event.eventId!!)
|
|
||||||
val sendWorkData = WorkerParamsFactory.toData(params)
|
|
||||||
|
|
||||||
return workManagerProvider.matrixOneTimeWorkRequestBuilder<EncryptEventWorker>()
|
|
||||||
.setConstraints(WorkManagerProvider.workConstraints)
|
|
||||||
.setInputData(sendWorkData)
|
|
||||||
.startChain(startChain)
|
|
||||||
.setBackoffCriteria(BackoffPolicy.LINEAR, WorkManagerProvider.BACKOFF_DELAY, TimeUnit.MILLISECONDS)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun createSendEventWork(event: Event, startChain: Boolean): OneTimeWorkRequest {
|
|
||||||
val sendContentWorkerParams = SendEventWorker.Params(sessionId = sessionId, eventId = event.eventId!!)
|
|
||||||
val sendWorkData = WorkerParamsFactory.toData(sendContentWorkerParams)
|
|
||||||
|
|
||||||
return timelineSendEventWorkCommon.createWork<SendEventWorker>(sendWorkData, startChain)
|
|
||||||
}
|
|
||||||
}
|
|
@ -22,12 +22,11 @@ import com.squareup.moshi.JsonClass
|
|||||||
import io.realm.RealmConfiguration
|
import io.realm.RealmConfiguration
|
||||||
import org.greenrobot.eventbus.EventBus
|
import org.greenrobot.eventbus.EventBus
|
||||||
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
import org.matrix.android.sdk.api.failure.shouldBeRetried
|
||||||
import org.matrix.android.sdk.api.session.events.model.Content
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
|
||||||
import org.matrix.android.sdk.internal.di.SessionDatabase
|
import org.matrix.android.sdk.internal.di.SessionDatabase
|
||||||
import org.matrix.android.sdk.internal.network.executeRequest
|
|
||||||
import org.matrix.android.sdk.internal.session.SessionComponent
|
import org.matrix.android.sdk.internal.session.SessionComponent
|
||||||
import org.matrix.android.sdk.internal.session.room.RoomAPI
|
|
||||||
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
|
||||||
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
@ -47,11 +46,14 @@ internal class SendEventWorker(context: Context,
|
|||||||
internal data class Params(
|
internal data class Params(
|
||||||
override val sessionId: String,
|
override val sessionId: String,
|
||||||
override val lastFailureMessage: String? = null,
|
override val lastFailureMessage: String? = null,
|
||||||
val eventId: String
|
val eventId: String,
|
||||||
|
// use this as an override if you want to send in clear in encrypted room
|
||||||
|
val isEncrypted: Boolean? = null
|
||||||
) : SessionWorkerParams
|
) : SessionWorkerParams
|
||||||
|
|
||||||
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
@Inject lateinit var localEchoRepository: LocalEchoRepository
|
||||||
@Inject lateinit var roomAPI: RoomAPI
|
@Inject lateinit var sendEventTask: SendEventTask
|
||||||
|
@Inject lateinit var cryptoService: CryptoService
|
||||||
@Inject lateinit var eventBus: EventBus
|
@Inject lateinit var eventBus: EventBus
|
||||||
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
@Inject lateinit var cancelSendTracker: CancelSendTracker
|
||||||
@SessionDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
|
@SessionDatabase @Inject lateinit var realmConfiguration: RealmConfiguration
|
||||||
@ -63,7 +65,7 @@ internal class SendEventWorker(context: Context,
|
|||||||
override suspend fun doSafeWork(params: Params): Result {
|
override suspend fun doSafeWork(params: Params): Result {
|
||||||
val event = localEchoRepository.getUpToDateEcho(params.eventId)
|
val event = localEchoRepository.getUpToDateEcho(params.eventId)
|
||||||
if (event?.eventId == null || event.roomId == null) {
|
if (event?.eventId == null || event.roomId == null) {
|
||||||
localEchoRepository.updateSendState(params.eventId, SendState.UNDELIVERED)
|
localEchoRepository.updateSendState(params.eventId, event?.roomId, SendState.UNDELIVERED)
|
||||||
return Result.success()
|
return Result.success()
|
||||||
.also { Timber.e("Work cancelled due to bad input data") }
|
.also { Timber.e("Work cancelled due to bad input data") }
|
||||||
}
|
}
|
||||||
@ -77,7 +79,7 @@ internal class SendEventWorker(context: Context,
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (params.lastFailureMessage != null) {
|
if (params.lastFailureMessage != null) {
|
||||||
localEchoRepository.updateSendState(event.eventId, SendState.UNDELIVERED)
|
localEchoRepository.updateSendState(event.eventId, event.roomId, SendState.UNDELIVERED)
|
||||||
// Transmit the error
|
// Transmit the error
|
||||||
return Result.success(inputData)
|
return Result.success(inputData)
|
||||||
.also { Timber.e("Work cancelled due to input error from parent") }
|
.also { Timber.e("Work cancelled due to input error from parent") }
|
||||||
@ -85,12 +87,12 @@ internal class SendEventWorker(context: Context,
|
|||||||
|
|
||||||
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Send event ${params.eventId}")
|
Timber.v("## SendEvent: [${System.currentTimeMillis()}] Send event ${params.eventId}")
|
||||||
return try {
|
return try {
|
||||||
sendEvent(event.eventId, event.roomId, event.type, event.content)
|
sendEventTask.execute(SendEventTask.Params(event, params.isEncrypted ?: cryptoService.isRoomEncrypted(event.roomId), cryptoService))
|
||||||
Result.success()
|
Result.success()
|
||||||
} catch (exception: Throwable) {
|
} catch (exception: Throwable) {
|
||||||
if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) {
|
if (/*currentAttemptCount >= MAX_NUMBER_OF_RETRY_BEFORE_FAILING ||**/ !exception.shouldBeRetried()) {
|
||||||
Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}")
|
Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed cannot retry ${params.eventId} > ${exception.localizedMessage}")
|
||||||
localEchoRepository.updateSendState(event.eventId, SendState.UNDELIVERED)
|
localEchoRepository.updateSendState(event.eventId, event.roomId, SendState.UNDELIVERED)
|
||||||
return Result.success()
|
return Result.success()
|
||||||
} else {
|
} else {
|
||||||
Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}")
|
Timber.e("## SendEvent: [${System.currentTimeMillis()}] Send event Failed schedule retry ${params.eventId} > ${exception.localizedMessage}")
|
||||||
@ -102,12 +104,4 @@ internal class SendEventWorker(context: Context,
|
|||||||
override fun buildErrorParams(params: Params, message: String): Params {
|
override fun buildErrorParams(params: Params, message: String): Params {
|
||||||
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun sendEvent(eventId: String, roomId: String, type: String, content: Content?) {
|
|
||||||
localEchoRepository.updateSendState(eventId, SendState.SENDING)
|
|
||||||
executeRequest<SendResponse>(eventBus) {
|
|
||||||
apiCall = roomAPI.send(eventId, roomId, type, content)
|
|
||||||
}
|
|
||||||
localEchoRepository.updateSendState(eventId, SendState.SENT)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,239 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.send.queue
|
||||||
|
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import kotlinx.coroutines.runBlocking
|
||||||
|
import org.matrix.android.sdk.api.auth.data.SessionParams
|
||||||
|
import org.matrix.android.sdk.api.auth.data.sessionId
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.api.failure.Failure
|
||||||
|
import org.matrix.android.sdk.api.failure.MatrixError
|
||||||
|
import org.matrix.android.sdk.api.failure.isTokenError
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.sync.SyncState
|
||||||
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
|
import org.matrix.android.sdk.internal.session.SessionScope
|
||||||
|
import org.matrix.android.sdk.internal.task.TaskExecutor
|
||||||
|
import timber.log.Timber
|
||||||
|
import java.io.IOException
|
||||||
|
import java.net.InetAddress
|
||||||
|
import java.net.InetSocketAddress
|
||||||
|
import java.net.Socket
|
||||||
|
import java.util.Timer
|
||||||
|
import java.util.TimerTask
|
||||||
|
import java.util.concurrent.LinkedBlockingQueue
|
||||||
|
import javax.inject.Inject
|
||||||
|
import kotlin.concurrent.schedule
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A simple ever running thread unique for that session responsible of sending events in order.
|
||||||
|
* Each send is retried 3 times, if there is no network (e.g if cannot ping home server) it will wait and
|
||||||
|
* periodically test reachability before resume (does not count as a retry)
|
||||||
|
*
|
||||||
|
* If the app is killed before all event were sent, on next wakeup the scheduled events will be re posted
|
||||||
|
*/
|
||||||
|
@SessionScope
|
||||||
|
internal class EventSenderProcessor @Inject constructor(
|
||||||
|
private val cryptoService: CryptoService,
|
||||||
|
private val sessionParams: SessionParams,
|
||||||
|
private val queuedTaskFactory: QueuedTaskFactory,
|
||||||
|
private val taskExecutor: TaskExecutor,
|
||||||
|
private val memento: QueueMemento
|
||||||
|
) : Thread("SENDER_THREAD_SID_${sessionParams.credentials.sessionId()}") {
|
||||||
|
|
||||||
|
private fun markAsManaged(task: QueuedTask) {
|
||||||
|
memento.track(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun markAsFinished(task: QueuedTask) {
|
||||||
|
memento.unTrack(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
// API
|
||||||
|
fun postEvent(event: Event): Cancelable {
|
||||||
|
return postEvent(event, event.roomId?.let { cryptoService.isRoomEncrypted(it) } ?: false)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun start() {
|
||||||
|
super.start()
|
||||||
|
// We should check for sending events not handled because app was killed
|
||||||
|
// But we should be careful of only took those that was submitted to us, because if it's
|
||||||
|
// for example it's a media event it is handled by some worker and he will handle it
|
||||||
|
// This is a bit fragile :/
|
||||||
|
// also some events cannot be retried manually by users, e.g reactions
|
||||||
|
// they were previously relying on workers to do the work :/ and was expected to always finally succeed
|
||||||
|
// Also some echos are not to be resent like redaction echos (fake event created for aggregation)
|
||||||
|
|
||||||
|
tryOrNull {
|
||||||
|
taskExecutor.executorScope.launch {
|
||||||
|
Timber.d("## Send relaunched pending events on restart")
|
||||||
|
memento.restoreTasks(this@EventSenderProcessor)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postEvent(event: Event, encrypt: Boolean): Cancelable {
|
||||||
|
val task = queuedTaskFactory.createSendTask(event, encrypt)
|
||||||
|
return postTask(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postRedaction(redactionLocalEcho: Event, reason: String?): Cancelable {
|
||||||
|
return postRedaction(redactionLocalEcho.eventId!!, redactionLocalEcho.redacts!!, redactionLocalEcho.roomId!!, reason)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postRedaction(redactionLocalEchoId: String, eventToRedactId: String, roomId: String, reason: String?): Cancelable {
|
||||||
|
val task = queuedTaskFactory.createRedactTask(redactionLocalEchoId, eventToRedactId, roomId, reason)
|
||||||
|
return postTask(task)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun postTask(task: QueuedTask): Cancelable {
|
||||||
|
// non blocking add to queue
|
||||||
|
sendingQueue.add(task)
|
||||||
|
markAsManaged(task)
|
||||||
|
return object : Cancelable {
|
||||||
|
override fun cancel() {
|
||||||
|
task.cancel()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
private const val RETRY_WAIT_TIME_MS = 10_000L
|
||||||
|
}
|
||||||
|
|
||||||
|
private var sendingQueue = LinkedBlockingQueue<QueuedTask>()
|
||||||
|
|
||||||
|
private var networkAvailableLock = Object()
|
||||||
|
private var canReachServer = true
|
||||||
|
private var retryNoNetworkTask: TimerTask? = null
|
||||||
|
|
||||||
|
override fun run() {
|
||||||
|
Timber.v("## SendThread started ts:${System.currentTimeMillis()}")
|
||||||
|
try {
|
||||||
|
while (!isInterrupted) {
|
||||||
|
Timber.v("## SendThread wait for task to process")
|
||||||
|
val task = sendingQueue.take()
|
||||||
|
Timber.v("## SendThread Found task to process $task")
|
||||||
|
|
||||||
|
if (task.isCancelled()) {
|
||||||
|
Timber.v("## SendThread send cancelled for $task")
|
||||||
|
// we do not execute this one
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
// we check for network connectivity
|
||||||
|
while (!canReachServer) {
|
||||||
|
Timber.v("## SendThread cannot reach server, wait ts:${System.currentTimeMillis()}")
|
||||||
|
// schedule to retry
|
||||||
|
waitForNetwork()
|
||||||
|
// if thread as been killed meanwhile
|
||||||
|
// if (state == State.KILLING) break
|
||||||
|
}
|
||||||
|
Timber.v("## Server is Reachable")
|
||||||
|
// so network is available
|
||||||
|
|
||||||
|
runBlocking {
|
||||||
|
retryLoop@ while (task.retryCount < 3) {
|
||||||
|
try {
|
||||||
|
// SendPerformanceProfiler.startStage(task.event.eventId!!, SendPerformanceProfiler.Stages.SEND_WORKER)
|
||||||
|
Timber.v("## SendThread retryLoop for $task retryCount ${task.retryCount}")
|
||||||
|
task.execute()
|
||||||
|
// sendEventTask.execute(SendEventTask.Params(task.event, task.encrypt, cryptoService))
|
||||||
|
// SendPerformanceProfiler.stopStage(task.event.eventId, SendPerformanceProfiler.Stages.SEND_WORKER)
|
||||||
|
break@retryLoop
|
||||||
|
} catch (exception: Throwable) {
|
||||||
|
when {
|
||||||
|
exception is IOException || exception is Failure.NetworkConnection -> {
|
||||||
|
canReachServer = false
|
||||||
|
task.retryCount++
|
||||||
|
if (task.retryCount >= 3) task.onTaskFailed()
|
||||||
|
while (!canReachServer) {
|
||||||
|
Timber.v("## SendThread retryLoop cannot reach server, wait ts:${System.currentTimeMillis()}")
|
||||||
|
// schedule to retry
|
||||||
|
waitForNetwork()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
(exception is Failure.ServerError && exception.error.code == MatrixError.M_LIMIT_EXCEEDED) -> {
|
||||||
|
task.retryCount++
|
||||||
|
if (task.retryCount >= 3) task.onTaskFailed()
|
||||||
|
Timber.v("## SendThread retryLoop retryable error for $task reason: ${exception.localizedMessage}")
|
||||||
|
// wait a bit
|
||||||
|
// Todo if its a quota exception can we get timout?
|
||||||
|
sleep(3_000)
|
||||||
|
continue@retryLoop
|
||||||
|
}
|
||||||
|
exception.isTokenError() -> {
|
||||||
|
Timber.v("## SendThread retryLoop retryable TOKEN error, interrupt")
|
||||||
|
// we can exit the loop
|
||||||
|
task.onTaskFailed()
|
||||||
|
throw InterruptedException()
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
Timber.v("## SendThread retryLoop Un-Retryable error, try next task")
|
||||||
|
// this task is in error, check next one?
|
||||||
|
break@retryLoop
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
markAsFinished(task)
|
||||||
|
}
|
||||||
|
} catch (interruptionException: InterruptedException) {
|
||||||
|
// will be thrown is thread is interrupted while seeping
|
||||||
|
interrupt()
|
||||||
|
Timber.v("## InterruptedException!! ${interruptionException.localizedMessage}")
|
||||||
|
}
|
||||||
|
// state = State.KILLED
|
||||||
|
// is this needed?
|
||||||
|
retryNoNetworkTask?.cancel()
|
||||||
|
Timber.w("## SendThread finished ${System.currentTimeMillis()}")
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun waitForNetwork() {
|
||||||
|
retryNoNetworkTask = Timer(SyncState.NoNetwork.toString(), false).schedule(RETRY_WAIT_TIME_MS) {
|
||||||
|
synchronized(networkAvailableLock) {
|
||||||
|
canReachServer = checkHostAvailable().also {
|
||||||
|
Timber.v("## SendThread checkHostAvailable $it")
|
||||||
|
}
|
||||||
|
networkAvailableLock.notify()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
synchronized(networkAvailableLock) { networkAvailableLock.wait() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if homeserver is reachable.
|
||||||
|
*/
|
||||||
|
private fun checkHostAvailable(): Boolean {
|
||||||
|
val host = sessionParams.homeServerConnectionConfig.homeServerUri.host ?: return false
|
||||||
|
val port = sessionParams.homeServerConnectionConfig.homeServerUri.port.takeIf { it != -1 } ?: 80
|
||||||
|
val timeout = 30_000
|
||||||
|
try {
|
||||||
|
Socket().use { socket ->
|
||||||
|
val inetAddress: InetAddress = InetAddress.getByName(host)
|
||||||
|
val inetSocketAddress = InetSocketAddress(inetAddress, port)
|
||||||
|
socket.connect(inetSocketAddress, timeout)
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
} catch (e: IOException) {
|
||||||
|
Timber.v("## EventSender isHostAvailable failure ${e.localizedMessage}")
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.send.queue
|
||||||
|
|
||||||
|
import android.content.Context
|
||||||
|
import org.matrix.android.sdk.api.auth.data.sessionId
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
import org.matrix.android.sdk.internal.di.SessionId
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
|
import timber.log.Timber
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Simple lightweight persistence
|
||||||
|
* Don't want to go in DB due to current issues
|
||||||
|
* Will never manage lots of events, it simply uses sharedPreferences.
|
||||||
|
* It is just used to remember what events/localEchos was managed by the event sender in order to
|
||||||
|
* reschedule them (and only them) on next restart
|
||||||
|
*/
|
||||||
|
internal class QueueMemento @Inject constructor(context: Context,
|
||||||
|
@SessionId sessionId: String,
|
||||||
|
private val queuedTaskFactory: QueuedTaskFactory,
|
||||||
|
private val localEchoRepository: LocalEchoRepository,
|
||||||
|
private val cryptoService: CryptoService) {
|
||||||
|
|
||||||
|
private val storage = context.getSharedPreferences("QueueMemento_$sessionId", Context.MODE_PRIVATE)
|
||||||
|
private val managedTaskInfos = mutableListOf<QueuedTask>()
|
||||||
|
|
||||||
|
fun track(task: QueuedTask) {
|
||||||
|
synchronized(managedTaskInfos) {
|
||||||
|
managedTaskInfos.add(task)
|
||||||
|
persist()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun unTrack(task: QueuedTask) {
|
||||||
|
managedTaskInfos.remove(task)
|
||||||
|
persist()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun persist() {
|
||||||
|
managedTaskInfos.mapIndexedNotNull { index, queuedTask ->
|
||||||
|
toTaskInfo(queuedTask, index)?.let { TaskInfo.map(it) }
|
||||||
|
}.toSet().let { set ->
|
||||||
|
storage.edit()
|
||||||
|
.putStringSet("ManagedBySender", set)
|
||||||
|
.apply()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun toTaskInfo(task: QueuedTask, order: Int): TaskInfo? {
|
||||||
|
synchronized(managedTaskInfos) {
|
||||||
|
return when (task) {
|
||||||
|
is SendEventQueuedTask -> SendEventTaskInfo(
|
||||||
|
localEchoId = task.event.eventId ?: "",
|
||||||
|
encrypt = task.encrypt,
|
||||||
|
order = order
|
||||||
|
)
|
||||||
|
is RedactQueuedTask -> RedactEventTaskInfo(
|
||||||
|
redactionLocalEcho = task.redactionLocalEchoId,
|
||||||
|
order = order
|
||||||
|
)
|
||||||
|
else -> null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
suspend fun restoreTasks(eventProcessor: EventSenderProcessor) {
|
||||||
|
// events should be restarted in correct order
|
||||||
|
storage.getStringSet("ManagedBySender", null)?.let { pending ->
|
||||||
|
Timber.d("## Send - Recovering unsent events $pending")
|
||||||
|
pending.mapNotNull { tryOrNull { TaskInfo.map(it) } }
|
||||||
|
}
|
||||||
|
?.sortedBy { it.order }
|
||||||
|
?.forEach { info ->
|
||||||
|
try {
|
||||||
|
when (info) {
|
||||||
|
is SendEventTaskInfo -> {
|
||||||
|
localEchoRepository.getUpToDateEcho(info.localEchoId)?.let {
|
||||||
|
if (it.sendState.isSending() && it.eventId != null && it.roomId != null) {
|
||||||
|
localEchoRepository.updateSendState(it.eventId, it.roomId, SendState.UNSENT)
|
||||||
|
Timber.d("## Send -Reschedule send $info")
|
||||||
|
eventProcessor.postTask(queuedTaskFactory.createSendTask(it, info.encrypt ?: cryptoService.isRoomEncrypted(it.roomId)))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
is RedactEventTaskInfo -> {
|
||||||
|
info.redactionLocalEcho?.let { localEchoRepository.getUpToDateEcho(it) }?.let {
|
||||||
|
localEchoRepository.updateSendState(it.eventId!!, it.roomId, SendState.UNSENT)
|
||||||
|
// try to get reason
|
||||||
|
val reason = it.content?.get("reason") as? String
|
||||||
|
if (it.redacts != null && it.roomId != null) {
|
||||||
|
Timber.d("## Send -Reschedule redact $info")
|
||||||
|
eventProcessor.postTask(queuedTaskFactory.createRedactTask(it.eventId, it.redacts, it.roomId, reason))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// postTask(queuedTaskFactory.createRedactTask(info.eventToRedactId, info.)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (failure: Throwable) {
|
||||||
|
Timber.e("failed to restore task $info")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.send.queue
|
||||||
|
|
||||||
|
abstract class QueuedTask {
|
||||||
|
var retryCount = 0
|
||||||
|
|
||||||
|
abstract suspend fun execute()
|
||||||
|
|
||||||
|
abstract fun onTaskFailed()
|
||||||
|
|
||||||
|
abstract fun isCancelled() : Boolean
|
||||||
|
|
||||||
|
abstract fun cancel()
|
||||||
|
}
|
@ -0,0 +1,57 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.send.queue
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
internal class QueuedTaskFactory @Inject constructor(
|
||||||
|
private val sendEventTask: SendEventTask,
|
||||||
|
private val cryptoService: CryptoService,
|
||||||
|
private val localEchoRepository: LocalEchoRepository,
|
||||||
|
private val redactEventTask: RedactEventTask,
|
||||||
|
private val cancelSendTracker: CancelSendTracker
|
||||||
|
) {
|
||||||
|
|
||||||
|
fun createSendTask(event: Event, encrypt: Boolean): QueuedTask {
|
||||||
|
return SendEventQueuedTask(
|
||||||
|
event = event,
|
||||||
|
encrypt = encrypt,
|
||||||
|
cryptoService = cryptoService,
|
||||||
|
localEchoRepository = localEchoRepository,
|
||||||
|
sendEventTask = sendEventTask,
|
||||||
|
cancelSendTracker = cancelSendTracker
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun createRedactTask(redactionLocalEcho: String, eventId: String, roomId: String, reason: String?): QueuedTask {
|
||||||
|
return RedactQueuedTask(
|
||||||
|
redactionLocalEchoId = redactionLocalEcho,
|
||||||
|
toRedactEventId = eventId,
|
||||||
|
roomId = roomId,
|
||||||
|
reason = reason,
|
||||||
|
redactEventTask = redactEventTask,
|
||||||
|
localEchoRepository = localEchoRepository,
|
||||||
|
cancelSendTracker = cancelSendTracker
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.send.queue
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.RedactEventTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
|
|
||||||
|
internal class RedactQueuedTask(
|
||||||
|
val toRedactEventId: String,
|
||||||
|
val redactionLocalEchoId: String,
|
||||||
|
val roomId: String,
|
||||||
|
val reason: String?,
|
||||||
|
val redactEventTask: RedactEventTask,
|
||||||
|
val localEchoRepository: LocalEchoRepository,
|
||||||
|
val cancelSendTracker: CancelSendTracker
|
||||||
|
) : QueuedTask() {
|
||||||
|
|
||||||
|
private var _isCancelled: Boolean = false
|
||||||
|
|
||||||
|
override fun toString() = "[RedactEventRunnableTask $redactionLocalEchoId]"
|
||||||
|
|
||||||
|
override suspend fun execute() {
|
||||||
|
redactEventTask.execute(RedactEventTask.Params(redactionLocalEchoId, roomId, toRedactEventId, reason))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTaskFailed() {
|
||||||
|
localEchoRepository.updateSendState(redactionLocalEchoId, roomId, SendState.UNDELIVERED)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isCancelled(): Boolean {
|
||||||
|
return _isCancelled || cancelSendTracker.isCancelRequestedFor(redactionLocalEchoId, roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancel() {
|
||||||
|
_isCancelled = true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.send.queue
|
||||||
|
|
||||||
|
import org.matrix.android.sdk.api.session.crypto.CryptoService
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.Event
|
||||||
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
|
import org.matrix.android.sdk.internal.crypto.tasks.SendEventTask
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.CancelSendTracker
|
||||||
|
import org.matrix.android.sdk.internal.session.room.send.LocalEchoRepository
|
||||||
|
|
||||||
|
internal class SendEventQueuedTask(
|
||||||
|
val event: Event,
|
||||||
|
val encrypt: Boolean,
|
||||||
|
val sendEventTask: SendEventTask,
|
||||||
|
val cryptoService: CryptoService,
|
||||||
|
val localEchoRepository: LocalEchoRepository,
|
||||||
|
val cancelSendTracker: CancelSendTracker
|
||||||
|
) : QueuedTask() {
|
||||||
|
|
||||||
|
private var _isCancelled: Boolean = false
|
||||||
|
|
||||||
|
override fun toString() = "[SendEventRunnableTask ${event.eventId}]"
|
||||||
|
|
||||||
|
override suspend fun execute() {
|
||||||
|
sendEventTask.execute(SendEventTask.Params(event, encrypt, cryptoService))
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onTaskFailed() {
|
||||||
|
when (event.getClearType()) {
|
||||||
|
EventType.REDACTION,
|
||||||
|
EventType.REACTION -> {
|
||||||
|
// we just delete? it will not be present on timeline and no ux to retry
|
||||||
|
localEchoRepository.deleteFailedEchoAsync(eventId = event.eventId, roomId = event.roomId ?: "")
|
||||||
|
// TODO update aggregation :/ or it will stay locally
|
||||||
|
}
|
||||||
|
else -> {
|
||||||
|
localEchoRepository.updateSendState(event.eventId!!, event.roomId, SendState.UNDELIVERED)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun isCancelled(): Boolean {
|
||||||
|
return _isCancelled || cancelSendTracker.isCancelRequestedFor(event.eventId, event.roomId)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun cancel() {
|
||||||
|
_isCancelled = true
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,76 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2020 The Matrix.org Foundation C.I.C.
|
||||||
|
*
|
||||||
|
* 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 org.matrix.android.sdk.internal.session.room.send.queue
|
||||||
|
|
||||||
|
import com.squareup.moshi.Json
|
||||||
|
import com.squareup.moshi.JsonClass
|
||||||
|
import com.squareup.moshi.Moshi
|
||||||
|
import org.matrix.android.sdk.api.extensions.tryOrNull
|
||||||
|
import org.matrix.android.sdk.internal.di.SerializeNulls
|
||||||
|
import org.matrix.android.sdk.internal.network.parsing.RuntimeJsonAdapterFactory
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Info that need to be persisted by the sender thread
|
||||||
|
* With polymorphic moshi parsing
|
||||||
|
*/
|
||||||
|
internal interface TaskInfo {
|
||||||
|
val type: String
|
||||||
|
val order: Int
|
||||||
|
|
||||||
|
companion object {
|
||||||
|
const val TYPE_UNKNOWN = "TYPE_UNKNOWN"
|
||||||
|
const val TYPE_SEND = "TYPE_SEND"
|
||||||
|
const val TYPE_REDACT = "TYPE_REDACT"
|
||||||
|
|
||||||
|
val moshi = Moshi.Builder()
|
||||||
|
.add(RuntimeJsonAdapterFactory.of(TaskInfo::class.java, "type", FallbackTaskInfo::class.java)
|
||||||
|
.registerSubtype(SendEventTaskInfo::class.java, TYPE_SEND)
|
||||||
|
.registerSubtype(RedactEventTaskInfo::class.java, TYPE_REDACT)
|
||||||
|
)
|
||||||
|
.add(SerializeNulls.JSON_ADAPTER_FACTORY)
|
||||||
|
.build()
|
||||||
|
|
||||||
|
fun map(info: TaskInfo): String {
|
||||||
|
return moshi.adapter(TaskInfo::class.java).toJson(info)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun map(string: String): TaskInfo? {
|
||||||
|
return tryOrNull { moshi.adapter(TaskInfo::class.java).fromJson(string) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class SendEventTaskInfo(
|
||||||
|
@Json(name = "type") override val type: String = TaskInfo.TYPE_SEND,
|
||||||
|
@Json(name = "localEchoId") val localEchoId: String,
|
||||||
|
@Json(name = "encrypt") val encrypt: Boolean?,
|
||||||
|
@Json(name = "order") override val order: Int
|
||||||
|
) : TaskInfo
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class RedactEventTaskInfo(
|
||||||
|
@Json(name = "type") override val type: String = TaskInfo.TYPE_REDACT,
|
||||||
|
@Json(name = "redactionLocalEcho") val redactionLocalEcho: String?,
|
||||||
|
@Json(name = "order") override val order: Int
|
||||||
|
) : TaskInfo
|
||||||
|
|
||||||
|
@JsonClass(generateAdapter = true)
|
||||||
|
internal data class FallbackTaskInfo(
|
||||||
|
@Json(name = "type") override val type: String = TaskInfo.TYPE_REDACT,
|
||||||
|
@Json(name = "order") override val order: Int
|
||||||
|
) : TaskInfo
|
@ -32,8 +32,11 @@ import org.matrix.android.sdk.api.extensions.tryOrNull
|
|||||||
import org.matrix.android.sdk.api.session.events.model.EventType
|
import org.matrix.android.sdk.api.session.events.model.EventType
|
||||||
import org.matrix.android.sdk.api.session.events.model.RelationType
|
import org.matrix.android.sdk.api.session.events.model.RelationType
|
||||||
import org.matrix.android.sdk.api.session.events.model.toModel
|
import org.matrix.android.sdk.api.session.events.model.toModel
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.EventAnnotationsSummary
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.ReactionAggregatedSummary
|
||||||
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
import org.matrix.android.sdk.api.session.room.model.ReadReceipt
|
||||||
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
import org.matrix.android.sdk.api.session.room.model.message.MessageContent
|
||||||
|
import org.matrix.android.sdk.api.session.room.model.relation.ReactionContent
|
||||||
import org.matrix.android.sdk.api.session.room.send.SendState
|
import org.matrix.android.sdk.api.session.room.send.SendState
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
import org.matrix.android.sdk.api.session.room.timeline.Timeline
|
||||||
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
|
||||||
@ -83,6 +86,7 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
data class OnNewTimelineEvents(val roomId: String, val eventIds: List<String>)
|
data class OnNewTimelineEvents(val roomId: String, val eventIds: List<String>)
|
||||||
data class OnLocalEchoCreated(val roomId: String, val timelineEvent: TimelineEvent)
|
data class OnLocalEchoCreated(val roomId: String, val timelineEvent: TimelineEvent)
|
||||||
|
data class OnLocalEchoUpdated(val roomId: String, val eventId: String, val sendState: SendState)
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
|
val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD")
|
||||||
@ -102,7 +106,9 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
private var prevDisplayIndex: Int? = null
|
private var prevDisplayIndex: Int? = null
|
||||||
private var nextDisplayIndex: Int? = null
|
private var nextDisplayIndex: Int? = null
|
||||||
private val inMemorySendingEvents = Collections.synchronizedList<TimelineEvent>(ArrayList())
|
|
||||||
|
private val uiEchoManager = UIEchoManager()
|
||||||
|
|
||||||
private val builtEvents = Collections.synchronizedList<TimelineEvent>(ArrayList())
|
private val builtEvents = Collections.synchronizedList<TimelineEvent>(ArrayList())
|
||||||
private val builtEventsIdMap = Collections.synchronizedMap(HashMap<String, Int>())
|
private val builtEventsIdMap = Collections.synchronizedMap(HashMap<String, Int>())
|
||||||
private val backwardsState = AtomicReference(State())
|
private val backwardsState = AtomicReference(State())
|
||||||
@ -163,10 +169,7 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
sendingEvents = roomEntity.sendingTimelineEvents.where().filterEventsWithSettings().findAll()
|
sendingEvents = roomEntity.sendingTimelineEvents.where().filterEventsWithSettings().findAll()
|
||||||
sendingEvents.addChangeListener { events ->
|
sendingEvents.addChangeListener { events ->
|
||||||
// Remove in memory as soon as they are known by database
|
uiEchoManager.sentEventsUpdated(events)
|
||||||
events.forEach { te ->
|
|
||||||
inMemorySendingEvents.removeAll { te.eventId == it.eventId }
|
|
||||||
}
|
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
|
nonFilteredEvents = buildEventQuery(realm).sort(TimelineEventEntityFields.DISPLAY_INDEX, Sort.DESCENDING).findAll()
|
||||||
@ -318,17 +321,16 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
@Subscribe(threadMode = ThreadMode.MAIN)
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
fun onLocalEchoCreated(onLocalEchoCreated: OnLocalEchoCreated) {
|
fun onLocalEchoCreated(onLocalEchoCreated: OnLocalEchoCreated) {
|
||||||
if (isLive && onLocalEchoCreated.roomId == roomId) {
|
if (uiEchoManager.onLocalEchoCreated(onLocalEchoCreated)) {
|
||||||
// do not add events that would have been filtered
|
|
||||||
if (listOf(onLocalEchoCreated.timelineEvent).filterEventsWithSettings().isNotEmpty()) {
|
|
||||||
listeners.forEach {
|
|
||||||
it.onNewTimelineEvents(listOf(onLocalEchoCreated.timelineEvent.eventId))
|
|
||||||
}
|
|
||||||
Timber.v("On local echo created: ${onLocalEchoCreated.timelineEvent.eventId}")
|
|
||||||
inMemorySendingEvents.add(0, onLocalEchoCreated.timelineEvent)
|
|
||||||
postSnapshot()
|
postSnapshot()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Subscribe(threadMode = ThreadMode.MAIN)
|
||||||
|
fun onLocalEchoUpdated(onLocalEchoUpdated: OnLocalEchoUpdated) {
|
||||||
|
if (uiEchoManager.onLocalEchoUpdated(onLocalEchoUpdated)) {
|
||||||
|
postSnapshot()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Private methods *****************************************************************************
|
// Private methods *****************************************************************************
|
||||||
@ -407,10 +409,12 @@ internal class DefaultTimeline(
|
|||||||
private fun buildSendingEvents(): List<TimelineEvent> {
|
private fun buildSendingEvents(): List<TimelineEvent> {
|
||||||
val builtSendingEvents = ArrayList<TimelineEvent>()
|
val builtSendingEvents = ArrayList<TimelineEvent>()
|
||||||
if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) {
|
if (hasReachedEnd(Timeline.Direction.FORWARDS) && !hasMoreInCache(Timeline.Direction.FORWARDS)) {
|
||||||
builtSendingEvents.addAll(inMemorySendingEvents.filterEventsWithSettings())
|
builtSendingEvents.addAll(uiEchoManager.getInMemorySendingEvents().filterEventsWithSettings())
|
||||||
sendingEvents.forEach { timelineEventEntity ->
|
sendingEvents.forEach { timelineEventEntity ->
|
||||||
if (builtSendingEvents.find { it.eventId == timelineEventEntity.eventId } == null) {
|
if (builtSendingEvents.find { it.eventId == timelineEventEntity.eventId } == null) {
|
||||||
builtSendingEvents.add(timelineEventMapper.map(timelineEventEntity))
|
val element = timelineEventMapper.map(timelineEventEntity)
|
||||||
|
uiEchoManager.updateSentStateWithUiEcho(element)
|
||||||
|
builtSendingEvents.add(element)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -622,10 +626,7 @@ internal class DefaultTimeline(
|
|||||||
|
|
||||||
val timelineEvent = buildTimelineEvent(eventEntity)
|
val timelineEvent = buildTimelineEvent(eventEntity)
|
||||||
val transactionId = timelineEvent.root.unsignedData?.transactionId
|
val transactionId = timelineEvent.root.unsignedData?.transactionId
|
||||||
val sendingEvent = inMemorySendingEvents.find {
|
uiEchoManager.onSyncedEvent(transactionId)
|
||||||
it.eventId == transactionId
|
|
||||||
}
|
|
||||||
inMemorySendingEvents.remove(sendingEvent)
|
|
||||||
|
|
||||||
if (timelineEvent.isEncrypted()
|
if (timelineEvent.isEncrypted()
|
||||||
&& timelineEvent.root.mxDecryptionResult == null) {
|
&& timelineEvent.root.mxDecryptionResult == null) {
|
||||||
@ -649,7 +650,10 @@ internal class DefaultTimeline(
|
|||||||
timelineEventEntity = eventEntity,
|
timelineEventEntity = eventEntity,
|
||||||
buildReadReceipts = settings.buildReadReceipts,
|
buildReadReceipts = settings.buildReadReceipts,
|
||||||
correctedReadReceipts = hiddenReadReceipts.correctedReadReceipts(eventEntity.eventId)
|
correctedReadReceipts = hiddenReadReceipts.correctedReadReceipts(eventEntity.eventId)
|
||||||
)
|
).let {
|
||||||
|
// eventually enhance with ui echo?
|
||||||
|
(uiEchoManager.decorateEventWithReactionUiEcho(it) ?: it)
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* This has to be called on TimelineThread as it accesses realm live results
|
* This has to be called on TimelineThread as it accesses realm live results
|
||||||
@ -797,4 +801,155 @@ internal class DefaultTimeline(
|
|||||||
val isPaginating: Boolean = false,
|
val isPaginating: Boolean = false,
|
||||||
val requestedPaginationCount: Int = 0
|
val requestedPaginationCount: Int = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
|
private data class ReactionUiEchoData(
|
||||||
|
val localEchoId: String,
|
||||||
|
val reactedOnEventId: String,
|
||||||
|
val reaction: String
|
||||||
|
)
|
||||||
|
|
||||||
|
inner class UIEchoManager {
|
||||||
|
|
||||||
|
private val inMemorySendingEvents = Collections.synchronizedList<TimelineEvent>(ArrayList())
|
||||||
|
|
||||||
|
fun getInMemorySendingEvents(): List<TimelineEvent> {
|
||||||
|
return inMemorySendingEvents
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Due to lag of DB updates, we keep some UI echo of some properties to update timeline faster
|
||||||
|
*/
|
||||||
|
private val inMemorySendingStates = Collections.synchronizedMap<String, SendState>(HashMap())
|
||||||
|
|
||||||
|
private val inMemoryReactions = Collections.synchronizedMap<String, MutableList<ReactionUiEchoData>>(HashMap())
|
||||||
|
|
||||||
|
fun sentEventsUpdated(events: RealmResults<TimelineEventEntity>) {
|
||||||
|
// Remove in memory as soon as they are known by database
|
||||||
|
events.forEach { te ->
|
||||||
|
inMemorySendingEvents.removeAll { te.eventId == it.eventId }
|
||||||
|
}
|
||||||
|
inMemorySendingStates.keys.removeAll { key ->
|
||||||
|
events.find { it.eventId == key } == null
|
||||||
|
}
|
||||||
|
inMemoryReactions.keys.removeAll { key ->
|
||||||
|
events.find { it.eventId == key } == null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onLocalEchoUpdated(onLocalEchoUpdated: OnLocalEchoUpdated): Boolean {
|
||||||
|
if (isLive && onLocalEchoUpdated.roomId == roomId) {
|
||||||
|
val existingState = inMemorySendingStates[onLocalEchoUpdated.eventId]
|
||||||
|
inMemorySendingStates[onLocalEchoUpdated.eventId] = onLocalEchoUpdated.sendState
|
||||||
|
if (existingState != onLocalEchoUpdated.sendState) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
// return true if should update
|
||||||
|
fun onLocalEchoCreated(onLocalEchoCreated: OnLocalEchoCreated): Boolean {
|
||||||
|
var postSnapshot = false
|
||||||
|
if (isLive && onLocalEchoCreated.roomId == roomId) {
|
||||||
|
// Manage some ui echos (do it before filter because actual event could be filtered out)
|
||||||
|
when (onLocalEchoCreated.timelineEvent.root.getClearType()) {
|
||||||
|
EventType.REDACTION -> {
|
||||||
|
}
|
||||||
|
EventType.REACTION -> {
|
||||||
|
val content = onLocalEchoCreated.timelineEvent.root.content?.toModel<ReactionContent>()
|
||||||
|
if (RelationType.ANNOTATION == content?.relatesTo?.type) {
|
||||||
|
val reaction = content.relatesTo.key
|
||||||
|
val relatedEventID = content.relatesTo.eventId
|
||||||
|
inMemoryReactions.getOrPut(relatedEventID) { mutableListOf() }
|
||||||
|
.add(
|
||||||
|
ReactionUiEchoData(
|
||||||
|
localEchoId = onLocalEchoCreated.timelineEvent.eventId,
|
||||||
|
reactedOnEventId = relatedEventID,
|
||||||
|
reaction = reaction
|
||||||
|
)
|
||||||
|
)
|
||||||
|
postSnapshot = rebuildEvent(relatedEventID) {
|
||||||
|
decorateEventWithReactionUiEcho(it)
|
||||||
|
} || postSnapshot
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// do not add events that would have been filtered
|
||||||
|
if (listOf(onLocalEchoCreated.timelineEvent).filterEventsWithSettings().isNotEmpty()) {
|
||||||
|
listeners.forEach {
|
||||||
|
it.onNewTimelineEvents(listOf(onLocalEchoCreated.timelineEvent.eventId))
|
||||||
|
}
|
||||||
|
Timber.v("On local echo created: ${onLocalEchoCreated.timelineEvent.eventId}")
|
||||||
|
inMemorySendingEvents.add(0, onLocalEchoCreated.timelineEvent)
|
||||||
|
postSnapshot = true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return postSnapshot
|
||||||
|
}
|
||||||
|
|
||||||
|
fun decorateEventWithReactionUiEcho(timelineEvent: TimelineEvent): TimelineEvent? {
|
||||||
|
val relatedEventID = timelineEvent.eventId
|
||||||
|
val contents = inMemoryReactions[relatedEventID] ?: return null
|
||||||
|
|
||||||
|
var existingAnnotationSummary = timelineEvent.annotations ?: EventAnnotationsSummary(
|
||||||
|
relatedEventID
|
||||||
|
)
|
||||||
|
val updateReactions = existingAnnotationSummary.reactionsSummary.toMutableList()
|
||||||
|
contents.forEach { uiEchoReaction ->
|
||||||
|
val existing = updateReactions.firstOrNull { it.key == uiEchoReaction.reaction }
|
||||||
|
if (existing == null) {
|
||||||
|
// just add the new key
|
||||||
|
ReactionAggregatedSummary(
|
||||||
|
key = uiEchoReaction.reaction,
|
||||||
|
count = 1,
|
||||||
|
addedByMe = true,
|
||||||
|
firstTimestamp = System.currentTimeMillis(),
|
||||||
|
sourceEvents = emptyList(),
|
||||||
|
localEchoEvents = listOf(uiEchoReaction.localEchoId)
|
||||||
|
).let { updateReactions.add(it) }
|
||||||
|
} else {
|
||||||
|
// update Existing Key
|
||||||
|
if (!existing.localEchoEvents.contains(uiEchoReaction.localEchoId)) {
|
||||||
|
updateReactions.remove(existing)
|
||||||
|
// only update if echo is not yet there
|
||||||
|
ReactionAggregatedSummary(
|
||||||
|
key = existing.key,
|
||||||
|
count = existing.count + 1,
|
||||||
|
addedByMe = true,
|
||||||
|
firstTimestamp = existing.firstTimestamp,
|
||||||
|
sourceEvents = existing.sourceEvents,
|
||||||
|
localEchoEvents = existing.localEchoEvents + uiEchoReaction.localEchoId
|
||||||
|
|
||||||
|
).let { updateReactions.add(it) }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
existingAnnotationSummary = existingAnnotationSummary.copy(
|
||||||
|
reactionsSummary = updateReactions
|
||||||
|
)
|
||||||
|
return timelineEvent.copy(
|
||||||
|
annotations = existingAnnotationSummary
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateSentStateWithUiEcho(element: TimelineEvent) {
|
||||||
|
inMemorySendingStates[element.eventId]?.let {
|
||||||
|
// Timber.v("## ${System.currentTimeMillis()} Send event refresh echo with live state ${it} from state ${element.root.sendState}")
|
||||||
|
element.root.sendState = element.root.sendState.takeIf { it == SendState.SENT } ?: it
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onSyncedEvent(transactionId: String?) {
|
||||||
|
val sendingEvent = inMemorySendingEvents.find {
|
||||||
|
it.eventId == transactionId
|
||||||
|
}
|
||||||
|
inMemorySendingEvents.remove(sendingEvent)
|
||||||
|
// Is it too early to clear it? will be done when removed from sending anyway?
|
||||||
|
inMemoryReactions.forEach { (_, u) ->
|
||||||
|
u.filterNot { it.localEchoId == transactionId }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -21,7 +21,6 @@ import androidx.work.ExistingWorkPolicy
|
|||||||
import androidx.work.ListenableWorker
|
import androidx.work.ListenableWorker
|
||||||
import androidx.work.OneTimeWorkRequest
|
import androidx.work.OneTimeWorkRequest
|
||||||
import org.matrix.android.sdk.api.util.Cancelable
|
import org.matrix.android.sdk.api.util.Cancelable
|
||||||
import org.matrix.android.sdk.api.util.NoOpCancellable
|
|
||||||
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
import org.matrix.android.sdk.internal.di.WorkManagerProvider
|
||||||
import org.matrix.android.sdk.internal.util.CancelableWork
|
import org.matrix.android.sdk.internal.util.CancelableWork
|
||||||
import org.matrix.android.sdk.internal.worker.startChain
|
import org.matrix.android.sdk.internal.worker.startChain
|
||||||
@ -38,24 +37,6 @@ internal class TimelineSendEventWorkCommon @Inject constructor(
|
|||||||
private val workManagerProvider: WorkManagerProvider
|
private val workManagerProvider: WorkManagerProvider
|
||||||
) {
|
) {
|
||||||
|
|
||||||
fun postSequentialWorks(roomId: String, vararg workRequests: OneTimeWorkRequest): Cancelable {
|
|
||||||
return when {
|
|
||||||
workRequests.isEmpty() -> NoOpCancellable
|
|
||||||
workRequests.size == 1 -> postWork(roomId, workRequests.first())
|
|
||||||
else -> {
|
|
||||||
val firstWork = workRequests.first()
|
|
||||||
var continuation = workManagerProvider.workManager
|
|
||||||
.beginUniqueWork(buildWorkName(roomId), ExistingWorkPolicy.APPEND, firstWork)
|
|
||||||
for (i in 1 until workRequests.size) {
|
|
||||||
val workRequest = workRequests[i]
|
|
||||||
continuation = continuation.then(workRequest)
|
|
||||||
}
|
|
||||||
continuation.enqueue()
|
|
||||||
CancelableWork(workManagerProvider.workManager, firstWork.id)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun postWork(roomId: String, workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND_OR_REPLACE): Cancelable {
|
fun postWork(roomId: String, workRequest: OneTimeWorkRequest, policy: ExistingWorkPolicy = ExistingWorkPolicy.APPEND_OR_REPLACE): Cancelable {
|
||||||
workManagerProvider.workManager
|
workManagerProvider.workManager
|
||||||
.beginUniqueWork(buildWorkName(roomId), policy, workRequest)
|
.beginUniqueWork(buildWorkName(roomId), policy, workRequest)
|
||||||
@ -77,11 +58,6 @@ internal class TimelineSendEventWorkCommon @Inject constructor(
|
|||||||
return "${roomId}_$SEND_WORK"
|
return "${roomId}_$SEND_WORK"
|
||||||
}
|
}
|
||||||
|
|
||||||
fun cancelAllWorks(roomId: String) {
|
|
||||||
workManagerProvider.workManager
|
|
||||||
.cancelUniqueWork(buildWorkName(roomId))
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SEND_WORK = "SEND_WORK"
|
private const val SEND_WORK = "SEND_WORK"
|
||||||
private const val BACKOFF_DELAY = 10_000L
|
private const val BACKOFF_DELAY = 10_000L
|
||||||
|
@ -68,7 +68,6 @@ sealed class RoomDetailAction : VectorViewModelAction {
|
|||||||
|
|
||||||
data class IgnoreUser(val userId: String?) : RoomDetailAction()
|
data class IgnoreUser(val userId: String?) : RoomDetailAction()
|
||||||
|
|
||||||
object ClearSendQueue : RoomDetailAction()
|
|
||||||
object ResendAll : RoomDetailAction()
|
object ResendAll : RoomDetailAction()
|
||||||
data class StartCall(val isVideo: Boolean) : RoomDetailAction()
|
data class StartCall(val isVideo: Boolean) : RoomDetailAction()
|
||||||
object EndCall : RoomDetailAction()
|
object EndCall : RoomDetailAction()
|
||||||
|
@ -645,13 +645,6 @@ class RoomDetailFragment @Inject constructor(
|
|||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
||||||
return when (item.itemId) {
|
return when (item.itemId) {
|
||||||
R.id.clear_message_queue -> {
|
|
||||||
// This a temporary option during dev as it is not super stable
|
|
||||||
// Cancel all pending actions in room queue and post a dummy
|
|
||||||
// Then mark all sending events as undelivered
|
|
||||||
roomDetailViewModel.handle(RoomDetailAction.ClearSendQueue)
|
|
||||||
true
|
|
||||||
}
|
|
||||||
R.id.invite -> {
|
R.id.invite -> {
|
||||||
navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId)
|
navigator.openInviteUsersToRoom(requireActivity(), roomDetailArgs.roomId)
|
||||||
true
|
true
|
||||||
|
@ -251,7 +251,6 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||||||
is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
|
is RoomDetailAction.HandleTombstoneEvent -> handleTombstoneEvent(action)
|
||||||
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
|
is RoomDetailAction.ResendMessage -> handleResendEvent(action)
|
||||||
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
|
is RoomDetailAction.RemoveFailedEcho -> handleRemove(action)
|
||||||
is RoomDetailAction.ClearSendQueue -> handleClearSendQueue()
|
|
||||||
is RoomDetailAction.ResendAll -> handleResendAll()
|
is RoomDetailAction.ResendAll -> handleResendAll()
|
||||||
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
|
is RoomDetailAction.MarkAllAsRead -> handleMarkAllAsRead()
|
||||||
is RoomDetailAction.ReportContent -> handleReportContent(action)
|
is RoomDetailAction.ReportContent -> handleReportContent(action)
|
||||||
@ -542,9 +541,6 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||||||
return@withState false
|
return@withState false
|
||||||
}
|
}
|
||||||
when (itemId) {
|
when (itemId) {
|
||||||
R.id.clear_message_queue ->
|
|
||||||
// For now always disable when not in developer mode, worker cancellation is not working properly
|
|
||||||
timeline.pendingEventCount() > 0 && vectorPreferences.developerMode()
|
|
||||||
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
R.id.resend_all -> state.asyncRoomSummary()?.hasFailedSending == true
|
||||||
R.id.timeline_setting -> true
|
R.id.timeline_setting -> true
|
||||||
R.id.invite -> state.canInvite
|
R.id.invite -> state.canInvite
|
||||||
@ -1065,10 +1061,6 @@ class RoomDetailViewModel @AssistedInject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun handleClearSendQueue() {
|
|
||||||
room.clearSendingQueue()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun handleResendAll() {
|
private fun handleResendAll() {
|
||||||
room.resendAllFailedMessages()
|
room.resendAllFailedMessages()
|
||||||
}
|
}
|
||||||
|
@ -41,23 +41,27 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
|
|||||||
var movementMethod: MovementMethod? = null
|
var movementMethod: MovementMethod? = null
|
||||||
|
|
||||||
override fun bind(holder: Holder) {
|
override fun bind(holder: Holder) {
|
||||||
super.bind(holder)
|
|
||||||
holder.messageView.movementMethod = movementMethod
|
|
||||||
if (useBigFont) {
|
if (useBigFont) {
|
||||||
holder.messageView.textSize = 44F
|
holder.messageView.textSize = 44F
|
||||||
} else {
|
} else {
|
||||||
holder.messageView.textSize = 14F
|
holder.messageView.textSize = 14F
|
||||||
}
|
}
|
||||||
renderSendState(holder.messageView, holder.messageView)
|
|
||||||
holder.messageView.setOnClickListener(attributes.itemClickListener)
|
|
||||||
holder.messageView.setOnLongClickListener(attributes.itemLongClickListener)
|
|
||||||
if (searchForPills) {
|
if (searchForPills) {
|
||||||
message?.findPillsAndProcess(coroutineScope) { it.bind(holder.messageView) }
|
message?.findPillsAndProcess(coroutineScope) {
|
||||||
|
// mmm.. not sure this is so safe in regards to cell reuse
|
||||||
|
it.bind(holder.messageView)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
val textFuture = PrecomputedTextCompat.getTextFuture(
|
val textFuture = PrecomputedTextCompat.getTextFuture(
|
||||||
message ?: "",
|
message ?: "",
|
||||||
TextViewCompat.getTextMetricsParams(holder.messageView),
|
TextViewCompat.getTextMetricsParams(holder.messageView),
|
||||||
null)
|
null)
|
||||||
|
super.bind(holder)
|
||||||
|
holder.messageView.movementMethod = movementMethod
|
||||||
|
|
||||||
|
renderSendState(holder.messageView, holder.messageView)
|
||||||
|
holder.messageView.setOnClickListener(attributes.itemClickListener)
|
||||||
|
holder.messageView.setOnLongClickListener(attributes.itemLongClickListener)
|
||||||
holder.messageView.setTextFuture(textFuture)
|
holder.messageView.setTextFuture(textFuture)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,11 +68,4 @@
|
|||||||
app:showAsAction="never"
|
app:showAsAction="never"
|
||||||
tools:visible="true" />
|
tools:visible="true" />
|
||||||
|
|
||||||
<item
|
|
||||||
android:id="@+id/clear_message_queue"
|
|
||||||
android:title="@string/clear_timeline_send_queue"
|
|
||||||
android:visible="@bool/debug_mode"
|
|
||||||
app:showAsAction="never"
|
|
||||||
tools:visible="true" />
|
|
||||||
|
|
||||||
</menu>
|
</menu>
|
Loading…
Reference in New Issue
Block a user