Skip to content

Commit

Permalink
feat: Handle room metadata updates from the room_metadata component (#…
Browse files Browse the repository at this point in the history
…1139)

* ref: Remove unused class.

* feat: Handle updates from the room_metadata component.

As opposed to reloading the MUC config form.
  • Loading branch information
bgrozev authored Feb 28, 2024
1 parent 49cc46e commit 877b475
Show file tree
Hide file tree
Showing 5 changed files with 123 additions and 66 deletions.
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

0 comments on commit 877b475

Please sign in to comment.