Create a PermalinkFactory to avoid injecting the service in SDK classes

This commit is contained in:
Benoit Marty 2020-08-20 17:41:01 +02:00
parent 2c90e33ceb
commit fde2bdf304
3 changed files with 130 additions and 80 deletions

View file

@ -19,44 +19,26 @@ package org.matrix.android.sdk.internal.session.permalinks
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.permalinks.PermalinkService import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.RoomGetter
import java.net.URLEncoder
import javax.inject.Inject import javax.inject.Inject
import javax.inject.Provider
internal class DefaultPermalinkService @Inject constructor( internal class DefaultPermalinkService @Inject constructor(
@UserId private val permalinkFactory: PermalinkFactory
private val userId: String,
// Use a provider to fix circular Dagger dependency
private val roomGetterProvider: Provider<RoomGetter>
) : PermalinkService { ) : PermalinkService {
override fun createPermalink(event: Event): String? { override fun createPermalink(event: Event): String? {
if (event.roomId.isNullOrEmpty() || event.eventId.isNullOrEmpty()) { return permalinkFactory.createPermalink(event)
return null
}
return createPermalink(event.roomId, event.eventId)
} }
override fun createPermalink(id: String): String? { override fun createPermalink(id: String): String? {
return if (id.isEmpty()) { return permalinkFactory.createPermalink(id)
null
} else MATRIX_TO_URL_BASE + escape(id)
} }
override fun createRoomPermalink(roomId: String): String? { override fun createRoomPermalink(roomId: String): String? {
return if (roomId.isEmpty()) { return permalinkFactory.createRoomPermalink(roomId)
null
} else {
MATRIX_TO_URL_BASE + escape(roomId) + computeViaParams(userId, roomId)
}
} }
override fun createPermalink(roomId: String, eventId: String): String { override fun createPermalink(roomId: String, eventId: String): String {
return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) + computeViaParams(userId, roomId) return permalinkFactory.createPermalink(roomId, eventId)
} }
override fun getLinkedId(url: String): String? { override fun getLinkedId(url: String): String? {
@ -66,54 +48,4 @@ internal class DefaultPermalinkService @Inject constructor(
url.substring(MATRIX_TO_URL_BASE.length) url.substring(MATRIX_TO_URL_BASE.length)
} else null } else null
} }
/**
* Compute the via parameters.
* Take up to 3 homeserver domains, taking the most representative one regarding room members and including the
* current user one.
*/
private fun computeViaParams(userId: String, roomId: String): String {
val userHomeserver = userId.substringAfter(":")
return getUserIdsOfJoinedMembers(roomId)
.map { it.substringAfter(":") }
.groupBy { it }
.mapValues { it.value.size }
.toMutableMap()
// Ensure the user homeserver will be included
.apply { this[userHomeserver] = Int.MAX_VALUE }
.let { map -> map.keys.sortedByDescending { map[it] } }
.take(3)
.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
}
/**
* Escape '/' in id, because it is used as a separator
*
* @param id the id to escape
* @return the escaped id
*/
private fun escape(id: String): String {
return id.replace("/", "%2F")
}
/**
* Unescape '/' in id
*
* @param id the id to escape
* @return the escaped id
*/
private fun unescape(id: String): String {
return id.replace("%2F", "/")
}
/**
* Get a set of userIds of joined members of a room
*/
private fun getUserIdsOfJoinedMembers(roomId: String): Set<String> {
return roomGetterProvider.get().getRoom(roomId)
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
?.map { it.userId }
.orEmpty()
.toSet()
}
} }

View file

@ -0,0 +1,118 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.matrix.android.sdk.internal.session.permalinks
import org.matrix.android.sdk.api.session.events.model.Event
import org.matrix.android.sdk.api.session.permalinks.PermalinkService.Companion.MATRIX_TO_URL_BASE
import org.matrix.android.sdk.api.session.room.members.roomMemberQueryParams
import org.matrix.android.sdk.api.session.room.model.Membership
import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.room.RoomGetter
import java.net.URLEncoder
import javax.inject.Inject
import javax.inject.Provider
internal class PermalinkFactory @Inject constructor(
@UserId
private val userId: String,
// Use a provider to fix circular Dagger dependency
private val roomGetterProvider: Provider<RoomGetter>
) {
fun createPermalink(event: Event): String? {
if (event.roomId.isNullOrEmpty() || event.eventId.isNullOrEmpty()) {
return null
}
return createPermalink(event.roomId, event.eventId)
}
fun createPermalink(id: String): String? {
return if (id.isEmpty()) {
null
} else MATRIX_TO_URL_BASE + escape(id)
}
fun createRoomPermalink(roomId: String): String? {
return if (roomId.isEmpty()) {
null
} else {
MATRIX_TO_URL_BASE + escape(roomId) + computeViaParams(userId, roomId)
}
}
fun createPermalink(roomId: String, eventId: String): String {
return MATRIX_TO_URL_BASE + escape(roomId) + "/" + escape(eventId) + computeViaParams(userId, roomId)
}
fun getLinkedId(url: String): String? {
val isSupported = url.startsWith(MATRIX_TO_URL_BASE)
return if (isSupported) {
url.substring(MATRIX_TO_URL_BASE.length)
} else null
}
/**
* Compute the via parameters.
* Take up to 3 homeserver domains, taking the most representative one regarding room members and including the
* current user one.
*/
private fun computeViaParams(userId: String, roomId: String): String {
val userHomeserver = userId.substringAfter(":")
return getUserIdsOfJoinedMembers(roomId)
.map { it.substringAfter(":") }
.groupBy { it }
.mapValues { it.value.size }
.toMutableMap()
// Ensure the user homeserver will be included
.apply { this[userHomeserver] = Int.MAX_VALUE }
.let { map -> map.keys.sortedByDescending { map[it] } }
.take(3)
.joinToString(prefix = "?via=", separator = "&via=") { URLEncoder.encode(it, "utf-8") }
}
/**
* Escape '/' in id, because it is used as a separator
*
* @param id the id to escape
* @return the escaped id
*/
private fun escape(id: String): String {
return id.replace("/", "%2F")
}
/**
* Unescape '/' in id
*
* @param id the id to escape
* @return the escaped id
*/
private fun unescape(id: String): String {
return id.replace("%2F", "/")
}
/**
* Get a set of userIds of joined members of a room
*/
private fun getUserIdsOfJoinedMembers(roomId: String): Set<String> {
return roomGetterProvider.get().getRoom(roomId)
?.getRoomMembers(roomMemberQueryParams { memberships = listOf(Membership.JOIN) })
?.map { it.userId }
.orEmpty()
.toSet()
}
}

