Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Handle room metadata updates from the room_metadata component #1139

Merged
merged 2 commits into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,9 @@ interface ChatRoom {
/** Updates the list of members that are allowed to unmute audio or video. */
fun setAvModerationWhitelist(mediaType: MediaType, whitelist: List<String>)

/** Update the value in the room_metadata structure */
fun setRoomMetadata(roomMetadata: RoomMetadata)

/** whether the current A/V moderation setting allow the member [jid] to unmute (for a specific [mediaType]). */
fun isMemberAllowedToUnmute(jid: Jid, mediaType: MediaType): Boolean

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -265,6 +265,12 @@ class ChatRoomImpl(
val config = muc.configurationForm
parseConfigForm(config)

// We only read the initial metadata from the config form. Setting room metadata after a config form reload may
// race with updates coming via [RoomMetadataHandler].
config.getRoomMetadata()?.let {
setRoomMetadata(it)
}

// Make the room non-anonymous, so that others can recognize focus JID
val answer = config.fillableForm
answer.setAnswer(MucConfigFields.WHOIS, "anyone")
Expand All @@ -283,23 +289,22 @@ class ChatRoomImpl(
)
}

override fun setRoomMetadata(roomMetadata: RoomMetadata) {
transcriptionRequested = roomMetadata.metadata?.recording?.isTranscribingEnabled == true
}

/** Read the fields we care about from [configForm] and update local state. */
private fun parseConfigForm(configForm: Form) {
lobbyEnabled =
configForm.getField(MucConfigFormManager.MUC_ROOMCONFIG_MEMBERSONLY)?.firstValue?.toBoolean() ?: false
visitorsEnabled = configForm.getField(MucConfigFields.VISITORS_ENABLED)?.firstValue?.toBoolean()
participantsSoftLimit = configForm.getField(MucConfigFields.PARTICIPANTS_SOFT_LIMIT)?.firstValue?.toInt()
// Default to false unless specified.
val roomMetadata = configForm.getRoomMetadata()
if (roomMetadata != null) {
transcriptionRequested = roomMetadata.recording?.isTranscribingEnabled == true
}
}

private fun Form.getRoomMetadata(): RoomMetadata.Metadata? {
private fun Form.getRoomMetadata(): RoomMetadata? {
getField("muc#roominfo_jitsimetadata")?.firstValue?.let {
try {
return RoomMetadata.parse(it).metadata
return RoomMetadata.parse(it)
} catch (e: Exception) {
logger.warn("Invalid room metadata content", e)
return null
Expand Down
59 changes: 0 additions & 59 deletions jicofo/src/main/kotlin/org/jitsi/jicofo/util/QueueExecutor.kt

This file was deleted.

107 changes: 107 additions & 0 deletions jicofo/src/main/kotlin/org/jitsi/jicofo/xmpp/RoomMetadataHandler.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
/*
* Jicofo, the Jitsi Conference Focus.
*
* Copyright @ 2024-Present 8x8, Inc.
*
* 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.jitsi.jicofo.xmpp

import org.jitsi.jicofo.ConferenceStore
import org.jitsi.jicofo.TaskPools
import org.jitsi.jicofo.xmpp.muc.RoomMetadata
import org.jitsi.utils.OrderedJsonObject
import org.jitsi.utils.logging2.createLogger
import org.jitsi.utils.queue.PacketQueue
import org.jitsi.xmpp.extensions.jitsimeet.JsonMessageExtension
import org.jivesoftware.smack.StanzaListener
import org.jivesoftware.smack.filter.MessageTypeFilter
import org.jivesoftware.smack.packet.Stanza
import org.jxmpp.jid.DomainBareJid
import org.jxmpp.jid.impl.JidCreate

class RoomMetadataHandler(
private val xmppProvider: XmppProvider,
private val conferenceStore: ConferenceStore
) : XmppProvider.Listener, StanzaListener {
private var componentAddress: DomainBareJid? = null
private val logger = createLogger()

/** Queue to process requests in order in the IO pool */
private val queue = PacketQueue<JsonMessageExtension>(
Integer.MAX_VALUE,
false,
"room_metadata queue",
{
doProcess(it)
return@PacketQueue true
},
TaskPools.ioPool
)

init {
xmppProvider.xmppConnection.addSyncStanzaListener(this, MessageTypeFilter.NORMAL)
xmppProvider.addListener(this)
registrationChanged(xmppProvider.registered)
componentsChanged(xmppProvider.components)
}

val debugState: OrderedJsonObject
get() = OrderedJsonObject().apply {
this["address"] = componentAddress.toString()
}

private fun doProcess(jsonMessage: JsonMessageExtension) {
try {
val conferenceJid = JidCreate.entityBareFrom(jsonMessage.getAttribute("room")?.toString())
val roomMetadata = RoomMetadata.parse(jsonMessage.json)

val conference = conferenceStore.getConference(conferenceJid)
?: throw IllegalStateException("Conference $conferenceJid does not exist.")
val chatRoom = conference.chatRoom
?: throw IllegalStateException("Conference has no associated chatRoom.")

chatRoom.setRoomMetadata(roomMetadata)
} catch (e: Exception) {
logger.warn("Failed to process room_metadata request: $jsonMessage", e)
}
}

override fun processStanza(stanza: Stanza) {
if (stanza.from != componentAddress) {
return
}

val jsonMessage = stanza.getExtension(JsonMessageExtension::class.java) ?: return Unit.also {
logger.warn("Skip processing stanza without JsonMessageExtension.")
}

queue.add(jsonMessage)
}

override fun componentsChanged(components: Set<XmppProvider.Component>) {
val address = components.find { it.type == "room_metadata" }?.address

componentAddress = if (address == null) {
logger.info("No room_metadata component discovered.")
null
} else {
logger.info("Using room_metadata component at $address.")
JidCreate.domainBareFrom(address)
}
}

fun shutdown() {
xmppProvider.xmppConnection.removeSyncStanzaListener(this)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,7 @@ class XmppServices(

val avModerationHandler = AvModerationHandler(clientConnection, conferenceStore)
val configurationChangeHandler = ConfigurationChangeHandler(clientConnection, conferenceStore)
val roomMetadataHandler = RoomMetadataHandler(clientConnection, conferenceStore)
private val audioMuteHandler = AudioMuteIqHandler(setOf(clientConnection.xmppConnection), conferenceStore)
private val videoMuteHandler = VideoMuteIqHandler(setOf(clientConnection.xmppConnection), conferenceStore)
val jingleHandler = JingleIqRequestHandler(
Expand Down
Loading