Skip to content

Commit

Permalink
Merge pull request #6128 from vector-im/feature/mna/PSF-999-auto-refr…
Browse files Browse the repository at this point in the history
…esh-db

[Location sharing] - Update DB entity when a live is timed out (PSF-999)
  • Loading branch information
mnaturel authored May 25, 2022
2 parents f5a621a + 791d4fb commit c8fb034
Show file tree
Hide file tree
Showing 9 changed files with 167 additions and 18 deletions.
1 change: 1 addition & 0 deletions changelog.d/6123.wip
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
[Live location sharing] Update entity in DB when a live is timed out
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,9 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati
* Aggregation info concerning a live location share.
*/
data class LiveLocationShareAggregatedSummary(
/**
* Indicate whether the live is currently running.
*/
val isActive: Boolean?,
val endOfLiveTimestampMillis: Long?,
val lastLocationDataContent: MessageBeaconLocationDataContent?,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,9 @@ internal open class LiveLocationShareAggregatedSummaryEntity(

var roomId: String = "",

/**
* Indicate whether the live is currently running.
*/
var isActive: Boolean? = null,

var endOfLiveTimestampMillis: Long? = null,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,3 +55,11 @@ internal fun LiveLocationShareAggregatedSummaryEntity.Companion.getOrCreate(
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
?: LiveLocationShareAggregatedSummaryEntity.create(realm, roomId, eventId)
}

internal fun LiveLocationShareAggregatedSummaryEntity.Companion.get(
realm: Realm,
roomId: String,
eventId: String,
): LiveLocationShareAggregatedSummaryEntity? {
return LiveLocationShareAggregatedSummaryEntity.where(realm, roomId, eventId).findFirst()
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ import org.matrix.android.sdk.internal.session.profile.ProfileModule
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
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.aggregation.livelocation.DeactivateLiveLocationShareWorker
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.SendEventWorker
Expand Down Expand Up @@ -131,6 +132,8 @@ internal interface SessionComponent {

fun inject(worker: UpdateTrustWorker)

fun inject(worker: DeactivateLiveLocationShareWorker)

@Component.Factory
interface Factory {
fun create(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
/*
* Copyright (c) 2022 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.aggregation.livelocation

import android.content.Context
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import io.realm.RealmConfiguration
import org.matrix.android.sdk.api.util.md5
import org.matrix.android.sdk.internal.SessionManager
import org.matrix.android.sdk.internal.database.awaitTransaction
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.query.get
import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.session.SessionComponent
import org.matrix.android.sdk.internal.worker.SessionSafeCoroutineWorker
import org.matrix.android.sdk.internal.worker.SessionWorkerParams
import timber.log.Timber
import javax.inject.Inject

/**
* Worker dedicated to update live location summary data so that it is considered as deactivated.
* For the context: it is needed since a live location share should be deactivated after a certain timeout.
*/
internal class DeactivateLiveLocationShareWorker(context: Context, params: WorkerParameters, sessionManager: SessionManager) :
SessionSafeCoroutineWorker<DeactivateLiveLocationShareWorker.Params>(
context,
params,
sessionManager,
Params::class.java
) {

@JsonClass(generateAdapter = true)
internal data class Params(
override val sessionId: String,
override val lastFailureMessage: String? = null,
val eventId: String,
val roomId: String
) : SessionWorkerParams

@SessionDatabase
@Inject lateinit var realmConfiguration: RealmConfiguration

override fun injectWith(injector: SessionComponent) {
injector.inject(this)
}

override suspend fun doSafeWork(params: Params): Result {
return runCatching {
deactivateLiveLocationShare(params)
}.fold(
onSuccess = {
Result.success()
},
onFailure = {
Timber.e("failed to deactivate live, eventId: ${params.eventId}, roomId: ${params.roomId}")
Result.failure()
}
)
}

private suspend fun deactivateLiveLocationShare(params: Params) {
awaitTransaction(realmConfiguration) { realm ->
val aggregatedSummary = LiveLocationShareAggregatedSummaryEntity.get(
realm = realm,
roomId = params.roomId,
eventId = params.eventId
)
aggregatedSummary?.isActive = false
}
}

override fun buildErrorParams(params: Params, message: String): Params {
return params.copy(lastFailureMessage = params.lastFailureMessage ?: message)
}

companion object {
fun getWorkName(eventId: String, roomId: String): String {
val hash = "$eventId$roomId".md5()
return "DeactivateLiveLocationWork-$hash"
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@

package org.matrix.android.sdk.internal.session.room.aggregation.livelocation

import androidx.work.ExistingWorkPolicy
import io.realm.Realm
import org.matrix.android.sdk.api.extensions.orTrue
import org.matrix.android.sdk.api.session.events.model.Event
Expand All @@ -26,17 +27,27 @@ import org.matrix.android.sdk.api.session.room.model.message.MessageBeaconLocati
import org.matrix.android.sdk.internal.database.mapper.ContentMapper
import org.matrix.android.sdk.internal.database.model.livelocation.LiveLocationShareAggregatedSummaryEntity
import org.matrix.android.sdk.internal.database.query.getOrCreate
import org.matrix.android.sdk.internal.di.SessionId
import org.matrix.android.sdk.internal.di.WorkManagerProvider
import org.matrix.android.sdk.internal.util.time.Clock
import org.matrix.android.sdk.internal.worker.WorkerParamsFactory
import timber.log.Timber
import java.util.concurrent.TimeUnit
import javax.inject.Inject

internal class LiveLocationAggregationProcessor @Inject constructor() {
internal class LiveLocationAggregationProcessor @Inject constructor(
@SessionId private val sessionId: String,
private val workManagerProvider: WorkManagerProvider,
private val clock: Clock,
) {

fun handleBeaconInfo(realm: Realm, event: Event, content: MessageBeaconInfoContent, roomId: String, isLocalEcho: Boolean) {
if (event.senderId.isNullOrEmpty() || isLocalEcho) {
return
}

val targetEventId = if (content.isLive.orTrue()) {
val isLive = content.isLive.orTrue()
val targetEventId = if (isLive) {
event.eventId
} else {
// when live is set to false, we use the id of the event that should have been replaced
Expand All @@ -56,8 +67,39 @@ internal class LiveLocationAggregationProcessor @Inject constructor() {

Timber.d("updating summary of id=$targetEventId with isLive=${content.isLive}")

aggregatedSummary.endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
aggregatedSummary.isActive = content.isLive
val endOfLiveTimestampMillis = content.getBestTimestampMillis()?.let { it + (content.timeout ?: 0) }
aggregatedSummary.endOfLiveTimestampMillis = endOfLiveTimestampMillis
aggregatedSummary.isActive = isLive

if (isLive) {
scheduleDeactivationAfterTimeout(targetEventId, roomId, endOfLiveTimestampMillis)
} else {
cancelDeactivationAfterTimeout(targetEventId, roomId)
}
}

private fun scheduleDeactivationAfterTimeout(eventId: String, roomId: String, endOfLiveTimestampMillis: Long?) {
endOfLiveTimestampMillis ?: return

val workParams = DeactivateLiveLocationShareWorker.Params(sessionId = sessionId, eventId = eventId, roomId = roomId)
val workData = WorkerParamsFactory.toData(workParams)
val workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = eventId, roomId = roomId)
val workDelayMillis = (endOfLiveTimestampMillis - clock.epochMillis()).coerceAtLeast(0)
val workRequest = workManagerProvider.matrixOneTimeWorkRequestBuilder<DeactivateLiveLocationShareWorker>()
.setInitialDelay(workDelayMillis, TimeUnit.MILLISECONDS)
.setInputData(workData)
.build()

workManagerProvider.workManager.enqueueUniqueWork(
workName,
ExistingWorkPolicy.REPLACE,
workRequest
)
}

private fun cancelDeactivationAfterTimeout(eventId: String, roomId: String) {
val workName = DeactivateLiveLocationShareWorker.getWorkName(eventId = eventId, roomId = roomId)
workManagerProvider.workManager.cancelUniqueWork(workName)
}

fun handleBeaconLocationData(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ import org.matrix.android.sdk.internal.di.MatrixScope
import org.matrix.android.sdk.internal.session.content.UploadContentWorker
import org.matrix.android.sdk.internal.session.group.GetGroupDataWorker
import org.matrix.android.sdk.internal.session.pushers.AddPusherWorker
import org.matrix.android.sdk.internal.session.room.aggregation.livelocation.DeactivateLiveLocationShareWorker
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.SendEventWorker
Expand Down Expand Up @@ -64,9 +65,11 @@ internal class MatrixWorkerFactory @Inject constructor(private val sessionManage
SyncWorker(appContext, workerParameters, sessionManager)
UpdateTrustWorker::class.java.name ->
UpdateTrustWorker(appContext, workerParameters, sessionManager)
UploadContentWorker::class.java.name ->
UploadContentWorker::class.java.name ->
UploadContentWorker(appContext, workerParameters, sessionManager)
else -> {
DeactivateLiveLocationShareWorker::class.java.name ->
DeactivateLiveLocationShareWorker(appContext, workerParameters, sessionManager)
else -> {
Timber.w("No worker defined on MatrixWorkerFactory for $workerClassName will delegate to default.")
// Return null to delegate to the default WorkerFactory.
null
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,6 @@ import im.vector.app.features.home.room.detail.timeline.item.MessageLiveLocation
import im.vector.app.features.location.INITIAL_MAP_ZOOM_IN_TIMELINE
import im.vector.app.features.location.UrlMapProvider
import im.vector.app.features.location.toLocationData
import org.matrix.android.sdk.api.extensions.orFalse
import org.matrix.android.sdk.api.session.Session
import org.matrix.android.sdk.api.session.room.timeline.TimelineEvent
import org.threeten.bp.LocalDateTime
Expand Down Expand Up @@ -129,7 +128,7 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
private fun getViewState(liveLocationShareSummaryData: LiveLocationShareSummaryData?): LiveLocationShareViewState {
return when {
liveLocationShareSummaryData?.isActive == null -> LiveLocationShareViewState.Unkwown
liveLocationShareSummaryData.isActive.not() || isLiveTimedOut(liveLocationShareSummaryData) -> LiveLocationShareViewState.Inactive
liveLocationShareSummaryData.isActive.not() -> LiveLocationShareViewState.Inactive
liveLocationShareSummaryData.isActive && liveLocationShareSummaryData.lastGeoUri.isNullOrEmpty() -> LiveLocationShareViewState.Loading
else ->
LiveLocationShareViewState.Running(
Expand All @@ -139,16 +138,6 @@ class LiveLocationShareMessageItemFactory @Inject constructor(
}.also { viewState -> Timber.d("computed viewState: $viewState") }
}

private fun isLiveTimedOut(liveLocationShareSummaryData: LiveLocationShareSummaryData): Boolean {
return getEndOfLiveDateTime(liveLocationShareSummaryData)
?.let { endOfLive ->
// this will only cover users with different timezones but not users with manually time set
val now = LocalDateTime.now()
now.isAfter(endOfLive)
}
.orFalse()
}

private fun getEndOfLiveDateTime(liveLocationShareSummaryData: LiveLocationShareSummaryData): LocalDateTime? {
return liveLocationShareSummaryData.endOfLiveTimestampMillis?.let { DateProvider.toLocalDateTime(timestamp = it) }
}
Expand Down

0 comments on commit c8fb034

Please sign in to comment.