View file

@ -30,7 +30,6 @@ import org.matrix.android.sdk.api.session.events.model.LocalEcho
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.UnsignedData import org.matrix.android.sdk.api.session.events.model.UnsignedData
import org.matrix.android.sdk.api.session.events.model.toContent import org.matrix.android.sdk.api.session.events.model.toContent
import org.matrix.android.sdk.api.session.permalinks.PermalinkService
import org.matrix.android.sdk.api.session.room.model.message.AudioInfo import org.matrix.android.sdk.api.session.room.model.message.AudioInfo
import org.matrix.android.sdk.api.session.room.model.message.FileInfo import org.matrix.android.sdk.api.session.room.model.message.FileInfo
import org.matrix.android.sdk.api.session.room.model.message.ImageInfo import org.matrix.android.sdk.api.session.room.model.message.ImageInfo
@ -59,6 +58,7 @@ import org.matrix.android.sdk.api.session.room.timeline.getLastMessageContent
import org.matrix.android.sdk.api.session.room.timeline.isReply import org.matrix.android.sdk.api.session.room.timeline.isReply
import org.matrix.android.sdk.internal.di.UserId import org.matrix.android.sdk.internal.di.UserId
import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor import org.matrix.android.sdk.internal.session.content.ThumbnailExtractor
import org.matrix.android.sdk.internal.session.permalinks.PermalinkFactory
import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils import org.matrix.android.sdk.internal.session.room.send.pills.TextPillsUtils
import org.matrix.android.sdk.internal.util.StringProvider import org.matrix.android.sdk.internal.util.StringProvider
import javax.inject.Inject import javax.inject.Inject
@ -79,7 +79,7 @@ internal class LocalEchoEventFactory @Inject constructor(
private val markdownParser: MarkdownParser, private val markdownParser: MarkdownParser,
private val textPillsUtils: TextPillsUtils, private val textPillsUtils: TextPillsUtils,
private val localEchoRepository: LocalEchoRepository, private val localEchoRepository: LocalEchoRepository,
private val permalinkService: PermalinkService private val permalinkFactory: PermalinkFactory
) { ) {
fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event { fun createTextEvent(roomId: String, msgType: String, text: CharSequence, autoMarkdown: Boolean): Event {
if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) { if (msgType == MessageType.MSGTYPE_TEXT || msgType == MessageType.MSGTYPE_EMOTE) {
@ -168,8 +168,8 @@ internal class LocalEchoEventFactory @Inject constructor(
newBodyAutoMarkdown: Boolean, newBodyAutoMarkdown: Boolean,
msgType: String, msgType: String,
compatibilityText: String): Event { compatibilityText: String): Event {
val permalink = permalinkService.createPermalink(roomId, originalEvent.root.eventId ?: "") val permalink = permalinkFactory.createPermalink(roomId, originalEvent.root.eventId ?: "")
val userLink = originalEvent.root.senderId?.let { permalinkService.createPermalink(it) } ?: "" val userLink = originalEvent.root.senderId?.let { permalinkFactory.createPermalink(it) } ?: ""
val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply()) val body = bodyForReply(originalEvent.getLastMessageContent(), originalEvent.isReply())
val replyFormatted = REPLY_PATTERN.format( val replyFormatted = REPLY_PATTERN.format(
@ -205,7 +205,7 @@ internal class LocalEchoEventFactory @Inject constructor(
ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment) ContentAttachmentData.Type.IMAGE -> createImageEvent(roomId, attachment)
ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment) ContentAttachmentData.Type.VIDEO -> createVideoEvent(roomId, attachment)
ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment) ContentAttachmentData.Type.AUDIO -> createAudioEvent(roomId, attachment)
ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment) ContentAttachmentData.Type.FILE -> createFileEvent(roomId, attachment)
} }
} }
@ -365,9 +365,9 @@ internal class LocalEchoEventFactory @Inject constructor(
autoMarkdown: Boolean): Event? { autoMarkdown: Boolean): Event? {
// Fallbacks and event representation // Fallbacks and event representation
// TODO Add error/warning logs when any of this is null // TODO Add error/warning logs when any of this is null
val permalink = permalinkService.createPermalink(eventReplied.root) ?: return null val permalink = permalinkFactory.createPermalink(eventReplied.root) ?: return null
val userId = eventReplied.root.senderId ?: return null val userId = eventReplied.root.senderId ?: return null
val userLink = permalinkService.createPermalink(userId) ?: return null val userLink = permalinkFactory.createPermalink(userId) ?: return null
val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply()) val body = bodyForReply(eventReplied.getLastMessageContent(), eventReplied.isReply())
val replyFormatted = REPLY_PATTERN.format( val replyFormatted = REPLY_PATTERN.format(