Merge pull request #19927 from antobinary/merge-dev-alpha5

chore: Merge 3.0.0-alpha.5 into develop
This commit is contained in:
Ramón Souza 2024-03-28 13:01:05 -03:00 committed by GitHub
commit 99dc23a6f8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
919 changed files with 21893 additions and 12950 deletions

2
.gitignore vendored
View File

@ -23,3 +23,5 @@ cache/*
artifacts/* artifacts/*
bbb-presentation-video.zip bbb-presentation-video.zip
bbb-presentation-video bbb-presentation-video
bbb-graphql-actions-adapter-server/
bigbluebutton-html5/public/locales/index.json

View File

@ -75,5 +75,6 @@ daemonUser in Linux := user
daemonGroup in Linux := group daemonGroup in Linux := group
javaOptions in Universal ++= Seq("-J-Xms130m", "-J-Xmx256m", "-Dconfig.file=/etc/bigbluebutton/bbb-apps-akka.conf", "-Dlogback.configurationFile=conf/logback.xml") javaOptions in Universal ++= Seq("-J-Xms130m", "-J-Xmx256m", "-Dconfig.file=/etc/bigbluebutton/bbb-apps-akka.conf", "-Dlogback.configurationFile=conf/logback.xml")
javaOptions in reStart ++= Seq("-Dconfig.file=/etc/bigbluebutton/bbb-apps-akka.conf", "-Dlogback.configurationFile=conf/logback.xml")
debianPackageDependencies in Debian ++= Seq("java17-runtime-headless", "bash") debianPackageDependencies in Debian ++= Seq("java17-runtime-headless", "bash")

View File

@ -16,7 +16,7 @@ object Dependencies {
val pekkoHttpVersion = "1.0.0" val pekkoHttpVersion = "1.0.0"
val gson = "2.8.9" val gson = "2.8.9"
val jackson = "2.13.5" val jackson = "2.13.5"
val logback = "1.2.11" val logback = "1.2.13"
val quicklens = "1.7.5" val quicklens = "1.7.5"
val spray = "1.3.6" val spray = "1.3.6"

View File

@ -10,7 +10,7 @@ import org.bigbluebutton.core.bus._
import org.bigbluebutton.core.pubsub.senders.ReceivedJsonMsgHandlerActor import org.bigbluebutton.core.pubsub.senders.ReceivedJsonMsgHandlerActor
import org.bigbluebutton.core2.AnalyticsActor import org.bigbluebutton.core2.AnalyticsActor
import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor
import org.bigbluebutton.endpoint.redis.{AppsRedisSubscriberActor, ExportAnnotationsActor, GraphqlActionsActor, LearningDashboardActor, RedisRecorderActor} import org.bigbluebutton.endpoint.redis.{AppsRedisSubscriberActor, ExportAnnotationsActor, GraphqlConnectionsActor, LearningDashboardActor, RedisRecorderActor}
import org.bigbluebutton.common2.bus.IncomingJsonMessageBus import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
import org.bigbluebutton.service.{HealthzService, MeetingInfoActor, MeetingInfoService} import org.bigbluebutton.service.{HealthzService, MeetingInfoActor, MeetingInfoService}
@ -67,9 +67,9 @@ object Boot extends App with SystemConfiguration {
"LearningDashboardActor" "LearningDashboardActor"
) )
val graphqlActionsActor = system.actorOf( val graphqlConnectionsActor = system.actorOf(
GraphqlActionsActor.props(system), GraphqlConnectionsActor.props(system, eventBus, outGW),
"GraphqlActionsActor" "GraphqlConnectionsActor"
) )
ClientSettings.loadClientSettingsFromFile() ClientSettings.loadClientSettingsFromFile()
@ -89,8 +89,8 @@ object Boot extends App with SystemConfiguration {
outBus2.subscribe(learningDashboardActor, outBbbMsgMsgChannel) outBus2.subscribe(learningDashboardActor, outBbbMsgMsgChannel)
bbbMsgBus.subscribe(learningDashboardActor, analyticsChannel) bbbMsgBus.subscribe(learningDashboardActor, analyticsChannel)
eventBus.subscribe(graphqlActionsActor, meetingManagerChannel) eventBus.subscribe(graphqlConnectionsActor, meetingManagerChannel)
bbbMsgBus.subscribe(graphqlActionsActor, analyticsChannel) bbbMsgBus.subscribe(graphqlConnectionsActor, analyticsChannel)
val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW, healthzService), "bigbluebutton-actor") val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW, healthzService), "bigbluebutton-actor")
eventBus.subscribe(bbbActor, meetingManagerChannel) eventBus.subscribe(bbbActor, meetingManagerChannel)

View File

@ -38,6 +38,9 @@ object ClientSettings extends SystemConfiguration {
Map[String, Object]() Map[String, Object]()
} }
) )
//Remove `:private` once it's used only by Meteor internal configs
clientSettingsFromFile -= "private"
} }
def getClientSettingsWithOverride(clientSettingsOverrideJson: String): Map[String, Object] = { def getClientSettingsWithOverride(clientSettingsOverrideJson: String): Map[String, Object] = {
@ -52,6 +55,33 @@ object ClientSettings extends SystemConfiguration {
} else clientSettingsFromFile } else clientSettingsFromFile
} }
def getConfigPropertyValueByPathAsIntOrElse(map: Map[String, Any], path: String, alternativeValue: Int): Int = {
getConfigPropertyValueByPath(map, path) match {
case Some(configValue: Int) => configValue
case _ =>
logger.debug(s"Config `$path` with type Integer not found in clientSettings.")
alternativeValue
}
}
def getConfigPropertyValueByPathAsStringOrElse(map: Map[String, Any], path: String, alternativeValue: String): String = {
getConfigPropertyValueByPath(map, path) match {
case Some(configValue: String) => configValue
case _ =>
logger.debug(s"Config `$path` with type String not found in clientSettings.")
alternativeValue
}
}
def getConfigPropertyValueByPathAsBooleanOrElse(map: Map[String, Any], path: String, alternativeValue: Boolean): Boolean = {
getConfigPropertyValueByPath(map, path) match {
case Some(configValue: Boolean) => configValue
case _ =>
logger.debug(s"Config `$path` with type Boolean found in clientSettings.")
alternativeValue
}
}
def getConfigPropertyValueByPath(map: Map[String, Any], path: String): Option[Any] = { def getConfigPropertyValueByPath(map: Map[String, Any], path: String): Option[Any] = {
val keys = path.split("\\.") val keys = path.split("\\.")
@ -90,14 +120,38 @@ object ClientSettings extends SystemConfiguration {
for { for {
dataChannel <- dataChannels dataChannel <- dataChannels
} yield { } yield {
if (dataChannel.contains("name") && dataChannel.contains("writePermission")) { if (dataChannel.contains("name")) {
val channelName = dataChannel("name").toString val channelName = dataChannel("name").toString
val writePermission = dataChannel("writePermission") val writePermission = {
writePermission match { if (dataChannel.contains("writePermission")) {
case wPerm: List[String] => pluginDataChannels += (channelName -> DataChannel(channelName, wPerm)) dataChannel("writePermission") match {
case _ => logger.warn(s"Invalid writePermission for channel $channelName in plugin $pluginName") case wPerm: List[String] => wPerm
case _ => {
logger.warn(s"Invalid writePermission for channel $channelName in plugin $pluginName")
List()
} }
} }
} else {
logger.warn(s"Missing config writePermission for channel $channelName in plugin $pluginName")
List()
}
}
val deletePermission = {
if (dataChannel.contains("deletePermission")) {
dataChannel("deletePermission") match {
case dPerm: List[String] => dPerm
case _ => {
logger.warn(s"Invalid deletePermission for channel $channelName in plugin $pluginName")
List()
}
}
} else {
List()
}
}
pluginDataChannels += (channelName -> DataChannel(channelName, writePermission, deletePermission))
}
} }
case _ => logger.warn(s"Plugin $pluginName has an invalid dataChannels format") case _ => logger.warn(s"Plugin $pluginName has an invalid dataChannels format")
} }
@ -112,7 +166,7 @@ object ClientSettings extends SystemConfiguration {
pluginsFromConfig pluginsFromConfig
} }
case class DataChannel(name: String, writePermission: List[String]) case class DataChannel(name: String, writePermission: List[String], deletePermission: List[String])
case class Plugin(name: String, url: String, dataChannels: Map[String, DataChannel]) case class Plugin(name: String, url: String, dataChannels: Map[String, DataChannel])
} }

View File

@ -13,6 +13,7 @@ trait SystemConfiguration {
lazy val bbbWebPort = Try(config.getInt("services.bbbWebPort")).getOrElse(8888) lazy val bbbWebPort = Try(config.getInt("services.bbbWebPort")).getOrElse(8888)
lazy val bbbWebAPI = Try(config.getString("services.bbbWebAPI")).getOrElse("localhost") lazy val bbbWebAPI = Try(config.getString("services.bbbWebAPI")).getOrElse("localhost")
lazy val bbbWebSharedSecret = Try(config.getString("services.sharedSecret")).getOrElse("changeme") lazy val bbbWebSharedSecret = Try(config.getString("services.sharedSecret")).getOrElse("changeme")
lazy val checkSumAlgorithmForBreakouts = Try(config.getString("services.checkSumAlgorithmForBreakouts")).getOrElse("sha256")
lazy val bbbWebModeratorPassword = Try(config.getString("services.moderatorPassword")).getOrElse("changeme") lazy val bbbWebModeratorPassword = Try(config.getString("services.moderatorPassword")).getOrElse("changeme")
lazy val bbbWebViewerPassword = Try(config.getString("services.viewerPassword")).getOrElse("changeme") lazy val bbbWebViewerPassword = Try(config.getString("services.viewerPassword")).getOrElse("changeme")
lazy val keysExpiresInSec = Try(config.getInt("redis.keyExpiry")).getOrElse(14 * 86400) // 14 days lazy val keysExpiresInSec = Try(config.getInt("redis.keyExpiry")).getOrElse(14 * 86400) // 14 days

View File

@ -14,6 +14,7 @@ import org.bigbluebutton.SystemConfiguration
import java.util.concurrent.TimeUnit import java.util.concurrent.TimeUnit
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.db.{ DatabaseConnection, MeetingDAO } import org.bigbluebutton.core.db.{ DatabaseConnection, MeetingDAO }
import org.bigbluebutton.core.domain.MeetingEndReason
import org.bigbluebutton.core.running.RunningMeeting import org.bigbluebutton.core.running.RunningMeeting
import org.bigbluebutton.core.util.ColorPicker import org.bigbluebutton.core.util.ColorPicker
import org.bigbluebutton.core2.RunningMeetings import org.bigbluebutton.core2.RunningMeetings
@ -57,6 +58,9 @@ class BigBlueButtonActor(
override def preStart() { override def preStart() {
bbbMsgBus.subscribe(self, meetingManagerChannel) bbbMsgBus.subscribe(self, meetingManagerChannel)
DatabaseConnection.initialize() DatabaseConnection.initialize()
//Terminate all previous meetings, as they will not function following the akka-apps restart
MeetingDAO.setAllMeetingsEnded(MeetingEndReason.ENDED_DUE_TO_SERVICE_INTERRUPTION, "system")
} }
override def postStop() { override def postStop() {
@ -83,6 +87,7 @@ class BigBlueButtonActor(
case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m) case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m)
case _: UserGraphqlConnectionEstablishedSysMsg => //Ignore case _: UserGraphqlConnectionEstablishedSysMsg => //Ignore
case _: UserGraphqlConnectionClosedSysMsg => //Ignore case _: UserGraphqlConnectionClosedSysMsg => //Ignore
case _: CheckGraphqlMiddlewareAlivePongSysMsg => //Ignore
case _ => log.warning("Cannot handle " + msg.envelope.name) case _ => log.warning("Cannot handle " + msg.envelope.name)
} }
} }
@ -189,9 +194,10 @@ class BigBlueButtonActor(
context.stop(m.actorRef) context.stop(m.actorRef)
} }
MeetingDAO.delete(msg.meetingId) // MeetingDAO.delete(msg.meetingId)
// MeetingDAO.setMeetingEnded(msg.meetingId)
// Removing the meeting is enough, all other tables has "ON DELETE CASCADE" // Removing the meeting is enough, all other tables has "ON DELETE CASCADE"
// UserDAO.deleteAllFromMeeting(msg.meetingId) // UserDAO.softDeleteAllFromMeeting(msg.meetingId)
// MeetingRecordingDAO.updateStopped(msg.meetingId, "") // MeetingRecordingDAO.updateStopped(msg.meetingId, "")
//Remove ColorPicker idx of the meeting //Remove ColorPicker idx of the meeting

View File

@ -1,5 +1,6 @@
package org.bigbluebutton.core.api package org.bigbluebutton.core.api
import org.bigbluebutton.core.apps.users.UserEstablishedGraphqlConnectionInternalMsgHdlr
import org.bigbluebutton.core.domain.{ BreakoutUser, BreakoutVoiceUser } import org.bigbluebutton.core.domain.{ BreakoutUser, BreakoutVoiceUser }
import spray.json.JsObject import spray.json.JsObject
case class InMessageHeader(name: String) case class InMessageHeader(name: String)
@ -126,6 +127,18 @@ case class SetPresenterInDefaultPodInternalMsg(presenterId: String) extends InMe
*/ */
case class CaptureSharedNotesReqInternalMsg(breakoutId: String, filename: String) extends InMessage case class CaptureSharedNotesReqInternalMsg(breakoutId: String, filename: String) extends InMessage
/**
* Sent by GraphqlActionsActor to inform MeetingActor that user disconnected
* @param userId
*/
case class UserClosedAllGraphqlConnectionsInternalMsg(userId: String) extends InMessage
/**
* Sent by GraphqlActionsActor to inform MeetingActor that user came back from disconnection
* @param userId
*/
case class UserEstablishedGraphqlConnectionInternalMsg(userId: String) extends InMessage
// DeskShare // DeskShare
case class DeskShareStartedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage case class DeskShareStartedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage
case class DeskShareStoppedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage case class DeskShareStoppedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage

View File

@ -45,6 +45,14 @@ object TimerModel {
} }
def setRunning(model: TimerModel, running: Boolean): Unit = { def setRunning(model: TimerModel, running: Boolean): Unit = {
//If it is running and will stop, calculate new Accumulated
if(getRunning(model) && !running) {
val now = System.currentTimeMillis()
val accumulated = getAccumulated(model) + Math.abs(now - getStartedAt(model)).toInt
this.setAccumulated(model, accumulated)
}
model.running = running model.running = running
} }

View File

@ -47,18 +47,30 @@ class WhiteboardModel extends SystemConfiguration {
}).toMap }).toMap
def addAnnotations(wbId: String, userId: String, annotations: Array[AnnotationVO], isPresenter: Boolean, isModerator: Boolean): Array[AnnotationVO] = { def addAnnotations(wbId: String, userId: String, annotations: Array[AnnotationVO], isPresenter: Boolean, isModerator: Boolean): Array[AnnotationVO] = {
var annotationsAdded = Array[AnnotationVO]()
val wb = getWhiteboard(wbId) val wb = getWhiteboard(wbId)
var annotationsAdded = Array[AnnotationVO]()
var newAnnotationsMap = wb.annotationsMap var newAnnotationsMap = wb.annotationsMap
for (annotation <- annotations) { for (annotation <- annotations) {
val oldAnnotation = wb.annotationsMap.get(annotation.id) val oldAnnotation = wb.annotationsMap.get(annotation.id)
if (!oldAnnotation.isEmpty) { if (!oldAnnotation.isEmpty) {
val hasPermission = isPresenter || isModerator || oldAnnotation.get.userId == userId val hasPermission = isPresenter || isModerator || oldAnnotation.get.userId == userId
if (hasPermission) { if (hasPermission) {
val newAnnotation = oldAnnotation.get.copy(annotationInfo = deepMerge(oldAnnotation.get.annotationInfo, annotation.annotationInfo)) // Merge old and new annotation properties
val mergedAnnotationInfo = deepMerge(oldAnnotation.get.annotationInfo, annotation.annotationInfo)
// Apply cleaning if it's an arrow annotation
val finalAnnotationInfo = if (annotation.annotationInfo.get("type").contains("arrow")) {
cleanArrowAnnotationProps(mergedAnnotationInfo)
} else {
mergedAnnotationInfo
}
val newAnnotation = oldAnnotation.get.copy(annotationInfo = finalAnnotationInfo)
newAnnotationsMap += (annotation.id -> newAnnotation) newAnnotationsMap += (annotation.id -> newAnnotation)
annotationsAdded :+= annotation annotationsAdded :+= newAnnotation
PresAnnotationDAO.insertOrUpdate(newAnnotation, annotation) PresAnnotationDAO.insertOrUpdate(newAnnotation, newAnnotation)
println(s"Updated annotation on page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].") println(s"Updated annotation on page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
} else { } else {
println(s"User $userId doesn't have permission to edit annotation ${annotation.id}, ignoring...") println(s"User $userId doesn't have permission to edit annotation ${annotation.id}, ignoring...")
@ -69,40 +81,67 @@ class WhiteboardModel extends SystemConfiguration {
PresAnnotationDAO.insertOrUpdate(annotation, annotation) PresAnnotationDAO.insertOrUpdate(annotation, annotation)
println(s"Adding annotation to page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].") println(s"Adding annotation to page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
} else { } else {
println(s"New annotation [${annotation.id}] with no type, ignoring (probably received a remove message before and now the shape is incomplete, ignoring...") println(s"New annotation [${annotation.id}] with no type, ignoring...")
} }
} }
val newWb = wb.copy(annotationsMap = newAnnotationsMap) val newWb = wb.copy(annotationsMap = newAnnotationsMap)
saveWhiteboard(newWb) saveWhiteboard(newWb)
annotationsAdded annotationsAdded
} }
private def cleanArrowAnnotationProps(annotationInfo: Map[String, _]): Map[String, _] = {
annotationInfo.get("props") match {
case Some(props: Map[String, _]) =>
val cleanedProps = props.map {
case ("end", endProps: Map[String, _]) => "end" -> cleanEndOrStartProps(endProps)
case ("start", startProps: Map[String, _]) => "start" -> cleanEndOrStartProps(startProps)
case other => other
}
annotationInfo + ("props" -> cleanedProps)
case _ => annotationInfo
}
}
private def cleanEndOrStartProps(props: Map[String, _]): Map[String, _] = {
props.get("type") match {
case Some("binding") => props - ("x", "y") // Remove 'x' and 'y' for 'binding' type
case Some("point") => props - ("boundShapeId", "normalizedAnchor", "isExact") // Remove unwanted properties for 'point' type
case _ => props
}
}
def getHistory(wbId: String): Array[AnnotationVO] = { def getHistory(wbId: String): Array[AnnotationVO] = {
val wb = getWhiteboard(wbId) val wb = getWhiteboard(wbId)
wb.annotationsMap.values.toArray wb.annotationsMap.values.toArray
} }
def deleteAnnotations(wbId: String, userId: String, annotationsIds: Array[String], isPresenter: Boolean, isModerator: Boolean): Array[String] = { def deleteAnnotations(wbId: String, userId: String, annotationsIds: Array[String], isPresenter: Boolean, isModerator: Boolean): Array[String] = {
var annotationsIdsRemoved = Array[String]()
val wb = getWhiteboard(wbId) val wb = getWhiteboard(wbId)
var annotationsIdsRemoved = Array[String]()
var newAnnotationsMap = wb.annotationsMap var newAnnotationsMap = wb.annotationsMap
for (annotationId <- annotationsIds) { for (annotationId <- annotationsIds) {
val annotation = wb.annotationsMap.get(annotationId) val annotation = wb.annotationsMap.get(annotationId)
if (!annotation.isEmpty) { if (annotation.isDefined) {
val hasPermission = isPresenter || isModerator || annotation.get.userId == userId val hasPermission = isPresenter || isModerator || annotation.get.userId == userId
if (hasPermission) { if (hasPermission) {
newAnnotationsMap -= annotationId newAnnotationsMap -= annotationId
println("Removing annotation on page [" + wb.id + "]. After numAnnotations=[" + newAnnotationsMap.size + "].") println(s"Removed annotation $annotationId on page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
annotationsIdsRemoved :+= annotationId annotationsIdsRemoved :+= annotationId
} else { } else {
println("User doesn't have permission to remove this annotation, ignoring...") println(s"User $userId doesn't have permission to remove annotation $annotationId, ignoring...")
}
} else {
println(s"Annotation $annotationId not found while trying to delete it.")
} }
} }
}
val newWb = wb.copy(annotationsMap = newAnnotationsMap) // Update whiteboard and save
saveWhiteboard(newWb) val updatedWb = wb.copy(annotationsMap = newAnnotationsMap)
saveWhiteboard(updatedWb)
annotationsIdsRemoved.map(PresAnnotationDAO.delete(wbId, userId, _)) annotationsIdsRemoved.map(PresAnnotationDAO.delete(wbId, userId, _))

View File

@ -4,6 +4,7 @@ import org.apache.pekko.actor.ActorContext
class AudioCaptionsApp2x(implicit val context: ActorContext) class AudioCaptionsApp2x(implicit val context: ActorContext)
extends UpdateTranscriptPubMsgHdlr extends UpdateTranscriptPubMsgHdlr
with TranscriptionProviderErrorMsgHdlr
with AudioFloorChangedVoiceConfEvtMsgHdlr { with AudioFloorChangedVoiceConfEvtMsgHdlr {
} }

View File

@ -0,0 +1,31 @@
package org.bigbluebutton.core.apps.audiocaptions
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.UserTranscriptionErrorDAO
import org.bigbluebutton.core.models.AudioCaptions
import org.bigbluebutton.core.running.LiveMeeting
trait TranscriptionProviderErrorMsgHdlr {
this: AudioCaptionsApp2x =>
def handleTranscriptionProviderErrorMsg(msg: TranscriptionProviderErrorMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val meetingId = liveMeeting.props.meetingProp.intId
def broadcastEvent(userId: String, errorCode: String, errorMessage: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, "nodeJSapp")
val envelope = BbbCoreEnvelope(TranscriptionProviderErrorEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(TranscriptionProviderErrorEvtMsg.NAME, meetingId, userId)
val body = TranscriptionProviderErrorEvtMsgBody(errorCode, errorMessage)
val event = TranscriptionProviderErrorEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
broadcastEvent(msg.header.userId, msg.body.errorCode, msg.body.errorMessage)
UserTranscriptionErrorDAO.insert(msg.header.userId, msg.header.meetingId, msg.body.errorCode, msg.body.errorMessage)
}
}

View File

@ -1,12 +1,13 @@
package org.bigbluebutton.core.apps.audiocaptions package org.bigbluebutton.core.apps.audiocaptions
import org.bigbluebutton.ClientSettings.getConfigPropertyValueByPathAsStringOrElse
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.CaptionDAO import org.bigbluebutton.core.db.CaptionDAO
import org.bigbluebutton.core.models.{AudioCaptions, Users2x} import org.bigbluebutton.core.models.{AudioCaptions, UserState, Users2x}
import org.bigbluebutton.core.running.LiveMeeting import org.bigbluebutton.core.running.LiveMeeting
import java.time.LocalDateTime
import java.sql.Timestamp import java.time.format.DateTimeFormatter
trait UpdateTranscriptPubMsgHdlr { trait UpdateTranscriptPubMsgHdlr {
this: AudioCaptionsApp2x => this: AudioCaptionsApp2x =>
@ -25,6 +26,17 @@ trait UpdateTranscriptPubMsgHdlr {
bus.outGW.send(msgEvent) bus.outGW.send(msgEvent)
} }
def sendPadUpdatePubMsg(userId: String, defaultPad: String, text: String, transcript: Boolean): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, "nodeJSapp")
val envelope = BbbCoreEnvelope(PadUpdatePubMsg.NAME, routing)
val header = BbbClientMsgHeader(PadUpdatePubMsg.NAME, meetingId, userId)
val body = PadUpdatePubMsgBody(defaultPad, text, transcript)
val event = PadUpdatePubMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
// Adapt to the current captions' recording process // Adapt to the current captions' recording process
def editTranscript( def editTranscript(
userId: String, userId: String,
@ -80,6 +92,28 @@ trait UpdateTranscriptPubMsgHdlr {
msg.body.locale, msg.body.locale,
msg.body.result, msg.body.result,
) )
if(msg.body.result) {
val userName = Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId).get match {
case u: UserState => u.name
case _ => "???"
}
val now = LocalDateTime.now()
val formatter = DateTimeFormatter.ofPattern("HH:mm:ss")
val formattedTime = now.format(formatter)
val userSpoke = s"\n $userName ($formattedTime): $transcript"
val defaultPad = getConfigPropertyValueByPathAsStringOrElse(
liveMeeting.clientSettings,
"public.captions.defaultPad",
alternativeValue = ""
)
sendPadUpdatePubMsg(msg.header.userId, defaultPad, userSpoke, transcript = true)
}
} }
} }
} }

View File

@ -4,6 +4,7 @@ import org.bigbluebutton.core.running.MeetingActor
import java.net.URLEncoder import java.net.URLEncoder
import scala.collection.SortedSet import scala.collection.SortedSet
import org.apache.commons.codec.digest.DigestUtils import org.apache.commons.codec.digest.DigestUtils
import org.bigbluebutton.SystemConfiguration
trait BreakoutApp2x extends BreakoutRoomCreatedMsgHdlr trait BreakoutApp2x extends BreakoutRoomCreatedMsgHdlr
with BreakoutRoomsListMsgHdlr with BreakoutRoomsListMsgHdlr
@ -26,7 +27,7 @@ trait BreakoutApp2x extends BreakoutRoomCreatedMsgHdlr
} }
object BreakoutRoomsUtil { object BreakoutRoomsUtil extends SystemConfiguration {
def createMeetingIds(id: String, index: Int): (String, String) = { def createMeetingIds(id: String, index: Int): (String, String) = {
val timeStamp = System.currentTimeMillis() val timeStamp = System.currentTimeMillis()
val externalHash = DigestUtils.sha1Hex(id.concat("-").concat(timeStamp.toString()).concat("-").concat(index.toString())) val externalHash = DigestUtils.sha1Hex(id.concat("-").concat(timeStamp.toString()).concat("-").concat(index.toString()))
@ -48,7 +49,13 @@ object BreakoutRoomsUtil {
//checksum() -- Return a checksum based on SHA-1 digest //checksum() -- Return a checksum based on SHA-1 digest
// //
def checksum(s: String): String = { def checksum(s: String): String = {
DigestUtils.sha256Hex(s); checkSumAlgorithmForBreakouts match {
case "sha1" => DigestUtils.sha1Hex(s);
case "sha256" => DigestUtils.sha256Hex(s);
case "sha384" => DigestUtils.sha384Hex(s);
case "sha512" => DigestUtils.sha512Hex(s);
case _ => DigestUtils.sha256Hex(s); // default
}
} }
def calculateChecksum(apiCall: String, baseString: String, sharedSecret: String): String = { def calculateChecksum(apiCall: String, baseString: String, sharedSecret: String): String = {

View File

@ -1,5 +1,6 @@
package org.bigbluebutton.core.apps.breakout package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.ClientSettings.{getConfigPropertyValueByPath, getConfigPropertyValueByPathAsIntOrElse}
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{BreakoutModel, PermissionCheck, RightsManagementTrait} import org.bigbluebutton.core.apps.{BreakoutModel, PermissionCheck, RightsManagementTrait}
import org.bigbluebutton.core.db.BreakoutRoomDAO import org.bigbluebutton.core.db.BreakoutRoomDAO
@ -16,6 +17,10 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
def handleCreateBreakoutRoomsCmdMsg(msg: CreateBreakoutRoomsCmdMsg, state: MeetingState2x): MeetingState2x = { def handleCreateBreakoutRoomsCmdMsg(msg: CreateBreakoutRoomsCmdMsg, state: MeetingState2x): MeetingState2x = {
val minOfRooms = 2
val maxOfRooms = getConfigPropertyValueByPathAsIntOrElse(liveMeeting.clientSettings, "public.app.breakouts.breakoutRoomLimit", 16)
if (liveMeeting.props.meetingProp.disabledFeatures.contains("breakoutRooms")) { if (liveMeeting.props.meetingProp.disabledFeatures.contains("breakoutRooms")) {
val meetingId = liveMeeting.props.meetingProp.intId val meetingId = liveMeeting.props.meetingProp.intId
val reason = "Breakout rooms is disabled for this meeting." val reason = "Breakout rooms is disabled for this meeting."
@ -27,6 +32,15 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId,
reason, outGW, liveMeeting) reason, outGW, liveMeeting)
state state
} else if(msg.body.rooms.length > maxOfRooms || msg.body.rooms.length < minOfRooms) {
log.warning(
"Attempt to create breakout rooms with invalid number of rooms (rooms: {}, max: {}, min: {}) in meeting {}",
msg.body.rooms.size,
maxOfRooms,
minOfRooms,
liveMeeting.props.meetingProp.intId
)
state
} else { } else {
state.breakout match { state.breakout match {
case Some(breakout) => case Some(breakout) =>

View File

@ -30,7 +30,7 @@ trait EjectUserFromBreakoutInternalMsgHdlr {
) )
//TODO inform reason //TODO inform reason
UserDAO.delete(registeredUser.id) UserDAO.softDelete(registeredUser.id)
// send a system message to force disconnection // send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(msg.breakoutId, registeredUser.id, msg.ejectedBy, msg.reasonCode, outGW) Sender.sendDisconnectClientSysMsg(msg.breakoutId, registeredUser.id, msg.ejectedBy, msg.reasonCode, outGW)

View File

@ -18,8 +18,8 @@ trait SendMessageToBreakoutRoomInternalMsgHdlr {
sender <- GroupChatApp.findGroupChatUser(SystemUser.ID, liveMeeting.users2x) sender <- GroupChatApp.findGroupChatUser(SystemUser.ID, liveMeeting.users2x)
chat <- state.groupChats.find(GroupChatApp.MAIN_PUBLIC_CHAT) chat <- state.groupChats.find(GroupChatApp.MAIN_PUBLIC_CHAT)
} yield { } yield {
val groupChatMsgFromUser = GroupChatMsgFromUser(sender.id, sender.copy(name = msg.senderName), true, msg.msg) val groupChatMsgFromUser = GroupChatMsgFromUser(sender.id, sender.copy(name = msg.senderName), msg.msg)
val gcm = GroupChatApp.toGroupChatMessage(sender.copy(name = msg.senderName), groupChatMsgFromUser) val gcm = GroupChatApp.toGroupChatMessage(sender.copy(name = msg.senderName), groupChatMsgFromUser, emphasizedText = true)
val gcs = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm, GroupChatMessageType.BREAKOUTROOM_MOD_MSG) val gcs = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm, GroupChatMessageType.BREAKOUTROOM_MOD_MSG)
val event = buildGroupChatMessageBroadcastEvtMsg( val event = buildGroupChatMessageBroadcastEvtMsg(

View File

@ -59,7 +59,7 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
val newState = for { val newState = for {
createdBy <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x) createdBy <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x)
} yield { } yield {
val msgs = msg.body.msg.map(m => GroupChatApp.toGroupChatMessage(createdBy, m)) val msgs = msg.body.msg.map(m => GroupChatApp.toGroupChatMessage(createdBy, m, emphasizedText = false))
val users = { val users = {
if (msg.body.access == GroupChatAccess.PRIVATE) { if (msg.body.access == GroupChatAccess.PRIVATE) {
val cu = msg.body.users.toSet + msg.header.userId val cu = msg.body.users.toSet + msg.header.userId

View File

@ -20,10 +20,10 @@ object GroupChatApp {
GroupChatFactory.create(gcId, access, createBy, users, msgs) GroupChatFactory.create(gcId, access, createBy, users, msgs)
} }
def toGroupChatMessage(sender: GroupChatUser, msg: GroupChatMsgFromUser): GroupChatMessage = { def toGroupChatMessage(sender: GroupChatUser, msg: GroupChatMsgFromUser, emphasizedText: Boolean): GroupChatMessage = {
val now = System.currentTimeMillis() val now = System.currentTimeMillis()
val id = GroupChatFactory.genId() val id = GroupChatFactory.genId()
GroupChatMessage(id, now, msg.correlationId, now, now, sender, msg.chatEmphasizedText, msg.message) GroupChatMessage(id, now, msg.correlationId, now, now, sender, emphasizedText, msg.message)
} }
def toMessageToUser(msg: GroupChatMessage): GroupChatMsgToUser = { def toMessageToUser(msg: GroupChatMessage): GroupChatMsgToUser = {
@ -80,8 +80,8 @@ object GroupChatApp {
sender <- GroupChatApp.findGroupChatUser(userId, liveMeeting.users2x) sender <- GroupChatApp.findGroupChatUser(userId, liveMeeting.users2x)
chat <- state.groupChats.find(chatId) chat <- state.groupChats.find(chatId)
} yield { } yield {
val emphasizedText = sender.role == Roles.MODERATOR_ROLE
val gcm1 = GroupChatApp.toGroupChatMessage(sender, msg) val gcm1 = GroupChatApp.toGroupChatMessage(sender, msg, emphasizedText)
val gcs1 = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm1) val gcs1 = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm1)
state.update(gcs1) state.update(gcs1)
} }

View File

@ -1,5 +1,6 @@
package org.bigbluebutton.core.apps.groupchats package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.ClientSettings.{ getConfigPropertyValueByPath, getConfigPropertyValueByPathAsBooleanOrElse }
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.PermissionCheck import org.bigbluebutton.core.apps.PermissionCheck
import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.bus.MessageBus
@ -48,7 +49,17 @@ trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
val userIsAParticipant = chat.users.filter(u => u.id == sender.id).length > 0; val userIsAParticipant = chat.users.filter(u => u.id == sender.id).length > 0;
if ((chatIsPrivate && userIsAParticipant) || !chatIsPrivate) { if ((chatIsPrivate && userIsAParticipant) || !chatIsPrivate) {
val gcm = GroupChatApp.toGroupChatMessage(sender, msg.body.msg) val moderatorChatEmphasizedEnabled = getConfigPropertyValueByPathAsBooleanOrElse(
liveMeeting.clientSettings,
"public.chat.moderatorChatEmphasized",
alternativeValue = true
)
val emphasizedText = moderatorChatEmphasizedEnabled &&
!chatIsPrivate &&
sender.role == Roles.MODERATOR_ROLE
val gcm = GroupChatApp.toGroupChatMessage(sender, msg.body.msg, emphasizedText)
val gcs = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm) val gcs = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm)
val event = buildGroupChatMessageBroadcastEvtMsg( val event = buildGroupChatMessageBroadcastEvtMsg(

View File

@ -6,6 +6,7 @@ import org.bigbluebutton.core.running.OutMsgRouter
import org.bigbluebutton.core2.MeetingStatus2x import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.LayoutDAO import org.bigbluebutton.core.db.LayoutDAO
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
trait BroadcastLayoutMsgHdlr extends RightsManagementTrait { trait BroadcastLayoutMsgHdlr extends RightsManagementTrait {
this: LayoutApp2x => this: LayoutApp2x =>
@ -60,5 +61,18 @@ trait BroadcastLayoutMsgHdlr extends RightsManagementTrait {
val msgEvent = BbbCommonEnvCoreMsg(envelope, event) val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent) outGW.send(msgEvent)
if (body.pushLayout) {
val notifyEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
fromUserId,
liveMeeting.props.meetingProp.intId,
"info",
"user",
"app.layoutUpdate.label",
"Notification to when the presenter changes size of cams",
Vector()
)
outGW.send(notifyEvent)
}
} }
} }

View File

@ -21,7 +21,7 @@ trait PadUpdatePubMsgHdlr {
bus.outGW.send(msgEvent) bus.outGW.send(msgEvent)
} }
if (Pads.hasAccess(liveMeeting, msg.body.externalId, msg.header.userId)) { if (Pads.hasAccess(liveMeeting, msg.body.externalId, msg.header.userId) || msg.body.transcript == true) {
Pads.getGroup(liveMeeting.pads, msg.body.externalId) match { Pads.getGroup(liveMeeting.pads, msg.body.externalId) match {
case Some(group) => broadcastEvent(group.groupId, msg.body.externalId, msg.body.text) case Some(group) => broadcastEvent(group.groupId, msg.body.externalId, msg.body.text)
case _ => case _ =>

View File

@ -0,0 +1,60 @@
package org.bigbluebutton.core.apps.plugin
import org.bigbluebutton.ClientSettings
import org.bigbluebutton.common2.msgs.PluginDataChannelDeleteMessageMsg
import org.bigbluebutton.core.db.PluginDataChannelMessageDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ Roles, Users2x }
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
trait PluginDataChannelDeleteMessageMsgHdlr extends HandlerHelpers {
def handle(msg: PluginDataChannelDeleteMessageMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
val pluginsDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("plugins")
val meetingId = liveMeeting.props.meetingProp.intId
for {
_ <- if (!pluginsDisabled) Some(()) else None
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
val pluginsConfig = ClientSettings.getPluginsFromConfig(ClientSettings.clientSettingsFromFile)
if (!pluginsConfig.contains(msg.body.pluginName)) {
println(s"Plugin '${msg.body.pluginName}' not found.")
} else if (!pluginsConfig(msg.body.pluginName).dataChannels.contains(msg.body.dataChannel)) {
println(s"Data channel '${msg.body.dataChannel}' not found in plugin '${msg.body.pluginName}'.")
} else {
val hasPermission = for {
deletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.dataChannel).deletePermission
} yield {
deletePermission.toLowerCase match {
case "all" => true
case "moderator" => user.role == Roles.MODERATOR_ROLE
case "presenter" => user.presenter
case "sender" => {
val senderUserId = PluginDataChannelMessageDAO.getMessageSender(
meetingId,
msg.body.pluginName,
msg.body.dataChannel,
msg.body.messageId
)
senderUserId == msg.header.userId
}
case _ => false
}
}
if (!hasPermission.contains(true)) {
println(s"No permission to delete in plugin: '${msg.body.pluginName}', data channel: '${msg.body.dataChannel}'.")
} else {
PluginDataChannelMessageDAO.delete(
meetingId,
msg.body.pluginName,
msg.body.dataChannel,
msg.body.messageId
)
}
}
}
}
}

View File

@ -7,9 +7,9 @@ import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ Roles, Users2x } import org.bigbluebutton.core.models.{ Roles, Users2x }
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting } import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
trait DispatchPluginDataChannelMessageMsgHdlr extends HandlerHelpers { trait PluginDataChannelDispatchMessageMsgHdlr extends HandlerHelpers {
def handle(msg: DispatchPluginDataChannelMessageMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = { def handle(msg: PluginDataChannelDispatchMessageMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
val pluginsDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("plugins") val pluginsDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("plugins")
val meetingId = liveMeeting.props.meetingProp.intId val meetingId = liveMeeting.props.meetingProp.intId

View File

@ -0,0 +1,50 @@
package org.bigbluebutton.core.apps.plugin
import org.bigbluebutton.ClientSettings
import org.bigbluebutton.common2.msgs.PluginDataChannelResetMsg
import org.bigbluebutton.core.db.PluginDataChannelMessageDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ Roles, Users2x }
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
trait PluginDataChannelResetMsgHdlr extends HandlerHelpers {
def handle(msg: PluginDataChannelResetMsg, state: MeetingState2x, liveMeeting: LiveMeeting): Unit = {
val pluginsDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("plugins")
val meetingId = liveMeeting.props.meetingProp.intId
for {
_ <- if (!pluginsDisabled) Some(()) else None
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
val pluginsConfig = ClientSettings.getPluginsFromConfig(ClientSettings.clientSettingsFromFile)
if (!pluginsConfig.contains(msg.body.pluginName)) {
println(s"Plugin '${msg.body.pluginName}' not found.")
} else if (!pluginsConfig(msg.body.pluginName).dataChannels.contains(msg.body.dataChannel)) {
println(s"Data channel '${msg.body.dataChannel}' not found in plugin '${msg.body.pluginName}'.")
} else {
val hasPermission = for {
deletePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.dataChannel).deletePermission
} yield {
deletePermission.toLowerCase match {
case "all" => true
case "moderator" => user.role == Roles.MODERATOR_ROLE
case "presenter" => user.presenter
case _ => false
}
}
if (!hasPermission.contains(true)) {
println(s"No permission to delete (reset) in plugin: '${msg.body.pluginName}', data channel: '${msg.body.dataChannel}'.")
} else {
PluginDataChannelMessageDAO.reset(
meetingId,
msg.body.pluginName,
msg.body.dataChannel
)
}
}
}
}
}

View File

@ -4,7 +4,9 @@ import org.apache.pekko.actor.ActorContext
import org.apache.pekko.event.Logging import org.apache.pekko.event.Logging
class PluginHdlrs(implicit val context: ActorContext) class PluginHdlrs(implicit val context: ActorContext)
extends DispatchPluginDataChannelMessageMsgHdlr { extends PluginDataChannelDispatchMessageMsgHdlr
with PluginDataChannelDeleteMessageMsgHdlr
with PluginDataChannelResetMsgHdlr {
val log = Logging(context.system, getClass) val log = Logging(context.system, getClass)
} }

View File

@ -0,0 +1,56 @@
package org.bigbluebutton.core.apps.polls
import org.bigbluebutton.common2.domain.SimplePollResultOutVO
import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, MessageTypes, PollUpdatedEvtMsg, PollUpdatedEvtMsgBody, Routing, UserRespondedToPollRecordMsg, UserRespondedToPollRecordMsgBody, UserRespondedToPollRespMsg, UserRespondedToPollRespMsgBody, UserRespondedToTypedPollRespMsg, UserRespondedToTypedPollRespMsgBody }
import org.bigbluebutton.core.running.OutMsgRouter
object PollHdlrHelpers {
def broadcastPollUpdatedEvent(outGW: OutMsgRouter, meetingId: String, userId: String, pollId: String, poll: SimplePollResultOutVO): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
val envelope = BbbCoreEnvelope(PollUpdatedEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(PollUpdatedEvtMsg.NAME, meetingId, userId)
val body = PollUpdatedEvtMsgBody(pollId, poll)
val event = PollUpdatedEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
def broadcastUserRespondedToTypedPollRespMsg(outGW: OutMsgRouter, meetingId: String, userId: String,
pollId: String, answer: String, sendToId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, sendToId)
val envelope = BbbCoreEnvelope(UserRespondedToTypedPollRespMsg.NAME, routing)
val header = BbbClientMsgHeader(UserRespondedToTypedPollRespMsg.NAME, meetingId, sendToId)
val body = UserRespondedToTypedPollRespMsgBody(pollId, userId, answer)
val event = UserRespondedToTypedPollRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
def broadcastUserRespondedToPollRecordMsg(outGW: OutMsgRouter, meetingId: String, userId: String,
pollId: String, answerId: Int, answer: String, isSecret: Boolean): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
val envelope = BbbCoreEnvelope(UserRespondedToPollRecordMsg.NAME, routing)
val header = BbbClientMsgHeader(UserRespondedToPollRecordMsg.NAME, meetingId, userId)
val body = UserRespondedToPollRecordMsgBody(pollId, answerId, answer, isSecret)
val event = UserRespondedToPollRecordMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
def broadcastUserRespondedToPollRespMsg(outGW: OutMsgRouter, meetingId: String, userId: String,
pollId: String, answerIds: Seq[Int], sendToId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, sendToId)
val envelope = BbbCoreEnvelope(UserRespondedToPollRespMsg.NAME, routing)
val header = BbbClientMsgHeader(UserRespondedToPollRespMsg.NAME, meetingId, sendToId)
val body = UserRespondedToPollRespMsgBody(pollId, userId, answerIds)
val event = UserRespondedToPollRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
}

View File

@ -4,7 +4,7 @@ import org.bigbluebutton.common2.domain.SimplePollResultOutVO
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.models.Polls import org.bigbluebutton.core.models.Polls
import org.bigbluebutton.core.running.{ LiveMeeting } import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.models.Users2x import org.bigbluebutton.core.models.Users2x
trait RespondToPollReqMsgHdlr { trait RespondToPollReqMsgHdlr {
@ -12,45 +12,12 @@ trait RespondToPollReqMsgHdlr {
def handle(msg: RespondToPollReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = { def handle(msg: RespondToPollReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastPollUpdatedEvent(msg: RespondToPollReqMsg, pollId: String, poll: SimplePollResultOutVO): Unit = { if (!Polls.hasUserAlreadyResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls)) {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(PollUpdatedEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(PollUpdatedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = PollUpdatedEvtMsgBody(pollId, poll)
val event = PollUpdatedEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
def broadcastUserRespondedToPollRecordMsg(msg: RespondToPollReqMsg, pollId: String, answerId: Int, answer: String, isSecret: Boolean): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(UserRespondedToPollRecordMsg.NAME, routing)
val header = BbbClientMsgHeader(UserRespondedToPollRecordMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = UserRespondedToPollRecordMsgBody(pollId, answerId, answer, isSecret)
val event = UserRespondedToPollRecordMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
def broadcastUserRespondedToPollRespMsg(msg: RespondToPollReqMsg, pollId: String, answerIds: Seq[Int], sendToId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, sendToId)
val envelope = BbbCoreEnvelope(UserRespondedToPollRespMsg.NAME, routing)
val header = BbbClientMsgHeader(UserRespondedToPollRespMsg.NAME, liveMeeting.props.meetingProp.intId, sendToId)
val body = UserRespondedToPollRespMsgBody(pollId, msg.header.userId, answerIds)
val event = UserRespondedToPollRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
if (Polls.checkUserResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls) == false) {
for { for {
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToPollReqMsg(msg.header.userId, msg.body.pollId, (pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToPollReqMsg(msg.header.userId, msg.body.pollId,
msg.body.questionId, msg.body.answerIds, liveMeeting) msg.body.questionId, msg.body.answerIds, liveMeeting)
} yield { } yield {
broadcastPollUpdatedEvent(msg, pollId, updatedPoll) PollHdlrHelpers.broadcastPollUpdatedEvent(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, updatedPoll)
for { for {
poll <- Polls.getPoll(pollId, liveMeeting.polls) poll <- Polls.getPoll(pollId, liveMeeting.polls)
} yield { } yield {
@ -58,14 +25,14 @@ trait RespondToPollReqMsgHdlr {
answerId <- msg.body.answerIds answerId <- msg.body.answerIds
} yield { } yield {
val answerText = poll.questions(0).answers.get(answerId).key val answerText = poll.questions(0).answers.get(answerId).key
broadcastUserRespondedToPollRecordMsg(msg, pollId, answerId, answerText, poll.isSecret) PollHdlrHelpers.broadcastUserRespondedToPollRecordMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, answerId, answerText, poll.isSecret)
} }
} }
for { for {
presenter <- Users2x.findPresenter(liveMeeting.users2x) presenter <- Users2x.findPresenter(liveMeeting.users2x)
} yield { } yield {
broadcastUserRespondedToPollRespMsg(msg, pollId, msg.body.answerIds, presenter.intId) PollHdlrHelpers.broadcastUserRespondedToPollRespMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, msg.body.answerIds, presenter.intId)
} }
} }
} else { } else {

View File

@ -1,10 +1,11 @@
package org.bigbluebutton.core.apps.polls package org.bigbluebutton.core.apps.polls
import org.bigbluebutton.ClientSettings.getConfigPropertyValueByPathAsIntOrElse
import org.bigbluebutton.common2.domain.SimplePollResultOutVO import org.bigbluebutton.common2.domain.SimplePollResultOutVO
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.models.Polls import org.bigbluebutton.core.models.Polls
import org.bigbluebutton.core.running.{ LiveMeeting } import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.models.Users2x import org.bigbluebutton.core.models.Users2x
trait RespondToTypedPollReqMsgHdlr { trait RespondToTypedPollReqMsgHdlr {
@ -12,43 +13,60 @@ trait RespondToTypedPollReqMsgHdlr {
def handle(msg: RespondToTypedPollReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = { def handle(msg: RespondToTypedPollReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastPollUpdatedEvent(msg: RespondToTypedPollReqMsg, pollId: String, poll: SimplePollResultOutVO): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(PollUpdatedEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(PollUpdatedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = PollUpdatedEvtMsgBody(pollId, poll)
val event = PollUpdatedEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
def broadcastUserRespondedToTypedPollRespMsg(msg: RespondToTypedPollReqMsg, pollId: String, answer: String, sendToId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, sendToId)
val envelope = BbbCoreEnvelope(UserRespondedToTypedPollRespMsg.NAME, routing)
val header = BbbClientMsgHeader(UserRespondedToTypedPollRespMsg.NAME, liveMeeting.props.meetingProp.intId, sendToId)
val body = UserRespondedToTypedPollRespMsgBody(pollId, msg.header.userId, answer)
val event = UserRespondedToTypedPollRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
if (Polls.isResponsePollType(msg.body.pollId, liveMeeting.polls) && if (Polls.isResponsePollType(msg.body.pollId, liveMeeting.polls) &&
Polls.checkUserResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls) == false && !Polls.hasUserAlreadyResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls) &&
Polls.checkUserAddedQuestion(msg.body.pollId, msg.header.userId, liveMeeting.polls) == false) { !Polls.hasUserAlreadyAddedTypedAnswer(msg.body.pollId, msg.header.userId, liveMeeting.polls)) {
//Truncate answer case it is longer than `maxTypedAnswerLength`
val maxTypedAnswerLength = getConfigPropertyValueByPathAsIntOrElse(liveMeeting.clientSettings, "public.poll.maxTypedAnswerLength", 45)
val answer = msg.body.answer.substring(0, Math.min(msg.body.answer.length, maxTypedAnswerLength))
val answerExists = Polls.findAnswerWithText(msg.body.pollId, msg.body.questionId, answer, liveMeeting.polls)
//Create answer if it doesn't exist
answerExists match {
case None => {
for { for {
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToTypedPollReqMsg(msg.header.userId, msg.body.pollId, (pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToTypedPollReqMsg(msg.header.userId, msg.body.pollId,
msg.body.questionId, msg.body.answer, liveMeeting) msg.body.questionId, answer, liveMeeting)
} yield { } yield {
broadcastPollUpdatedEvent(msg, pollId, updatedPoll) PollHdlrHelpers.broadcastPollUpdatedEvent(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, updatedPoll)
for { for {
presenter <- Users2x.findPresenter(liveMeeting.users2x) presenter <- Users2x.findPresenter(liveMeeting.users2x)
} yield { } yield {
broadcastUserRespondedToTypedPollRespMsg(msg, pollId, msg.body.answer, presenter.intId) PollHdlrHelpers.broadcastUserRespondedToTypedPollRespMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, answer, presenter.intId)
} }
} }
}
case _ => //Do nothing, answer with same text exists already
}
//Submit the answer
Polls.findAnswerWithText(msg.body.pollId, msg.body.questionId, answer, liveMeeting.polls) match {
case Some(answerId) => {
for {
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToPollReqMsg(msg.header.userId, msg.body.pollId,
msg.body.questionId, Seq(answerId), liveMeeting)
} yield {
PollHdlrHelpers.broadcastPollUpdatedEvent(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, updatedPoll)
for {
poll <- Polls.getPoll(pollId, liveMeeting.polls)
} yield {
val answerText = poll.questions(0).answers.get(answerId).key
PollHdlrHelpers.broadcastUserRespondedToPollRecordMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, answerId, answerText, poll.isSecret)
}
for {
presenter <- Users2x.findPresenter(liveMeeting.users2x)
} yield {
PollHdlrHelpers.broadcastUserRespondedToPollRespMsg(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, Seq(answerId), presenter.intId)
}
}
}
case None => log.error("Error while trying to answer the poll {} in meeting {}: Answer not found or something went wrong while trying to create the answer.", msg.body.pollId, msg.header.meetingId)
}
} else { } else {
log.info("Ignoring typed answer from user {} once user already added an answer to this poll {} in meeting {}", msg.header.userId, msg.body.pollId, msg.header.meetingId) log.info("Ignoring typed answer from user {} once user already added an answer to this poll {} in meeting {}", msg.header.userId, msg.body.pollId, msg.header.meetingId)
} }

View File

@ -46,7 +46,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
def buildNewPresFileAvailable(annotatedFileURI: String, originalFileURI: String, convertedFileURI: String, def buildNewPresFileAvailable(annotatedFileURI: String, originalFileURI: String, convertedFileURI: String,
presId: String, fileStateType: String): NewPresFileAvailableMsg = { presId: String, fileStateType: String): NewPresFileAvailableMsg = {
val header = BbbClientMsgHeader(NewPresFileAvailableMsg.NAME, "not-used", "not-used") val header = BbbClientMsgHeader(NewPresFileAvailableMsg.NAME, "not-used", "not-used")
val body = NewPresFileAvailableMsgBody(annotatedFileURI, originalFileURI, convertedFileURI, presId, fileStateType) val body = NewPresFileAvailableMsgBody(annotatedFileURI, originalFileURI, convertedFileURI, presId, fileStateType, "")
NewPresFileAvailableMsg(header, body) NewPresFileAvailableMsg(header, body)
} }
@ -160,7 +160,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
val pages: List[Int] = m.body.pages // Desired presentation pages for export val pages: List[Int] = m.body.pages // Desired presentation pages for export
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else pages val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else pages
val exportJob: ExportJob = new ExportJob(jobId, JobTypes.DOWNLOAD, "annotated_slides", presId, presLocation, allPages, pagesRange, meetingId, ""); val exportJob: ExportJob = new ExportJob(jobId, JobTypes.DOWNLOAD, currentPres.get.name, "annotated_slides", presId, presLocation, allPages, pagesRange, meetingId, "");
val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting); val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting);
val isPresentationOriginalOrConverted = m.body.fileStateType == "Original" || m.body.fileStateType == "Converted" val isPresentationOriginalOrConverted = m.body.fileStateType == "Original" || m.body.fileStateType == "Converted"
@ -226,7 +226,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
val currentPage: PresentationPage = PresentationInPod.getCurrentPage(currentPres.get).get val currentPage: PresentationPage = PresentationInPod.getCurrentPage(currentPres.get).get
val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else List(currentPage.num) val pagesRange: List[Int] = if (allPages) (1 to pageCount).toList else List(currentPage.num)
val exportJob: ExportJob = ExportJob(jobId, JobTypes.CAPTURE_PRESENTATION, filename, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken) val exportJob: ExportJob = ExportJob(jobId, JobTypes.CAPTURE_PRESENTATION, filename, filename, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken)
val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting); val storeAnnotationPages: List[PresentationPageForExport] = getPresentationPagesForExport(pagesRange, pageCount, presId, currentPres, liveMeeting);
val annotationCount: Int = storeAnnotationPages.map(_.annotations.size).sum val annotationCount: Int = storeAnnotationPages.map(_.annotations.size).sum
@ -252,11 +252,10 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
liveMeeting.props.meetingProp.intId, m.body.presId liveMeeting.props.meetingProp.intId, m.body.presId
) )
//TODO let frontend choose the name in favor of internationalization
if (m.body.fileStateType == "Annotated") { if (m.body.fileStateType == "Annotated") {
val presentationDownloadInfo = Map( val presentationDownloadInfo = Map(
"fileURI" -> m.body.annotatedFileURI, "fileURI" -> m.body.annotatedFileURI,
"filename" -> "annotated_slides.pdf" "filename" -> m.body.fileName
) )
ChatMessageDAO.insertSystemMsg(liveMeeting.props.meetingProp.intId, GroupChatApp.MAIN_PUBLIC_CHAT, "", GroupChatMessageType.PRESENTATION, presentationDownloadInfo, "") ChatMessageDAO.insertSystemMsg(liveMeeting.props.meetingProp.intId, GroupChatApp.MAIN_PUBLIC_CHAT, "", GroupChatMessageType.PRESENTATION, presentationDownloadInfo, "")
} else if (m.body.fileStateType == "Converted") { } else if (m.body.fileStateType == "Converted") {
@ -295,7 +294,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(m.body.parentMeetingId, userId, presentationUploadToken, filename, presentationId)) bus.outGW.send(buildPresentationUploadTokenSysPubMsg(m.body.parentMeetingId, userId, presentationUploadToken, filename, presentationId))
val exportJob = new ExportJob(jobId, JobTypes.CAPTURE_NOTES, filename, m.body.padId, "", true, List(), m.body.parentMeetingId, presentationUploadToken) val exportJob = new ExportJob(jobId, JobTypes.CAPTURE_NOTES, filename, filename, m.body.padId, "", true, List(), m.body.parentMeetingId, presentationUploadToken)
val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting) val job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)
bus.outGW.send(job) bus.outGW.send(job)

View File

@ -30,7 +30,10 @@ trait DeactivateTimerReqMsgHdlr extends RightsManagementTrait {
val reason = "You need to be the presenter or moderator to deactivate timer" val reason = "You need to be the presenter or moderator to deactivate timer"
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting) PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else { } else {
TimerModel.setIsActive(liveMeeting.timerModel, false) TimerModel.setRunning(liveMeeting.timerModel, running = false)
TimerModel.setIsActive(liveMeeting.timerModel, active = false)
TimerModel.setStopwatch(liveMeeting.timerModel, stopwatch = true)
TimerModel.reset(liveMeeting.timerModel)
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel) TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
broadcastEvent() broadcastEvent()
} }

View File

@ -31,7 +31,7 @@ trait StartTimerReqMsgHdlr extends RightsManagementTrait {
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting) PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else { } else {
TimerModel.setStartedAt(liveMeeting.timerModel, System.currentTimeMillis()) TimerModel.setStartedAt(liveMeeting.timerModel, System.currentTimeMillis())
TimerModel.setRunning(liveMeeting.timerModel, true) TimerModel.setRunning(liveMeeting.timerModel, running = true)
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel) TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
broadcastEvent() broadcastEvent()
} }

View File

@ -33,10 +33,9 @@ trait StopTimerReqMsgHdlr extends RightsManagementTrait {
val reason = "You need to be the presenter or moderator to stop timer" val reason = "You need to be the presenter or moderator to stop timer"
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting) PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else { } else {
TimerModel.setAccumulated(liveMeeting.timerModel, msg.body.accumulated) TimerModel.setRunning(liveMeeting.timerModel, running = false)
TimerModel.setRunning(liveMeeting.timerModel, false)
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel) TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
broadcastEvent(msg.body.accumulated) broadcastEvent(TimerModel.getAccumulated(liveMeeting.timerModel))
} }
} }
} }

View File

@ -34,6 +34,7 @@ trait SwitchTimerReqMsgHdlr extends RightsManagementTrait {
} else { } else {
if (TimerModel.getStopwatch(liveMeeting.timerModel) != msg.body.stopwatch) { if (TimerModel.getStopwatch(liveMeeting.timerModel) != msg.body.stopwatch) {
TimerModel.setStopwatch(liveMeeting.timerModel, msg.body.stopwatch) TimerModel.setStopwatch(liveMeeting.timerModel, msg.body.stopwatch)
TimerModel.setRunning(liveMeeting.timerModel, running = false)
TimerModel.reset(liveMeeting.timerModel) //Reset on switch Stopwatch/Timer TimerModel.reset(liveMeeting.timerModel) //Reset on switch Stopwatch/Timer
if (msg.body.stopwatch) { if (msg.body.stopwatch) {
TimerModel.setTrack(liveMeeting.timerModel, "noTrack") TimerModel.setTrack(liveMeeting.timerModel, "noTrack")

View File

@ -9,7 +9,7 @@ import org.bigbluebutton.core.running.OutMsgRouter
import org.bigbluebutton.core.running.MeetingActor import org.bigbluebutton.core.running.MeetingActor
import org.bigbluebutton.core2.MeetingStatus2x import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core2.Permissions import org.bigbluebutton.core2.Permissions
import org.bigbluebutton.core2.message.senders.MsgBuilder import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait { trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
this: MeetingActor => this: MeetingActor =>
@ -237,6 +237,16 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
) )
outGW.send(BbbCommonEnvCoreMsg(envelope, LockSettingsInMeetingChangedEvtMsg(header, body))) outGW.send(BbbCommonEnvCoreMsg(envelope, LockSettingsInMeetingChangedEvtMsg(header, body)))
//Refresh graphql session for all locked viewers
for {
user <- Users2x.findAll(liveMeeting.users2x)
if user.locked
if user.role == Roles.VIEWER_ROLE
regUser <- RegisteredUsers.findWithUserId(user.intId, liveMeeting.registeredUsers)
} yield {
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "lockSettings_changed", outGW)
}
} }
} }
} }

View File

@ -1,6 +1,6 @@
package org.bigbluebutton.core.apps.users package org.bigbluebutton.core.apps.users
import org.bigbluebutton.ClientSettings.getConfigPropertyValueByPath import org.bigbluebutton.ClientSettings.{ getConfigPropertyValueByPath, getConfigPropertyValueByPathAsIntOrElse }
import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.RightsManagementTrait import org.bigbluebutton.core.apps.RightsManagementTrait
import org.bigbluebutton.core.models.{ UserState, Users2x } import org.bigbluebutton.core.models.{ UserState, Users2x }
@ -31,13 +31,7 @@ trait ChangeUserReactionEmojiReqMsgHdlr extends RightsManagementTrait {
} }
//Get durationInSeconds from Client config //Get durationInSeconds from Client config
val userReactionExpire = val userReactionExpire = getConfigPropertyValueByPathAsIntOrElse(liveMeeting.clientSettings, "public.userReaction.expire", 30)
getConfigPropertyValueByPath(liveMeeting.clientSettings, "public.userReaction.expire") match {
case Some(durationInSeconds: Int) => durationInSeconds
case _ =>
log.debug("Config `public.userReaction.expire` not found.")
30
}
for { for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
newUserState <- Users2x.setReactionEmoji(liveMeeting.users2x, user.intId, msg.body.reactionEmoji, userReactionExpire) newUserState <- Users2x.setReactionEmoji(liveMeeting.users2x, user.intId, msg.body.reactionEmoji, userReactionExpire)

View File

@ -1,63 +0,0 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.SystemConfiguration
import scala.util.Random
trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val outGW: OutMsgRouter
def handleSelectRandomViewerReqMsg(msg: SelectRandomViewerReqMsg): Unit = {
log.debug("Received SelectRandomViewerReqMsg {}", SelectRandomViewerReqMsg)
def broadcastEvent(msg: SelectRandomViewerReqMsg, users: Vector[String], choice: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(SelectRandomViewerRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SelectRandomViewerRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = SelectRandomViewerRespMsgBody(msg.header.userId, users, choice)
val event = SelectRandomViewerRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to select random user."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
} else {
val users = Users2x.getRandomlyPickableUsers(liveMeeting.users2x, false)
val usersPicked = Users2x.getRandomlyPickableUsers(liveMeeting.users2x, reduceDuplicatedPick)
val randNum = new scala.util.Random
var pickedUser = if (usersPicked.size == 0) "" else usersPicked(randNum.nextInt(usersPicked.size)).intId
if (reduceDuplicatedPick) {
if (usersPicked.size <= 1) {
// Initialise the exemption
val usersToUnexempt = Users2x.findAll(liveMeeting.users2x)
usersToUnexempt foreach { u =>
Users2x.setUserExempted(liveMeeting.users2x, u.intId, false)
}
if (usersPicked.size == 0) {
// Pick again
val usersRepicked = Users2x.getRandomlyPickableUsers(liveMeeting.users2x, reduceDuplicatedPick)
pickedUser = if (usersRepicked.size == 0) "" else usersRepicked(randNum.nextInt(usersRepicked.size)).intId
Users2x.setUserExempted(liveMeeting.users2x, pickedUser, true)
}
} else if (usersPicked.size > 1) {
Users2x.setUserExempted(liveMeeting.users2x, pickedUser, true)
}
}
val userIds = users.map { case (v) => v.intId }
broadcastEvent(msg, userIds, pickedUser)
}
}
}

View File

@ -0,0 +1,41 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.domain.MeetingState2x
trait SetUserSpeechOptionsMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleSetUserSpeechOptionsReqMsg(msg: SetUserSpeechOptionsReqMsg): Unit = {
log.info("handleSetUserSpeechOptionsReqMsg: partialUtterances={} minUtteranceLength={} userId={}", msg.body.partialUtterances, msg.body.minUtteranceLength, msg.header.userId)
def broadcastUserSpeechOptionsChanged(user: UserState, partialUtterances: Boolean, minUtteranceLength: Int): Unit = {
val routingChange = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, user.intId
)
val envelopeChange = BbbCoreEnvelope(UserSpeechOptionsChangedEvtMsg.NAME, routingChange)
val headerChange = BbbClientMsgHeader(UserSpeechOptionsChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, user.intId)
val bodyChange = UserSpeechOptionsChangedEvtMsgBody(partialUtterances, minUtteranceLength)
val eventChange = UserSpeechOptionsChangedEvtMsg(headerChange, bodyChange)
val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange)
outGW.send(msgEventChange)
}
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
var changeLocale: Option[UserState] = None;
//changeLocale = Users2x.setUserSpeechLocale(liveMeeting.users2x, msg.header.userId, msg.body.locale)
broadcastUserSpeechOptionsChanged(user, msg.body.partialUtterances, msg.body.minUtteranceLength)
}
}
}

View File

@ -0,0 +1,25 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.RightsManagementTrait
import org.bigbluebutton.core.db.UserConnectionStatusDAO
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
trait UserConnectionAliveReqMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleUserConnectionAliveReqMsg(msg: UserConnectionAliveReqMsg): Unit = {
log.info("handleUserConnectionAliveReqMsg: userId={}", msg.body.userId)
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
} yield {
UserConnectionStatusDAO.updateUserAlive(user.intId)
}
}
}

View File

@ -0,0 +1,25 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.RightsManagementTrait
import org.bigbluebutton.core.db.UserConnectionStatusDAO
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
trait UserConnectionUpdateRttReqMsgHdlr extends RightsManagementTrait {
this: UsersApp =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleUserConnectionUpdateRttReqMsg(msg: UserConnectionUpdateRttReqMsg): Unit = {
log.info("handleUserConnectionUpdateRttReqMsg: networkRttInMs={} userId={}", msg.body.networkRttInMs, msg.body.userId)
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
} yield {
UserConnectionStatusDAO.updateUserRtt(user.intId, msg.body.networkRttInMs)
}
}
}

View File

@ -0,0 +1,29 @@
package org.bigbluebutton.core.apps.users
import org.bigbluebutton.core.api.UserEstablishedGraphqlConnectionInternalMsg
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
trait UserEstablishedGraphqlConnectionInternalMsgHdlr extends HandlerHelpers {
this: MeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleUserEstablishedGraphqlConnectionInternalMsg(msg: UserEstablishedGraphqlConnectionInternalMsg, state: MeetingState2x): MeetingState2x = {
log.info("Received user established a graphql connection. user {} meetingId={}", msg.userId, liveMeeting.props.meetingProp.intId)
Users2x.findWithIntId(liveMeeting.users2x, msg.userId) match {
case Some(reconnectingUser) =>
if (reconnectingUser.userLeftFlag.left) {
log.info("Resetting flag that user left meeting. user {}", msg.userId)
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.userId, leftFlag = false)
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.userId)
}
state
case None =>
state
}
}
}

View File

@ -20,7 +20,7 @@ trait UserJoinMeetingAfterReconnectReqMsgHdlr extends HandlerHelpers with UserJo
if (reconnectingUser.userLeftFlag.left) { if (reconnectingUser.userLeftFlag.left) {
log.info("Resetting flag that user left meeting. user {}", msg.body.userId) log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
// User has reconnected. Just reset it's flag. ralam Oct 23, 2018 // User has reconnected. Just reset it's flag. ralam Oct 23, 2018
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false) sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, leftFlag = false)
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId) Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
} }
state state

View File

@ -71,7 +71,7 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
private def resetUserLeftFlag(msg: UserJoinMeetingReqMsg) = { private def resetUserLeftFlag(msg: UserJoinMeetingReqMsg) = {
log.info("Resetting flag that user left meeting. user {}", msg.body.userId) log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, false) sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, leftFlag = false)
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId) Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
} }

View File

@ -1,6 +1,7 @@
package org.bigbluebutton.core.apps.users package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs.UserLeaveReqMsg import org.bigbluebutton.common2.msgs.UserLeaveReqMsg
import org.bigbluebutton.core.api.{ UserClosedAllGraphqlConnectionsInternalMsg }
import org.bigbluebutton.core.domain.MeetingState2x import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x } import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x }
import org.bigbluebutton.core.running.{ HandlerHelpers, MeetingActor, OutMsgRouter } import org.bigbluebutton.core.running.{ HandlerHelpers, MeetingActor, OutMsgRouter }
@ -12,23 +13,33 @@ trait UserLeaveReqMsgHdlr extends HandlerHelpers {
val outGW: OutMsgRouter val outGW: OutMsgRouter
def handleUserLeaveReqMsg(msg: UserLeaveReqMsg, state: MeetingState2x): MeetingState2x = { def handleUserLeaveReqMsg(msg: UserLeaveReqMsg, state: MeetingState2x): MeetingState2x = {
Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match { handleUserLeaveReq(msg.body.userId, msg.header.meetingId, msg.body.loggedOut, state)
}
def handleUserClosedAllGraphqlConnectionsInternalMsg(msg: UserClosedAllGraphqlConnectionsInternalMsg, state: MeetingState2x): MeetingState2x = {
log.info("Received user closed all graphql connections. user {} meetingId={}", msg.userId, liveMeeting.props.meetingProp.intId)
handleUserLeaveReq(msg.userId, liveMeeting.props.meetingProp.intId, loggedOut = false, state)
}
def handleUserLeaveReq(userId: String, meetingId: String, loggedOut: Boolean, state: MeetingState2x): MeetingState2x = {
Users2x.findWithIntId(liveMeeting.users2x, userId) match {
case Some(reconnectingUser) => case Some(reconnectingUser) =>
log.info("Received user left meeting. user {} meetingId={}", msg.body.userId, msg.header.meetingId) log.info("Received user left meeting. user {} meetingId={}", userId, meetingId)
if (!reconnectingUser.userLeftFlag.left) { if (!reconnectingUser.userLeftFlag.left) {
log.info("Setting user left flag. user {} meetingId={}", msg.body.userId, msg.header.meetingId) log.info("Setting user left flag. user {} meetingId={}", userId, meetingId)
// Just flag that user has left as the user might be reconnecting. // Just flag that user has left as the user might be reconnecting.
// An audit will remove this user if it hasn't rejoined after a certain period of time. // An audit will remove this user if it hasn't rejoined after a certain period of time.
// ralam oct 23, 2018 // ralam oct 23, 2018
sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, msg.body.userId, true) sendUserLeftFlagUpdatedEvtMsg(outGW, liveMeeting, userId, leftFlag = true)
Users2x.setUserLeftFlag(liveMeeting.users2x, msg.body.userId) Users2x.setUserLeftFlag(liveMeeting.users2x, userId)
} }
if (msg.body.loggedOut) { if (loggedOut) {
log.info("Setting user logged out flag. user {} meetingId={}", msg.body.userId, msg.header.meetingId) log.info("Setting user logged out flag. user {} meetingId={}", userId, meetingId)
for { for {
ru <- RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers) ru <- RegisteredUsers.findWithUserId(userId, liveMeeting.registeredUsers)
} yield { } yield {
RegisteredUsers.setUserLoggedOutFlag(liveMeeting.registeredUsers, ru) RegisteredUsers.setUserLoggedOutFlag(liveMeeting.registeredUsers, ru)
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, ru.id, ru.sessionToken, "user_loggedout", outGW) Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, ru.id, ru.sessionToken, "user_loggedout", outGW)
@ -39,4 +50,5 @@ trait UserLeaveReqMsgHdlr extends HandlerHelpers {
state state
} }
} }
} }

View File

@ -158,16 +158,18 @@ class UsersApp(
with RegisterUserReqMsgHdlr with RegisterUserReqMsgHdlr
with ChangeUserRoleCmdMsgHdlr with ChangeUserRoleCmdMsgHdlr
with SetUserSpeechLocaleMsgHdlr with SetUserSpeechLocaleMsgHdlr
with SetUserSpeechOptionsMsgHdlr
with SyncGetUsersMeetingRespMsgHdlr with SyncGetUsersMeetingRespMsgHdlr
with LogoutAndEndMeetingCmdMsgHdlr with LogoutAndEndMeetingCmdMsgHdlr
with SetRecordingStatusCmdMsgHdlr with SetRecordingStatusCmdMsgHdlr
with RecordAndClearPreviousMarkersCmdMsgHdlr with RecordAndClearPreviousMarkersCmdMsgHdlr
with SendRecordingTimerInternalMsgHdlr with SendRecordingTimerInternalMsgHdlr
with GetRecordingStatusReqMsgHdlr with GetRecordingStatusReqMsgHdlr
with SelectRandomViewerReqMsgHdlr
with AssignPresenterReqMsgHdlr with AssignPresenterReqMsgHdlr
with ChangeUserPinStateReqMsgHdlr with ChangeUserPinStateReqMsgHdlr
with ChangeUserMobileFlagReqMsgHdlr with ChangeUserMobileFlagReqMsgHdlr
with UserConnectionAliveReqMsgHdlr
with UserConnectionUpdateRttReqMsgHdlr
with ChangeUserReactionEmojiReqMsgHdlr with ChangeUserReactionEmojiReqMsgHdlr
with ChangeUserRaiseHandReqMsgHdlr with ChangeUserRaiseHandReqMsgHdlr
with ChangeUserAwayReqMsgHdlr with ChangeUserAwayReqMsgHdlr

View File

@ -58,7 +58,6 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
avatar = "", avatar = "",
color = userColor, color = userColor,
clientType = if (isDialInUser) "dial-in-user" else "", clientType = if (isDialInUser) "dial-in-user" else "",
pickExempted = false,
userLeftFlag = UserLeftFlag(false, 0) userLeftFlag = UserLeftFlag(false, 0)
) )
Users2x.add(liveMeeting.users2x, newUser) Users2x.add(liveMeeting.users2x, newUser)

View File

@ -40,7 +40,7 @@ trait UserLeftVoiceConfEvtMsgHdlr {
UsersApp.guestWaitingLeft(liveMeeting, user.intId, outGW) UsersApp.guestWaitingLeft(liveMeeting, user.intId, outGW)
} }
Users2x.remove(liveMeeting.users2x, user.intId) Users2x.remove(liveMeeting.users2x, user.intId)
UserDAO.delete(user.intId) UserDAO.softDelete(user.intId)
VoiceApp.removeUserFromVoiceConf(liveMeeting, outGW, msg.body.voiceUserId) VoiceApp.removeUserFromVoiceConf(liveMeeting, outGW, msg.body.voiceUserId)
} }

View File

@ -4,8 +4,9 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.PermissionCheck import org.bigbluebutton.core.apps.PermissionCheck
import org.bigbluebutton.core.bus.MessageBus import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.MeetingUsersPoliciesDAO import org.bigbluebutton.core.db.MeetingUsersPoliciesDAO
import org.bigbluebutton.core.models.{ RegisteredUsers, Roles, Users2x }
import org.bigbluebutton.core.running.LiveMeeting import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core2.message.senders.MsgBuilder import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr { trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
this: WebcamApp2x => this: WebcamApp2x =>
@ -76,6 +77,16 @@ trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
} }
broadcastEvent(meetingId, msg.body.setBy, value) broadcastEvent(meetingId, msg.body.setBy, value)
//Refresh graphql session for all locked viewers
for {
user <- Users2x.findAll(liveMeeting.users2x)
if user.locked
if user.role == Roles.VIEWER_ROLE
regUser <- RegisteredUsers.findWithUserId(user.intId, liveMeeting.registeredUsers)
} yield {
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "webcamOnlyForMod_changed", bus.outGW)
}
} }
case _ => case _ =>
} }

View File

@ -52,11 +52,11 @@ trait SendWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
) )
if (isUserOneOfPermited || isUserAmongPresenters) { if (isUserOneOfPermited || isUserAmongPresenters) {
println("============= Printing Sanitized annotations ============") // println("============= Printing Sanitized annotations ============")
for (annotation <- msg.body.annotations) { // for (annotation <- msg.body.annotations) {
printAnnotationInfo(annotation) // printAnnotationInfo(annotation)
} // }
println("============= Printed Sanitized annotations ============") // println("============= Printed Sanitized annotations ============")
val annotations = sendWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotations, liveMeeting, isUserAmongPresenters, isUserModerator) val annotations = sendWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotations, liveMeeting, isUserAmongPresenters, isUserModerator)
broadcastEvent(msg, msg.body.whiteboardId, annotations, msg.body.html5InstanceId) broadcastEvent(msg, msg.body.whiteboardId, annotations, msg.body.html5InstanceId)
} else { } else {

View File

@ -20,8 +20,16 @@ case class MeetingDbModel(
presentationUploadExternalUrl: String, presentationUploadExternalUrl: String,
learningDashboardAccessToken: String, learningDashboardAccessToken: String,
logoutUrl: String, logoutUrl: String,
customLogoUrl: Option[String],
bannerText: Option[String],
bannerColor: Option[String],
createdTime: Long, createdTime: Long,
durationInSeconds: Int durationInSeconds: Int,
endWhenNoModerator: Boolean,
endWhenNoModeratorDelayInMinutes: Int,
endedAt: Option[java.sql.Timestamp],
endedReasonCode: Option[String],
endedBy: Option[String],
) )
class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meeting") { class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meeting") {
@ -38,8 +46,16 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
presentationUploadExternalUrl, presentationUploadExternalUrl,
learningDashboardAccessToken, learningDashboardAccessToken,
logoutUrl, logoutUrl,
customLogoUrl,
bannerText,
bannerColor,
createdTime, createdTime,
durationInSeconds durationInSeconds,
endWhenNoModerator,
endWhenNoModeratorDelayInMinutes,
endedAt,
endedReasonCode,
endedBy
) <> (MeetingDbModel.tupled, MeetingDbModel.unapply) ) <> (MeetingDbModel.tupled, MeetingDbModel.unapply)
val meetingId = column[String]("meetingId", O.PrimaryKey) val meetingId = column[String]("meetingId", O.PrimaryKey)
val extId = column[String]("extId") val extId = column[String]("extId")
@ -53,8 +69,16 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
val presentationUploadExternalUrl = column[String]("presentationUploadExternalUrl") val presentationUploadExternalUrl = column[String]("presentationUploadExternalUrl")
val learningDashboardAccessToken = column[String]("learningDashboardAccessToken") val learningDashboardAccessToken = column[String]("learningDashboardAccessToken")
val logoutUrl = column[String]("logoutUrl") val logoutUrl = column[String]("logoutUrl")
val customLogoUrl = column[Option[String]]("customLogoUrl")
val bannerText = column[Option[String]]("bannerText")
val bannerColor = column[Option[String]]("bannerColor")
val createdTime = column[Long]("createdTime") val createdTime = column[Long]("createdTime")
val durationInSeconds = column[Int]("durationInSeconds") val durationInSeconds = column[Int]("durationInSeconds")
val endWhenNoModerator = column[Boolean]("endWhenNoModerator")
val endWhenNoModeratorDelayInMinutes = column[Int]("endWhenNoModeratorDelayInMinutes")
val endedAt = column[Option[java.sql.Timestamp]]("endedAt")
val endedReasonCode = column[Option[String]]("endedReasonCode")
val endedBy = column[Option[String]]("endedBy")
} }
object MeetingDAO { object MeetingDAO {
@ -74,8 +98,25 @@ object MeetingDAO {
presentationUploadExternalUrl = meetingProps.meetingProp.presentationUploadExternalUrl, presentationUploadExternalUrl = meetingProps.meetingProp.presentationUploadExternalUrl,
learningDashboardAccessToken = meetingProps.password.learningDashboardAccessToken, learningDashboardAccessToken = meetingProps.password.learningDashboardAccessToken,
logoutUrl = meetingProps.systemProps.logoutUrl, logoutUrl = meetingProps.systemProps.logoutUrl,
customLogoUrl = meetingProps.systemProps.customLogoURL match {
case "" => None
case logoUrl => Some(logoUrl)
},
bannerText = meetingProps.systemProps.bannerText match {
case "" => None
case bannerText => Some(bannerText)
},
bannerColor = meetingProps.systemProps.bannerColor match {
case "" => None
case bannerColor => Some(bannerColor)
},
createdTime = meetingProps.durationProps.createdTime, createdTime = meetingProps.durationProps.createdTime,
durationInSeconds = meetingProps.durationProps.duration * 60 durationInSeconds = meetingProps.durationProps.duration * 60,
endWhenNoModerator = meetingProps.durationProps.endWhenNoModerator,
endWhenNoModeratorDelayInMinutes = meetingProps.durationProps.endWhenNoModeratorDelayInMinutes,
endedAt = None,
endedReasonCode = None,
endedBy = None
) )
) )
).onComplete { ).onComplete {
@ -126,4 +167,50 @@ object MeetingDAO {
} }
} }
def setMeetingEnded(meetingId: String, endedReasonCode: String, endedBy: String) = {
UserDAO.softDeleteAllFromMeeting(meetingId)
DatabaseConnection.db.run(
TableQuery[MeetingDbTableDef]
.filter(_.meetingId === meetingId)
.map(a => (a.endedAt, a.endedReasonCode, a.endedBy))
.update(
(
Some(new java.sql.Timestamp(System.currentTimeMillis())),
Some(endedReasonCode),
endedBy match {
case "" => None
case c => Some(c)
}
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated endedAt=now() on Meeting table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating endedAt=now() Meeting: $e")
}
}
def setAllMeetingsEnded(endedReasonCode: String, endedBy: String) = {
DatabaseConnection.db.run(
TableQuery[MeetingDbTableDef]
.filter(_.endedAt.isEmpty)
.map(a => (a.endedAt, a.endedReasonCode, a.endedBy))
.update(
(
Some(new java.sql.Timestamp(System.currentTimeMillis())),
Some(endedReasonCode),
endedBy match {
case "" => None
case c => Some(c)
}
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated all-meetings endedAt=now() on Meeting table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating all-meetings endedAt=now() on Meeting table: $e")
}
}
} }

View File

@ -1,9 +1,13 @@
package org.bigbluebutton.core.db package org.bigbluebutton.core.db
import PostgresProfile.api._ import PostgresProfile.api._
import org.bigbluebutton.core.db.DatabaseConnection.{db, logger}
import spray.json.JsValue import spray.json.JsValue
import scala.concurrent.ExecutionContext.Implicits.global import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.{Await, Future}
import scala.util.{Failure, Success} import scala.util.{Failure, Success}
import scala.concurrent.duration.Duration
object Permission { object Permission {
val allowedRoles = List("MODERATOR","VIEWER","PRESENTER") val allowedRoles = List("MODERATOR","VIEWER","PRESENTER")
@ -19,6 +23,7 @@ case class PluginDataChannelMessageDbModel(
toRoles: Option[List[String]], toRoles: Option[List[String]],
toUserIds: Option[List[String]], toUserIds: Option[List[String]],
createdAt: java.sql.Timestamp, createdAt: java.sql.Timestamp,
deletedAt: Option[java.sql.Timestamp],
) )
class PluginDataChannelMessageDbTableDef(tag: Tag) extends Table[PluginDataChannelMessageDbModel](tag, None, "pluginDataChannelMessage") { class PluginDataChannelMessageDbTableDef(tag: Tag) extends Table[PluginDataChannelMessageDbModel](tag, None, "pluginDataChannelMessage") {
@ -31,7 +36,8 @@ class PluginDataChannelMessageDbTableDef(tag: Tag) extends Table[PluginDataChann
val toRoles = column[Option[List[String]]]("toRoles") val toRoles = column[Option[List[String]]]("toRoles")
val toUserIds = column[Option[List[String]]]("toUserIds") val toUserIds = column[Option[List[String]]]("toUserIds")
val createdAt = column[java.sql.Timestamp]("createdAt") val createdAt = column[java.sql.Timestamp]("createdAt")
override def * = (meetingId, pluginName, dataChannel, payloadJson, fromUserId, toRoles, toUserIds, createdAt) <> (PluginDataChannelMessageDbModel.tupled, PluginDataChannelMessageDbModel.unapply) val deletedAt = column[Option[java.sql.Timestamp]]("deletedAt")
override def * = (meetingId, pluginName, dataChannel, payloadJson, fromUserId, toRoles, toUserIds, createdAt, deletedAt) <> (PluginDataChannelMessageDbModel.tupled, PluginDataChannelMessageDbModel.unapply)
} }
object PluginDataChannelMessageDAO { object PluginDataChannelMessageDAO {
@ -49,7 +55,8 @@ object PluginDataChannelMessageDAO {
case filtered => Some(filtered) case filtered => Some(filtered)
}, },
toUserIds = if(toUserIds.isEmpty) None else Some(toUserIds), toUserIds = if(toUserIds.isEmpty) None else Some(toUserIds),
createdAt = new java.sql.Timestamp(System.currentTimeMillis()) createdAt = new java.sql.Timestamp(System.currentTimeMillis()),
deletedAt = None
) )
) )
).onComplete { ).onComplete {
@ -57,4 +64,51 @@ object PluginDataChannelMessageDAO {
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PluginDataChannelMessage: $e") case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PluginDataChannelMessage: $e")
} }
} }
def reset(meetingId: String, pluginName: String, dataChannel: String) = {
DatabaseConnection.db.run(
TableQuery[PluginDataChannelMessageDbTableDef]
.filter(_.meetingId === meetingId)
.filter(_.pluginName === pluginName)
.filter(_.dataChannel === dataChannel)
.map(u => (u.deletedAt))
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated deleted=now() on pluginDataChannelMessage table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating deleted=now() pluginDataChannelMessage: $e")
}
}
def getMessageSender(meetingId: String, pluginName: String, dataChannel: String, messageId: String): String = {
val query = sql"""SELECT "fromUserId"
FROM "pluginDataChannelMessage"
WHERE "deletedAt" is null
AND "meetingId" = ${meetingId}
AND "pluginName" = ${pluginName}
AND "dataChannel" = ${dataChannel}
AND "messageId" = ${messageId}""".as[String].headOption
Await.result(DatabaseConnection.db.run(query), Duration.Inf) match {
case Some(userId) => userId
case None => {
logger.debug("Message {} not found in database (maybe it was deleted).", messageId)
""
}
}
}
def delete(meetingId: String, pluginName: String, dataChannel: String, messageId: String) = {
DatabaseConnection.db.run(
sqlu"""UPDATE "pluginDataChannelMessage" SET
"deletedAt" = current_timestamp
WHERE "meetingId" = ${meetingId}
AND "pluginName" = ${pluginName}
AND "dataChannel" = ${dataChannel}
AND "messageId" = ${messageId}"""
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated deleted=now() on pluginDataChannelMessage table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating deleted=now() pluginDataChannelMessage: $e")
}
}
} }

View File

@ -14,8 +14,8 @@ case class TimerDbModel(
active: Boolean, active: Boolean,
time: Long, time: Long,
accumulated: Long, accumulated: Long,
startedAt: Long, startedOn: Long,
endedAt: Long, endedOn: Long,
songTrack: String, songTrack: String,
) )
@ -26,10 +26,10 @@ class TimerDbTableDef(tag: Tag) extends Table[TimerDbModel](tag, None, "timer")
val active = column[Boolean]("active") val active = column[Boolean]("active")
val time = column[Long]("time") val time = column[Long]("time")
val accumulated = column[Long]("accumulated") val accumulated = column[Long]("accumulated")
val startedAt = column[Long]("startedAt") val startedOn = column[Long]("startedOn")
val endedAt = column[Long]("endedAt") val endedOn = column[Long]("endedOn")
val songTrack = column[String]("songTrack") val songTrack = column[String]("songTrack")
override def * = (meetingId, stopwatch, running, active, time, accumulated, startedAt, endedAt, songTrack) <> (TimerDbModel.tupled, TimerDbModel.unapply) override def * = (meetingId, stopwatch, running, active, time, accumulated, startedOn, endedOn, songTrack) <> (TimerDbModel.tupled, TimerDbModel.unapply)
} }
object TimerDAO { object TimerDAO {
@ -43,8 +43,8 @@ object TimerDAO {
active = false, active = false,
time = 300000, time = 300000,
accumulated = 0, accumulated = 0,
startedAt = 0, startedOn = 0,
endedAt = 0, endedOn = 0,
songTrack = "noTrack", songTrack = "noTrack",
) )
) )
@ -58,7 +58,7 @@ object TimerDAO {
DatabaseConnection.db.run( DatabaseConnection.db.run(
TableQuery[TimerDbTableDef] TableQuery[TimerDbTableDef]
.filter(_.meetingId === meetingId) .filter(_.meetingId === meetingId)
.map(t => (t.stopwatch, t.running, t.active, t.time, t.accumulated, t.startedAt, t.endedAt, t.songTrack)) .map(t => (t.stopwatch, t.running, t.active, t.time, t.accumulated, t.startedOn, t.endedOn, t.songTrack))
.update((getStopwatch(timerModel), getRunning(timerModel), getIsActive(timerModel), getTime(timerModel), getAccumulated(timerModel), getStartedAt(timerModel), getEndedAt(timerModel), getTrack(timerModel)) .update((getStopwatch(timerModel), getRunning(timerModel), getIsActive(timerModel), getTime(timerModel), getAccumulated(timerModel), getStartedAt(timerModel), getEndedAt(timerModel), getTrack(timerModel))
) )
).onComplete { ).onComplete {

View File

@ -7,16 +7,20 @@ import scala.util.{ Failure, Success }
case class UserConnectionStatusDbModel( case class UserConnectionStatusDbModel(
userId: String, userId: String,
meetingId: String, meetingId: String,
connectionAliveAt: Option[java.sql.Timestamp] connectionAliveAt: Option[java.sql.Timestamp],
userClientResponseAt: Option[java.sql.Timestamp],
networkRttInMs: Option[Double]
) )
class UserConnectionStatusDbTableDef(tag: Tag) extends Table[UserConnectionStatusDbModel](tag, None, "user_connectionStatus") { class UserConnectionStatusDbTableDef(tag: Tag) extends Table[UserConnectionStatusDbModel](tag, None, "user_connectionStatus") {
override def * = ( override def * = (
userId, meetingId, connectionAliveAt userId, meetingId, connectionAliveAt, userClientResponseAt, networkRttInMs
) <> (UserConnectionStatusDbModel.tupled, UserConnectionStatusDbModel.unapply) ) <> (UserConnectionStatusDbModel.tupled, UserConnectionStatusDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey) val userId = column[String]("userId", O.PrimaryKey)
val meetingId = column[String]("meetingId") val meetingId = column[String]("meetingId")
val connectionAliveAt = column[Option[java.sql.Timestamp]]("connectionAliveAt") val connectionAliveAt = column[Option[java.sql.Timestamp]]("connectionAliveAt")
val userClientResponseAt = column[Option[java.sql.Timestamp]]("userClientResponseAt")
val networkRttInMs = column[Option[Double]]("networkRttInMs")
} }
object UserConnectionStatusDAO { object UserConnectionStatusDAO {
@ -27,7 +31,9 @@ object UserConnectionStatusDAO {
UserConnectionStatusDbModel( UserConnectionStatusDbModel(
userId = userId, userId = userId,
meetingId = meetingId, meetingId = meetingId,
connectionAliveAt = None connectionAliveAt = None,
userClientResponseAt = None,
networkRttInMs = None
) )
) )
).onComplete { ).onComplete {
@ -36,4 +42,28 @@ object UserConnectionStatusDAO {
} }
} }
def updateUserAlive(userId: String) = {
DatabaseConnection.db.run(
TableQuery[UserConnectionStatusDbTableDef]
.filter(_.userId === userId)
.map(t => (t.connectionAliveAt))
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated connectionAliveAt on UserConnectionStatus table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating connectionAliveAt on UserConnectionStatus: $e")
}
}
def updateUserRtt(userId: String, networkRttInMs: Double) = {
DatabaseConnection.db.run(
TableQuery[UserConnectionStatusDbTableDef]
.filter(_.userId === userId)
.map(t => (t.networkRttInMs, t.userClientResponseAt))
.update((Some(networkRttInMs), Some(new java.sql.Timestamp(System.currentTimeMillis()))))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated networkRttInMs on UserConnectionStatus table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating networkRttInMs on UserConnectionStatus: $e")
}
}
} }

View File

@ -14,6 +14,7 @@ case class UserDbModel(
avatar: String = "", avatar: String = "",
color: String = "", color: String = "",
sessionToken: String = "", sessionToken: String = "",
authToken: String = "",
authed: Boolean = false, authed: Boolean = false,
joined: Boolean = false, joined: Boolean = false,
joinErrorMessage: Option[String], joinErrorMessage: Option[String],
@ -31,7 +32,7 @@ case class UserDbModel(
class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") { class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
override def * = ( override def * = (
userId,extId,meetingId,name,role,avatar,color, sessionToken, authed,joined,joinErrorCode, joinErrorMessage, banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard, enforceLayout) <> (UserDbModel.tupled, UserDbModel.unapply) userId,extId,meetingId,name,role,avatar,color, sessionToken, authToken, authed,joined,joinErrorCode, joinErrorMessage, banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard, enforceLayout) <> (UserDbModel.tupled, UserDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey) val userId = column[String]("userId", O.PrimaryKey)
val extId = column[String]("extId") val extId = column[String]("extId")
val meetingId = column[String]("meetingId") val meetingId = column[String]("meetingId")
@ -40,6 +41,7 @@ class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
val avatar = column[String]("avatar") val avatar = column[String]("avatar")
val color = column[String]("color") val color = column[String]("color")
val sessionToken = column[String]("sessionToken") val sessionToken = column[String]("sessionToken")
val authToken = column[String]("authToken")
val authed = column[Boolean]("authed") val authed = column[Boolean]("authed")
val joined = column[Boolean]("joined") val joined = column[Boolean]("joined")
val joinErrorCode = column[Option[String]]("joinErrorCode") val joinErrorCode = column[Option[String]]("joinErrorCode")
@ -60,6 +62,7 @@ object UserDAO {
UserDbModel( UserDbModel(
userId = regUser.id, userId = regUser.id,
extId = regUser.externId, extId = regUser.externId,
authToken = regUser.authToken,
meetingId = meetingId, meetingId = meetingId,
name = regUser.name, name = regUser.name,
role = regUser.role, role = regUser.role,
@ -132,7 +135,7 @@ object UserDAO {
} }
def delete(intId: String) = { def softDelete(intId: String) = {
DatabaseConnection.db.run( DatabaseConnection.db.run(
TableQuery[UserDbTableDef] TableQuery[UserDbTableDef]
.filter(_.userId === intId) .filter(_.userId === intId)
@ -144,7 +147,19 @@ object UserDAO {
} }
} }
def deleteAllFromMeeting(meetingId: String) = { def softDeleteAllFromMeeting(meetingId: String) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.meetingId === meetingId)
.map(u => (u.loggedOut))
.update((true))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated loggedOut=true on user table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating loggedOut=true user: $e")
}
}
def permanentlyDeleteAllFromMeeting(meetingId: String) = {
DatabaseConnection.db.run( DatabaseConnection.db.run(
TableQuery[UserDbTableDef] TableQuery[UserDbTableDef]
.filter(_.meetingId === meetingId) .filter(_.meetingId === meetingId)

View File

@ -9,32 +9,35 @@ import scala.util.{Failure, Success }
case class UserGraphqlConnectionDbModel ( case class UserGraphqlConnectionDbModel (
graphqlConnectionId: Option[Int], graphqlConnectionId: Option[Int],
sessionToken: String, sessionToken: String,
middlewareUID: String,
middlewareConnectionId: String, middlewareConnectionId: String,
stablishedAt: java.sql.Timestamp, establishedAt: java.sql.Timestamp,
closedAt: Option[java.sql.Timestamp], closedAt: Option[java.sql.Timestamp],
) )
class UserGraphqlConnectionDbTableDef(tag: Tag) extends Table[UserGraphqlConnectionDbModel](tag, None, "user_graphqlConnection") { class UserGraphqlConnectionDbTableDef(tag: Tag) extends Table[UserGraphqlConnectionDbModel](tag, None, "user_graphqlConnection") {
override def * = ( override def * = (
graphqlConnectionId, sessionToken, middlewareConnectionId, stablishedAt, closedAt graphqlConnectionId, sessionToken, middlewareUID, middlewareConnectionId, establishedAt, closedAt
) <> (UserGraphqlConnectionDbModel.tupled, UserGraphqlConnectionDbModel.unapply) ) <> (UserGraphqlConnectionDbModel.tupled, UserGraphqlConnectionDbModel.unapply)
val graphqlConnectionId = column[Option[Int]]("graphqlConnectionId", O.PrimaryKey, O.AutoInc) val graphqlConnectionId = column[Option[Int]]("graphqlConnectionId", O.PrimaryKey, O.AutoInc)
val sessionToken = column[String]("sessionToken") val sessionToken = column[String]("sessionToken")
val middlewareUID = column[String]("middlewareUID")
val middlewareConnectionId = column[String]("middlewareConnectionId") val middlewareConnectionId = column[String]("middlewareConnectionId")
val stablishedAt = column[java.sql.Timestamp]("stablishedAt") val establishedAt = column[java.sql.Timestamp]("establishedAt")
val closedAt = column[Option[java.sql.Timestamp]]("closedAt") val closedAt = column[Option[java.sql.Timestamp]]("closedAt")
} }
object UserGraphqlConnectionDAO { object UserGraphqlConnectionDAO {
def insert(sessionToken: String, middlewareConnectionId: String) = { def insert(sessionToken: String, middlewareUID:String, middlewareConnectionId: String) = {
DatabaseConnection.db.run( DatabaseConnection.db.run(
TableQuery[UserGraphqlConnectionDbTableDef].insertOrUpdate( TableQuery[UserGraphqlConnectionDbTableDef].insertOrUpdate(
UserGraphqlConnectionDbModel( UserGraphqlConnectionDbModel(
graphqlConnectionId = None, graphqlConnectionId = None,
sessionToken = sessionToken, sessionToken = sessionToken,
middlewareUID = middlewareUID,
middlewareConnectionId = middlewareConnectionId, middlewareConnectionId = middlewareConnectionId,
stablishedAt = new java.sql.Timestamp(System.currentTimeMillis()), establishedAt = new java.sql.Timestamp(System.currentTimeMillis()),
closedAt = None closedAt = None
) )
) )
@ -46,11 +49,12 @@ object UserGraphqlConnectionDAO {
} }
} }
def updateClosed(sessionToken: String, middlewareConnectionId: String) = { def updateClosed(sessionToken: String, middlewareUID: String, middlewareConnectionId: String) = {
DatabaseConnection.db.run( DatabaseConnection.db.run(
TableQuery[UserGraphqlConnectionDbTableDef] TableQuery[UserGraphqlConnectionDbTableDef]
.filter(_.sessionToken === sessionToken) .filter(_.sessionToken === sessionToken)
.filter(_.middlewareConnectionId === middlewareConnectionId) .filter(_.middlewareConnectionId === middlewareConnectionId)
.filter(_.middlewareUID === middlewareUID)
.filter(_.closedAt.isEmpty) .filter(_.closedAt.isEmpty)
.map(u => u.closedAt) .map(u => u.closedAt)
.update(Some(new java.sql.Timestamp(System.currentTimeMillis()))) .update(Some(new java.sql.Timestamp(System.currentTimeMillis())))

View File

@ -26,11 +26,15 @@ case class UserStateDbModel(
pinned: Boolean = false, pinned: Boolean = false,
locked: Boolean = false, locked: Boolean = false,
speechLocale: String, speechLocale: String,
inactivityWarningDisplay: Boolean = false,
inactivityWarningTimeoutSecs: Option[Long],
) )
class UserStateDbTableDef(tag: Tag) extends Table[UserStateDbModel](tag, None, "user") { class UserStateDbTableDef(tag: Tag) extends Table[UserStateDbModel](tag, None, "user") {
override def * = ( override def * = (
userId,emoji,away,raiseHand,guestStatus,guestStatusSetByModerator,guestLobbyMessage,mobile,clientType,disconnected,expired,ejected,ejectReason,ejectReasonCode,ejectedByModerator,presenter,pinned,locked,speechLocale) <> (UserStateDbModel.tupled, UserStateDbModel.unapply) userId,emoji,away,raiseHand,guestStatus,guestStatusSetByModerator,guestLobbyMessage,mobile,clientType,disconnected,
expired,ejected,ejectReason,ejectReasonCode,ejectedByModerator,presenter,pinned,locked,speechLocale,
inactivityWarningDisplay, inactivityWarningTimeoutSecs) <> (UserStateDbModel.tupled, UserStateDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey) val userId = column[String]("userId", O.PrimaryKey)
val emoji = column[String]("emoji") val emoji = column[String]("emoji")
val away = column[Boolean]("away") val away = column[Boolean]("away")
@ -50,6 +54,8 @@ class UserStateDbTableDef(tag: Tag) extends Table[UserStateDbModel](tag, None, "
val pinned = column[Boolean]("pinned") val pinned = column[Boolean]("pinned")
val locked = column[Boolean]("locked") val locked = column[Boolean]("locked")
val speechLocale = column[String]("speechLocale") val speechLocale = column[String]("speechLocale")
val inactivityWarningDisplay = column[Boolean]("inactivityWarningDisplay")
val inactivityWarningTimeoutSecs = column[Option[Long]]("inactivityWarningTimeoutSecs")
} }
object UserStateDAO { object UserStateDAO {
@ -119,4 +125,21 @@ object UserStateDAO {
} }
} }
def updateInactivityWarning(intId: String, inactivityWarningDisplay: Boolean, inactivityWarningTimeoutSecs: Long) = {
DatabaseConnection.db.run(
TableQuery[UserStateDbTableDef]
.filter(_.userId === intId)
.map(u => (u.inactivityWarningDisplay, u.inactivityWarningTimeoutSecs))
.update((inactivityWarningDisplay,
inactivityWarningTimeoutSecs match {
case 0 => None
case timeout: Long => Some(timeout)
case _ => None
}))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated inactivityWarningDisplay on user table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating inactivityWarningDisplay user: $e")
}
}
} }

View File

@ -0,0 +1,48 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.models.{ VoiceUserState }
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class UserTranscriptionErrorDbModel(
userId: String,
meetingId: String,
errorCode: String,
errorMessage: String,
lastUpdatedAt: java.sql.Timestamp = new java.sql.Timestamp(System.currentTimeMillis())
)
class UserTranscriptionErrorDbTableDef(tag: Tag) extends Table[UserTranscriptionErrorDbModel](tag, None, "user_transcriptionError") {
override def * = (
userId, meetingId, errorCode, errorMessage, lastUpdatedAt
) <> (UserTranscriptionErrorDbModel.tupled, UserTranscriptionErrorDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
val errorCode = column[String]("errorCode")
val errorMessage = column[String]("errorMessage")
val lastUpdatedAt = column[java.sql.Timestamp]("lastUpdatedAt")
}
object UserTranscriptionErrorDAO {
def insert(userId: String, meetingId: String, errorCode: String, errorMessage: String) = {
DatabaseConnection.db.run(
TableQuery[UserTranscriptionErrorDbTableDef].insertOrUpdate(
UserTranscriptionErrorDbModel(
userId = userId,
meetingId = meetingId,
errorCode = errorCode,
errorMessage = errorMessage,
lastUpdatedAt = new java.sql.Timestamp(System.currentTimeMillis()),
)
)
).onComplete {
case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on user_transcriptionError table!")
}
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting user_transcriptionError: $e")
}
}
}

View File

@ -42,4 +42,5 @@ object MeetingEndReason {
val BREAKOUT_ENDED_BY_MOD = "BREAKOUT_ENDED_BY_MOD" val BREAKOUT_ENDED_BY_MOD = "BREAKOUT_ENDED_BY_MOD"
val ENDED_DUE_TO_NO_AUTHED_USER = "ENDED_DUE_TO_NO_AUTHED_USER" val ENDED_DUE_TO_NO_AUTHED_USER = "ENDED_DUE_TO_NO_AUTHED_USER"
val ENDED_DUE_TO_NO_MODERATOR = "ENDED_DUE_TO_NO_MODERATOR" val ENDED_DUE_TO_NO_MODERATOR = "ENDED_DUE_TO_NO_MODERATOR"
val ENDED_DUE_TO_SERVICE_INTERRUPTION = "ENDED_DUE_TO_SERVICE_INTERRUPTION"
} }

View File

@ -7,12 +7,10 @@ import org.bigbluebutton.SystemConfiguration
object AudioCaptions extends SystemConfiguration { object AudioCaptions extends SystemConfiguration {
def setFloor(audioCaptions: AudioCaptions, userId: String) = audioCaptions.floor = userId def setFloor(audioCaptions: AudioCaptions, userId: String) = audioCaptions.floor = userId
def isFloor(audioCaptions: AudioCaptions, userId: String) = audioCaptions.floor == userId def isFloor(audioCaptions: AudioCaptions, userId: String) = true
def parseTranscript(transcript: String): String = { def parseTranscript(transcript: String): String = {
val words = transcript.split("\\s+") // Split on whitespaces transcript
val lines = words.grouped(transcriptWords).toArray // Group each X words into lines
lines.takeRight(transcriptLines).map(l => l.mkString(" ")).mkString("\n") // Join the last X lines
} }
/* /*

View File

@ -104,7 +104,7 @@ object Polls {
} yield { } yield {
val pageId = if (poll.id.contains("deskshare")) "deskshare" else page.id val pageId = if (poll.id.contains("deskshare")) "deskshare" else page.id
val updatedShape = shape + ("whiteboardId" -> pageId) val updatedShape = shape + ("whiteboardId" -> pageId)
val annotation = new AnnotationVO(poll.id, updatedShape, pageId, requesterId) val annotation = new AnnotationVO(s"shape:poll-result-${poll.id}", updatedShape, pageId, requesterId)
annotation annotation
} }
} }
@ -243,7 +243,6 @@ object Polls {
private def handleRespondToTypedPoll(poll: SimplePollResultOutVO, requesterId: String, pollId: String, questionId: Int, private def handleRespondToTypedPoll(poll: SimplePollResultOutVO, requesterId: String, pollId: String, questionId: Int,
answer: String, lm: LiveMeeting): Option[SimplePollResultOutVO] = { answer: String, lm: LiveMeeting): Option[SimplePollResultOutVO] = {
addQuestionResponse(poll.id, questionId, answer, requesterId, lm.polls) addQuestionResponse(poll.id, questionId, answer, requesterId, lm.polls)
for { for {
updatedPoll <- getSimplePollResult(poll.id, lm.polls) updatedPoll <- getSimplePollResult(poll.id, lm.polls)
@ -254,12 +253,13 @@ object Polls {
private def pollResultToWhiteboardShape(result: SimplePollResultOutVO): scala.collection.immutable.Map[String, Object] = { private def pollResultToWhiteboardShape(result: SimplePollResultOutVO): scala.collection.immutable.Map[String, Object] = {
val shape = new scala.collection.mutable.HashMap[String, Object]() val shape = new scala.collection.mutable.HashMap[String, Object]()
shape += "numRespondents" -> new Integer(result.numRespondents) shape += "numRespondents" -> Integer.valueOf(result.numRespondents)
shape += "numResponders" -> new Integer(result.numResponders) shape += "numResponders" -> Integer.valueOf(result.numResponders)
shape += "questionType" -> result.questionType shape += "questionType" -> result.questionType
shape += "questionText" -> result.questionText shape += "questionText" -> result.questionText.getOrElse("")
shape += "id" -> result.id shape += "id" -> s"shape:poll-result-${result.id}"
shape += "answers" -> result.answers shape += "answers" -> result.answers
shape += "type" -> "geo"
shape.toMap shape.toMap
} }
@ -362,10 +362,10 @@ object Polls {
pvo pvo
} }
def checkUserResponded(pollId: String, userId: String, polls: Polls): Boolean = { def hasUserAlreadyResponded(pollId: String, userId: String, polls: Polls): Boolean = {
polls.polls.get(pollId) match { polls.polls.get(pollId) match {
case Some(p) => { case Some(p) => {
if (p.getResponders().filter(p => p.userId == userId).length > 0) { if (p.getResponders().exists(p => p.userId == userId)) {
true true
} else { } else {
false false
@ -375,10 +375,10 @@ object Polls {
} }
} }
def checkUserAddedQuestion(pollId: String, userId: String, polls: Polls): Boolean = { def hasUserAlreadyAddedTypedAnswer(pollId: String, userId: String, polls: Polls): Boolean = {
polls.polls.get(pollId) match { polls.polls.get(pollId) match {
case Some(p) => { case Some(p) => {
if (p.getTypedPollResponders().filter(responderId => responderId == userId).length > 0) { if (p.getTypedPollResponders().contains(userId)) {
true true
} else { } else {
false false
@ -401,6 +401,17 @@ object Polls {
} }
} }
def findAnswerWithText(pollId: String, questionId: Int, answerText: String, polls: Polls): Option[Int] = {
for {
poll <- Polls.getPoll(pollId, polls)
question <- poll.questions.find(q => q.id == questionId)
answers <- question.answers
equalAnswer <- answers.find(ans => ans.text.getOrElse("") == answerText)
} yield {
equalAnswer.id
}
}
def showPollResult(pollId: String, polls: Polls) { def showPollResult(pollId: String, polls: Polls) {
polls.get(pollId) foreach { polls.get(pollId) foreach {
p => p =>

View File

@ -176,8 +176,8 @@ case class PresentationPod(id: String, currentPresenter: String,
// 100D-checkedWidth is the maximum the page can be moved over // 100D-checkedWidth is the maximum the page can be moved over
val checkedWidth = Math.min(widthRatio, 100D) //if (widthRatio <= 100D) widthRatio else 100D val checkedWidth = Math.min(widthRatio, 100D) //if (widthRatio <= 100D) widthRatio else 100D
val checkedHeight = Math.min(heightRatio, 100D) val checkedHeight = Math.min(heightRatio, 100D)
val checkedXOffset = Math.min(xOffset, 0D) val checkedXOffset = xOffset
val checkedYOffset = Math.min(yOffset, 0D) val checkedYOffset = yOffset
for { for {
pres <- presentations.get(presentationId) pres <- presentations.get(presentationId)

View File

@ -91,7 +91,7 @@ object RegisteredUsers {
// will fail and can't join. // will fail and can't join.
// ralam april 21, 2020 // ralam april 21, 2020
val bannedUser = user.copy(banned = true) val bannedUser = user.copy(banned = true)
//UserDAO.insert(meetingId, bannedUser) UserDAO.insert(meetingId, bannedUser)
users.save(bannedUser) users.save(bannedUser)
} else { } else {
// If user hasn't been ejected, we allow user to join // If user hasn't been ejected, we allow user to join
@ -122,7 +122,7 @@ object RegisteredUsers {
u u
} else { } else {
users.delete(ejectedUser.id) users.delete(ejectedUser.id)
// UserDAO.delete(ejectedUser) it's being removed in User2x already // UserDAO.softDelete(ejectedUser) it's being removed in User2x already
ejectedUser ejectedUser
} }
} }

View File

@ -27,7 +27,7 @@ object Users2x {
} }
def remove(users: Users2x, intId: String): Option[UserState] = { def remove(users: Users2x, intId: String): Option[UserState] = {
//UserDAO.delete(intId) //UserDAO.softDelete(intId)
users.remove(intId) users.remove(intId)
} }
@ -78,15 +78,6 @@ object Users2x {
users.toVector.filter(u => !u.presenter) users.toVector.filter(u => !u.presenter)
} }
def getRandomlyPickableUsers(users: Users2x, reduceDup: Boolean): Vector[UserState] = {
if (reduceDup) {
users.toVector.filter(u => !u.presenter && u.role != Roles.MODERATOR_ROLE && !u.userLeftFlag.left && !u.pickExempted)
} else {
users.toVector.filter(u => !u.presenter && u.role != Roles.MODERATOR_ROLE && !u.userLeftFlag.left)
}
}
def findViewers(users: Users2x): Vector[UserState] = { def findViewers(users: Users2x): Vector[UserState] = {
users.toVector.filter(u => u.role == Roles.VIEWER_ROLE) users.toVector.filter(u => u.role == Roles.VIEWER_ROLE)
} }
@ -98,6 +89,19 @@ object Users2x {
def updateLastUserActivity(users: Users2x, u: UserState): UserState = { def updateLastUserActivity(users: Users2x, u: UserState): UserState = {
val newUserState = modify(u)(_.lastActivityTime).setTo(System.currentTimeMillis()) val newUserState = modify(u)(_.lastActivityTime).setTo(System.currentTimeMillis())
users.save(newUserState) users.save(newUserState)
//Reset inactivity warning
if (u.lastInactivityInspect != 0) {
resetLastInactivityInspect(users, newUserState)
} else {
newUserState
}
}
def resetLastInactivityInspect(users: Users2x, u: UserState): UserState = {
val newUserState = modify(u)(_.lastInactivityInspect).setTo(0)
users.save(newUserState)
UserStateDAO.updateInactivityWarning(u.intId, inactivityWarningDisplay = false, 0)
newUserState newUserState
} }
@ -125,7 +129,7 @@ object Users2x {
_ <- users.remove(intId) _ <- users.remove(intId)
ejectedUser <- users.removeFromCache(intId) ejectedUser <- users.removeFromCache(intId)
} yield { } yield {
// UserDAO.delete(intId) --it will keep the user on Db // UserDAO.softDelete(intId) --it will keep the user on Db
ejectedUser ejectedUser
} }
} }
@ -241,16 +245,6 @@ object Users2x {
} }
} }
def setUserExempted(users: Users2x, intId: String, exempted: Boolean): Option[UserState] = {
for {
u <- findWithIntId(users, intId)
} yield {
val newUser = u.modify(_.pickExempted).setTo(exempted)
users.save(newUser)
newUser
}
}
def setUserSpeechLocale(users: Users2x, intId: String, locale: String): Option[UserState] = { def setUserSpeechLocale(users: Users2x, intId: String, locale: String): Option[UserState] = {
for { for {
u <- findWithIntId(users, intId) u <- findWithIntId(users, intId)
@ -435,7 +429,6 @@ case class UserState(
lastActivityTime: Long = System.currentTimeMillis(), lastActivityTime: Long = System.currentTimeMillis(),
lastInactivityInspect: Long = 0, lastInactivityInspect: Long = 0,
clientType: String, clientType: String,
pickExempted: Boolean,
userLeftFlag: UserLeftFlag, userLeftFlag: UserLeftFlag,
speechLocale: String = "" speechLocale: String = ""
) )

View File

@ -111,10 +111,14 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[ChangeUserPinStateReqMsg](envelope, jsonNode) routeGenericMsg[ChangeUserPinStateReqMsg](envelope, jsonNode)
case ChangeUserMobileFlagReqMsg.NAME => case ChangeUserMobileFlagReqMsg.NAME =>
routeGenericMsg[ChangeUserMobileFlagReqMsg](envelope, jsonNode) routeGenericMsg[ChangeUserMobileFlagReqMsg](envelope, jsonNode)
case UserConnectionAliveReqMsg.NAME =>
routeGenericMsg[UserConnectionAliveReqMsg](envelope, jsonNode)
case UserConnectionUpdateRttReqMsg.NAME =>
routeGenericMsg[UserConnectionUpdateRttReqMsg](envelope, jsonNode)
case SetUserSpeechLocaleReqMsg.NAME => case SetUserSpeechLocaleReqMsg.NAME =>
routeGenericMsg[SetUserSpeechLocaleReqMsg](envelope, jsonNode) routeGenericMsg[SetUserSpeechLocaleReqMsg](envelope, jsonNode)
case SelectRandomViewerReqMsg.NAME => case SetUserSpeechOptionsReqMsg.NAME =>
routeGenericMsg[SelectRandomViewerReqMsg](envelope, jsonNode) routeGenericMsg[SetUserSpeechOptionsReqMsg](envelope, jsonNode)
// Poll // Poll
case StartCustomPollReqMsg.NAME => case StartCustomPollReqMsg.NAME =>
@ -404,6 +408,8 @@ class ReceivedJsonMsgHandlerActor(
// AudioCaptions // AudioCaptions
case UpdateTranscriptPubMsg.NAME => case UpdateTranscriptPubMsg.NAME =>
routeGenericMsg[UpdateTranscriptPubMsg](envelope, jsonNode) routeGenericMsg[UpdateTranscriptPubMsg](envelope, jsonNode)
case TranscriptionProviderErrorMsg.NAME =>
routeGenericMsg[TranscriptionProviderErrorMsg](envelope, jsonNode)
// GroupChats // GroupChats
case GetGroupChatsReqMsg.NAME => case GetGroupChatsReqMsg.NAME =>
@ -416,8 +422,14 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[CreateGroupChatReqMsg](envelope, jsonNode) routeGenericMsg[CreateGroupChatReqMsg](envelope, jsonNode)
//Plugin //Plugin
case DispatchPluginDataChannelMessageMsg.NAME => case PluginDataChannelDispatchMessageMsg.NAME =>
routeGenericMsg[DispatchPluginDataChannelMessageMsg](envelope, jsonNode) routeGenericMsg[PluginDataChannelDispatchMessageMsg](envelope, jsonNode)
case PluginDataChannelDeleteMessageMsg.NAME =>
routeGenericMsg[PluginDataChannelDeleteMessageMsg](envelope, jsonNode)
case PluginDataChannelResetMsg.NAME =>
routeGenericMsg[PluginDataChannelResetMsg](envelope, jsonNode)
// ExternalVideo // ExternalVideo
case StartExternalVideoPubMsg.NAME => case StartExternalVideoPubMsg.NAME =>
@ -456,6 +468,9 @@ class ReceivedJsonMsgHandlerActor(
case UserGraphqlConnectionClosedSysMsg.NAME => case UserGraphqlConnectionClosedSysMsg.NAME =>
route[UserGraphqlConnectionClosedSysMsg](meetingManagerChannel, envelope, jsonNode) route[UserGraphqlConnectionClosedSysMsg](meetingManagerChannel, envelope, jsonNode)
case CheckGraphqlMiddlewareAlivePongSysMsg.NAME =>
route[CheckGraphqlMiddlewareAlivePongSysMsg](meetingManagerChannel, envelope, jsonNode)
case _ => case _ =>
log.error("Cannot route envelope name " + envelope.name) log.error("Cannot route envelope name " + envelope.name)
// do nothing // do nothing

View File

@ -27,6 +27,10 @@ class StoreExportJobInRedisPresAnnEvent extends AbstractPresentationWithAnnotati
setEvent("StoreExportJobInRedisPresAnnEvent") setEvent("StoreExportJobInRedisPresAnnEvent")
def setserverSideFilename(serverSideFilename: String) {
eventMap.put(SERVER_SIDE_FILENAME, serverSideFilename)
}
def setJobId(jobId: String) { def setJobId(jobId: String) {
eventMap.put(JOB_ID, jobId) eventMap.put(JOB_ID, jobId)
} }
@ -68,6 +72,7 @@ object StoreExportJobInRedisPresAnnEvent {
protected final val JOB_ID = "jobId" protected final val JOB_ID = "jobId"
protected final val JOB_TYPE = "jobType" protected final val JOB_TYPE = "jobType"
protected final val FILENAME = "filename" protected final val FILENAME = "filename"
protected final val SERVER_SIDE_FILENAME = "serverSideFilename"
protected final val PRES_ID = "presId" protected final val PRES_ID = "presId"
protected final val PRES_LOCATION = "presLocation" protected final val PRES_LOCATION = "presLocation"
protected final val ALL_PAGES = "allPages" protected final val ALL_PAGES = "allPages"

View File

@ -7,7 +7,7 @@ import org.bigbluebutton.core.apps.groupchats.GroupChatApp
import org.bigbluebutton.core.apps.users.UsersApp import org.bigbluebutton.core.apps.users.UsersApp
import org.bigbluebutton.core.apps.voice.VoiceApp import org.bigbluebutton.core.apps.voice.VoiceApp
import org.bigbluebutton.core.bus.{BigBlueButtonEvent, InternalEventBus} import org.bigbluebutton.core.bus.{BigBlueButtonEvent, InternalEventBus}
import org.bigbluebutton.core.db.{BreakoutRoomUserDAO, MeetingRecordingDAO, UserBreakoutRoomDAO} import org.bigbluebutton.core.db.{BreakoutRoomUserDAO, MeetingDAO, MeetingRecordingDAO, UserBreakoutRoomDAO}
import org.bigbluebutton.core.domain.{MeetingEndReason, MeetingState2x} import org.bigbluebutton.core.domain.{MeetingEndReason, MeetingState2x}
import org.bigbluebutton.core.models._ import org.bigbluebutton.core.models._
import org.bigbluebutton.core2.MeetingStatus2x import org.bigbluebutton.core2.MeetingStatus2x
@ -73,7 +73,6 @@ trait HandlerHelpers extends SystemConfiguration {
avatar = regUser.avatarURL, avatar = regUser.avatarURL,
color = regUser.color, color = regUser.color,
clientType = clientType, clientType = clientType,
pickExempted = false,
userLeftFlag = UserLeftFlag(false, 0) userLeftFlag = UserLeftFlag(false, 0)
) )
} }
@ -206,6 +205,8 @@ trait HandlerHelpers extends SystemConfiguration {
val endedEvnt = buildMeetingEndedEvtMsg(liveMeeting.props.meetingProp.intId) val endedEvnt = buildMeetingEndedEvtMsg(liveMeeting.props.meetingProp.intId)
outGW.send(endedEvnt) outGW.send(endedEvnt)
MeetingDAO.setMeetingEnded(liveMeeting.props.meetingProp.intId, reason, userId)
} }
def destroyMeeting(eventBus: InternalEventBus, meetingId: String): Unit = { def destroyMeeting(eventBus: InternalEventBus, meetingId: String): Unit = {

View File

@ -76,6 +76,7 @@ class MeetingActor(
with UserJoinMeetingReqMsgHdlr with UserJoinMeetingReqMsgHdlr
with UserJoinMeetingAfterReconnectReqMsgHdlr with UserJoinMeetingAfterReconnectReqMsgHdlr
with UserEstablishedGraphqlConnectionInternalMsgHdlr
with UserConnectedToGlobalAudioMsgHdlr with UserConnectedToGlobalAudioMsgHdlr
with UserDisconnectedFromGlobalAudioMsgHdlr with UserDisconnectedFromGlobalAudioMsgHdlr
with MuteAllExceptPresentersCmdMsgHdlr with MuteAllExceptPresentersCmdMsgHdlr
@ -266,6 +267,12 @@ class MeetingActor(
// internal messages // internal messages
case msg: MonitorNumberOfUsersInternalMsg => handleMonitorNumberOfUsers(msg) case msg: MonitorNumberOfUsersInternalMsg => handleMonitorNumberOfUsers(msg)
case msg: SetPresenterInDefaultPodInternalMsg => state = presentationPodsApp.handleSetPresenterInDefaultPodInternalMsg(msg, state, liveMeeting, msgBus) case msg: SetPresenterInDefaultPodInternalMsg => state = presentationPodsApp.handleSetPresenterInDefaultPodInternalMsg(msg, state, liveMeeting, msgBus)
case msg: UserClosedAllGraphqlConnectionsInternalMsg =>
state = handleUserClosedAllGraphqlConnectionsInternalMsg(msg, state)
updateModeratorsPresence()
case msg: UserEstablishedGraphqlConnectionInternalMsg =>
state = handleUserEstablishedGraphqlConnectionInternalMsg(msg, state)
updateModeratorsPresence()
case msg: ExtendMeetingDuration => handleExtendMeetingDuration(msg) case msg: ExtendMeetingDuration => handleExtendMeetingDuration(msg)
case msg: SendTimeRemainingAuditInternalMsg => case msg: SendTimeRemainingAuditInternalMsg =>
@ -395,10 +402,12 @@ class MeetingActor(
case m: UserReactionTimeExpiredCmdMsg => handleUserReactionTimeExpiredCmdMsg(m) case m: UserReactionTimeExpiredCmdMsg => handleUserReactionTimeExpiredCmdMsg(m)
case m: ClearAllUsersEmojiCmdMsg => handleClearAllUsersEmojiCmdMsg(m) case m: ClearAllUsersEmojiCmdMsg => handleClearAllUsersEmojiCmdMsg(m)
case m: ClearAllUsersReactionCmdMsg => handleClearAllUsersReactionCmdMsg(m) case m: ClearAllUsersReactionCmdMsg => handleClearAllUsersReactionCmdMsg(m)
case m: SelectRandomViewerReqMsg => usersApp.handleSelectRandomViewerReqMsg(m)
case m: ChangeUserPinStateReqMsg => usersApp.handleChangeUserPinStateReqMsg(m) case m: ChangeUserPinStateReqMsg => usersApp.handleChangeUserPinStateReqMsg(m)
case m: ChangeUserMobileFlagReqMsg => usersApp.handleChangeUserMobileFlagReqMsg(m) case m: ChangeUserMobileFlagReqMsg => usersApp.handleChangeUserMobileFlagReqMsg(m)
case m: UserConnectionAliveReqMsg => usersApp.handleUserConnectionAliveReqMsg(m)
case m: UserConnectionUpdateRttReqMsg => usersApp.handleUserConnectionUpdateRttReqMsg(m)
case m: SetUserSpeechLocaleReqMsg => usersApp.handleSetUserSpeechLocaleReqMsg(m) case m: SetUserSpeechLocaleReqMsg => usersApp.handleSetUserSpeechLocaleReqMsg(m)
case m: SetUserSpeechOptionsReqMsg => usersApp.handleSetUserSpeechOptionsReqMsg(m)
// Client requested to eject user // Client requested to eject user
case m: EjectUserFromMeetingCmdMsg => case m: EjectUserFromMeetingCmdMsg =>
@ -582,6 +591,7 @@ class MeetingActor(
// AudioCaptions // AudioCaptions
case m: UpdateTranscriptPubMsg => audioCaptionsApp2x.handle(m, liveMeeting, msgBus) case m: UpdateTranscriptPubMsg => audioCaptionsApp2x.handle(m, liveMeeting, msgBus)
case m: TranscriptionProviderErrorMsg => audioCaptionsApp2x.handleTranscriptionProviderErrorMsg(m, liveMeeting, msgBus)
// GroupChat // GroupChat
case m: CreateGroupChatReqMsg => case m: CreateGroupChatReqMsg =>
@ -594,7 +604,9 @@ class MeetingActor(
updateUserLastActivity(m.body.msg.sender.id) updateUserLastActivity(m.body.msg.sender.id)
// Plugin // Plugin
case m: DispatchPluginDataChannelMessageMsg => pluginHdlrs.handle(m, state, liveMeeting) case m: PluginDataChannelDispatchMessageMsg => pluginHdlrs.handle(m, state, liveMeeting)
case m: PluginDataChannelDeleteMessageMsg => pluginHdlrs.handle(m, state, liveMeeting)
case m: PluginDataChannelResetMsg => pluginHdlrs.handle(m, state, liveMeeting)
// Webcams // Webcams
case m: UserBroadcastCamStartMsg => webcamApp2x.handle(m, liveMeeting, msgBus) case m: UserBroadcastCamStartMsg => webcamApp2x.handle(m, liveMeeting, msgBus)
@ -973,6 +985,7 @@ class MeetingActor(
val secsToDisconnect = TimeUnit.MILLISECONDS.toSeconds(expiryTracker.userActivitySignResponseDelayInMs); val secsToDisconnect = TimeUnit.MILLISECONDS.toSeconds(expiryTracker.userActivitySignResponseDelayInMs);
Sender.sendUserInactivityInspectMsg(liveMeeting.props.meetingProp.intId, u.intId, secsToDisconnect, outGW) Sender.sendUserInactivityInspectMsg(liveMeeting.props.meetingProp.intId, u.intId, secsToDisconnect, outGW)
UserStateDAO.updateInactivityWarning(u.intId, inactivityWarningDisplay = true, secsToDisconnect)
updateUserLastInactivityInspect(u.intId) updateUserLastInactivityInspect(u.intId)
} }
} }

View File

@ -78,6 +78,8 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
case m: AssignPresenterReqMsg => logMessage(msg) case m: AssignPresenterReqMsg => logMessage(msg)
case m: ChangeUserPinStateReqMsg => logMessage(msg) case m: ChangeUserPinStateReqMsg => logMessage(msg)
case m: ChangeUserMobileFlagReqMsg => logMessage(msg) case m: ChangeUserMobileFlagReqMsg => logMessage(msg)
case m: UserConnectionAliveReqMsg => logMessage(msg)
case m: UserConnectionUpdateRttReqMsg => logMessage(msg)
case m: ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg => logMessage(msg) case m: ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg => logMessage(msg)
case m: ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsg => logMessage(msg) case m: ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsg => logMessage(msg)
case m: ScreenshareRtmpBroadcastStartedEvtMsg => logMessage(msg) case m: ScreenshareRtmpBroadcastStartedEvtMsg => logMessage(msg)

View File

@ -256,6 +256,16 @@ object MsgBuilder {
BbbCommonEnvCoreMsg(envelope, event) BbbCommonEnvCoreMsg(envelope, event)
} }
def buildCheckGraphqlMiddlewareAlivePingSysMsg(middlewareUid: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.SYSTEM, "", "")
val envelope = BbbCoreEnvelope(CheckGraphqlMiddlewareAlivePingSysMsg.NAME, routing)
val header = BbbCoreHeaderWithMeetingId(CheckGraphqlMiddlewareAlivePingSysMsg.NAME, "")
val body = CheckGraphqlMiddlewareAlivePingSysMsgBody(middlewareUid)
val event = CheckGraphqlMiddlewareAlivePingSysMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def buildEjectAllFromVoiceConfMsg(meetingId: String, voiceConf: String): BbbCommonEnvCoreMsg = { def buildEjectAllFromVoiceConfMsg(meetingId: String, voiceConf: String): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka") val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(EjectAllFromVoiceConfMsg.NAME, routing) val envelope = BbbCoreEnvelope(EjectAllFromVoiceConfMsg.NAME, routing)

View File

@ -69,8 +69,7 @@ trait FakeTestData {
UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role, pin = false, UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role, pin = false,
mobile = false, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus, mobile = false, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, locked = false, presenter = false, emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, locked = false, presenter = false,
avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown", avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown", userLeftFlag = UserLeftFlag(false, 0))
pickExempted = false, userLeftFlag = UserLeftFlag(false, 0))
} }
} }

View File

@ -73,6 +73,7 @@ class ExportAnnotationsActor(
private def handleStoreExportJobInRedisSysMsg(msg: StoreExportJobInRedisSysMsg) { private def handleStoreExportJobInRedisSysMsg(msg: StoreExportJobInRedisSysMsg) {
val ev = new StoreExportJobInRedisPresAnnEvent() val ev = new StoreExportJobInRedisPresAnnEvent()
ev.setserverSideFilename(msg.body.exportJob.serverSideFilename)
ev.setJobId(msg.body.exportJob.jobId) ev.setJobId(msg.body.exportJob.jobId)
ev.setJobType(msg.body.exportJob.jobType) ev.setJobType(msg.body.exportJob.jobType)
ev.setFilename(msg.body.exportJob.filename) ev.setFilename(msg.body.exportJob.filename)

View File

@ -1,43 +0,0 @@
package org.bigbluebutton.endpoint.redis
import org.apache.pekko.actor.{Actor, ActorLogging, ActorSystem, Props}
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.db.UserGraphqlConnectionDAO
object GraphqlActionsActor {
def props(system: ActorSystem): Props =
Props(
classOf[GraphqlActionsActor],
system,
)
}
class GraphqlActionsActor(
system: ActorSystem,
) extends Actor with ActorLogging {
def receive = {
//=============================
// 2x messages
case msg: BbbCommonEnvCoreMsg => handleBbbCommonEnvCoreMsg(msg)
case _ => // do nothing
}
private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = {
msg.core match {
// Messages from bbb-graphql-middleware
case m: UserGraphqlConnectionEstablishedSysMsg => handleUserGraphqlConnectionEstablishedSysMsg(m)
case m: UserGraphqlConnectionClosedSysMsg => handleUserGraphqlConnectionClosedSysMsg(m)
case _ => // message not to be handled.
}
}
private def handleUserGraphqlConnectionEstablishedSysMsg(msg: UserGraphqlConnectionEstablishedSysMsg) {
UserGraphqlConnectionDAO.insert(msg.body.sessionToken, msg.body.browserConnectionId)
}
private def handleUserGraphqlConnectionClosedSysMsg(msg: UserGraphqlConnectionClosedSysMsg) {
UserGraphqlConnectionDAO.updateClosed(msg.body.sessionToken, msg.body.browserConnectionId)
}
}

View File

@ -0,0 +1,171 @@
package org.bigbluebutton.endpoint.redis
import org.apache.pekko.actor.{Actor, ActorLogging, ActorSystem, Props}
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.OutMessageGateway
import org.bigbluebutton.core.api.{UserClosedAllGraphqlConnectionsInternalMsg, UserEstablishedGraphqlConnectionInternalMsg}
import org.bigbluebutton.core.bus.{BigBlueButtonEvent, InternalEventBus}
import org.bigbluebutton.core.db.UserGraphqlConnectionDAO
import org.bigbluebutton.core2.message.senders.MsgBuilder
import scala.concurrent.ExecutionContext
import scala.concurrent.duration._
import ExecutionContext.Implicits.global
case object MiddlewareHealthCheckScheduler10Sec
object GraphqlConnectionsActor {
def props(system: ActorSystem,
eventBus: InternalEventBus,
outGW: OutMessageGateway,
): Props =
Props(
classOf[GraphqlConnectionsActor],
system,
eventBus,
outGW,
)
}
case class GraphqlUser(
intId: String,
meetingId: String,
sessionToken: String,
)
case class GraphqlUserConnection(
middlewareUID: String,
browserConnectionId: String,
sessionToken: String,
user: GraphqlUser,
)
class GraphqlConnectionsActor(
system: ActorSystem,
val eventBus: InternalEventBus,
val outGW: OutMessageGateway,
) extends Actor with ActorLogging {
private var users: Map[String, GraphqlUser] = Map()
private var graphqlConnections: Map[String, GraphqlUserConnection] = Map()
private var pendingResponseMiddlewareUIDs: Map[String, BigInt] = Map()
system.scheduler.schedule(10.seconds, 10.seconds, self, MiddlewareHealthCheckScheduler10Sec)
private val maxMiddlewareInactivityInMillis = 11000
def receive = {
//=============================
// 2x messages
case msg: BbbCommonEnvCoreMsg => handleBbbCommonEnvCoreMsg(msg)
case MiddlewareHealthCheckScheduler10Sec => runMiddlewareHealthCheck()
case _ => // do nothing
}
private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = {
msg.core match {
case m: RegisterUserReqMsg => handleUserRegisteredRespMsg(m)
case m: DestroyMeetingSysCmdMsg => handleDestroyMeetingSysCmdMsg(m)
// Messages from bbb-graphql-middleware
case m: UserGraphqlConnectionEstablishedSysMsg => handleUserGraphqlConnectionEstablishedSysMsg(m)
case m: UserGraphqlConnectionClosedSysMsg => handleUserGraphqlConnectionClosedSysMsg(m)
case m: CheckGraphqlMiddlewareAlivePongSysMsg => handleCheckGraphqlMiddlewareAlivePongSysMsg(m)
case _ => // message not to be handled.
}
}
private def handleUserRegisteredRespMsg(msg: RegisterUserReqMsg): Unit = {
users += (msg.body.sessionToken -> GraphqlUser(
msg.body.intUserId,
msg.body.meetingId,
msg.body.sessionToken
))
}
private def handleDestroyMeetingSysCmdMsg(msg: DestroyMeetingSysCmdMsg): Unit = {
users = users.filter(u => u._2.meetingId != msg.body.meetingId)
graphqlConnections = graphqlConnections.filter(c => c._2.user.meetingId != msg.body.meetingId)
}
private def handleUserGraphqlConnectionEstablishedSysMsg(msg: UserGraphqlConnectionEstablishedSysMsg): Unit = {
UserGraphqlConnectionDAO.insert(msg.body.sessionToken, msg.body.middlewareUID, msg.body.browserConnectionId)
for {
user <- users.get(msg.body.sessionToken)
} yield {
//Send internal message informing user has connected
if (!graphqlConnections.values.exists(c => c.sessionToken == msg.body.sessionToken)) {
eventBus.publish(BigBlueButtonEvent(user.meetingId, UserEstablishedGraphqlConnectionInternalMsg(user.intId)))
}
graphqlConnections += (msg.body.browserConnectionId -> GraphqlUserConnection(
msg.body.middlewareUID,
msg.body.browserConnectionId,
msg.body.sessionToken,
user
))
}
}
private def handleUserGraphqlConnectionClosedSysMsg(msg: UserGraphqlConnectionClosedSysMsg): Unit = {
handleUserGraphqlConnectionClosed(msg.body.sessionToken, msg.body.middlewareUID, msg.body.browserConnectionId)
}
private def handleUserGraphqlConnectionClosed(sessionToken: String, middlewareUID: String, browserConnectionId: String): Unit = {
UserGraphqlConnectionDAO.updateClosed(sessionToken, middlewareUID, browserConnectionId)
for {
user <- users.get(sessionToken)
} yield {
graphqlConnections = graphqlConnections.-(browserConnectionId)
//Send internal message informing user disconnected
if (!graphqlConnections.values.exists(c => c.sessionToken == sessionToken)) {
eventBus.publish(BigBlueButtonEvent(user.meetingId, UserClosedAllGraphqlConnectionsInternalMsg(user.intId)))
}
}
}
private def runMiddlewareHealthCheck(): Unit = {
removeInactiveConnections()
sendPingMessageToAllMiddlewareServices()
}
private def sendPingMessageToAllMiddlewareServices(): Unit = {
graphqlConnections.map(c => {
c._2.middlewareUID
}).toVector.distinct.map(middlewareUID => {
val event = MsgBuilder.buildCheckGraphqlMiddlewareAlivePingSysMsg(middlewareUID)
outGW.send(event)
log.debug(s"Sent ping message from graphql middleware ${middlewareUID}.")
pendingResponseMiddlewareUIDs.get(middlewareUID) match {
case None => pendingResponseMiddlewareUIDs += (middlewareUID -> System.currentTimeMillis)
case _ => //Ignore
}
})
}
private def removeInactiveConnections(): Unit = {
for {
(middlewareUid, pingSentAt) <- pendingResponseMiddlewareUIDs
if (System.currentTimeMillis - pingSentAt) > maxMiddlewareInactivityInMillis
} yield {
log.info("Removing connections from the middleware {} due to inactivity of the service.",middlewareUid)
for {
(_, graphqlConn) <- graphqlConnections
if graphqlConn.middlewareUID == middlewareUid
} yield {
handleUserGraphqlConnectionClosed(graphqlConn.sessionToken, graphqlConn.middlewareUID, graphqlConn.browserConnectionId)
}
pendingResponseMiddlewareUIDs -= middlewareUid
}
}
private def handleCheckGraphqlMiddlewareAlivePongSysMsg(msg: CheckGraphqlMiddlewareAlivePongSysMsg): Unit = {
log.debug(s"Received pong message from graphql middleware ${msg.body.middlewareUID}.")
pendingResponseMiddlewareUIDs -= msg.body.middlewareUID
}
}

View File

@ -20,6 +20,7 @@ case class Meeting(
intId: String, intId: String,
extId: String, extId: String,
name: String, name: String,
downloadSessionDataEnabled: Boolean,
users: Map[String, User] = Map(), users: Map[String, User] = Map(),
polls: Map[String, Poll] = Map(), polls: Map[String, Poll] = Map(),
screenshares: Vector[Screenshare] = Vector(), screenshares: Vector[Screenshare] = Vector(),
@ -585,6 +586,7 @@ class LearningDashboardActor(
msg.body.props.meetingProp.intId, msg.body.props.meetingProp.intId,
msg.body.props.meetingProp.extId, msg.body.props.meetingProp.extId,
msg.body.props.meetingProp.name, msg.body.props.meetingProp.name,
downloadSessionDataEnabled = !msg.body.props.meetingProp.disabledFeatures.contains("learningDashboardDownloadSessionData"),
) )
meetings += (newMeeting.intId -> newMeeting) meetings += (newMeeting.intId -> newMeeting)

View File

@ -85,6 +85,9 @@ class RedisRecorderActor(
case m: UserLeftMeetingEvtMsg => handleUserLeftMeetingEvtMsg(m) case m: UserLeftMeetingEvtMsg => handleUserLeftMeetingEvtMsg(m)
case m: PresenterAssignedEvtMsg => handlePresenterAssignedEvtMsg(m) case m: PresenterAssignedEvtMsg => handlePresenterAssignedEvtMsg(m)
case m: UserEmojiChangedEvtMsg => handleUserEmojiChangedEvtMsg(m) case m: UserEmojiChangedEvtMsg => handleUserEmojiChangedEvtMsg(m)
case m: UserAwayChangedEvtMsg => handleUserAwayChangedEvtMsg(m)
case m: UserRaiseHandChangedEvtMsg => handleUserRaiseHandChangedEvtMsg(m)
case m: UserReactionEmojiChangedEvtMsg => handleUserReactionEmojiChangedEvtMsg(m)
case m: UserRoleChangedEvtMsg => handleUserRoleChangedEvtMsg(m) case m: UserRoleChangedEvtMsg => handleUserRoleChangedEvtMsg(m)
case m: UserBroadcastCamStartedEvtMsg => handleUserBroadcastCamStartedEvtMsg(m) case m: UserBroadcastCamStartedEvtMsg => handleUserBroadcastCamStartedEvtMsg(m)
case m: UserBroadcastCamStoppedEvtMsg => handleUserBroadcastCamStoppedEvtMsg(m) case m: UserBroadcastCamStoppedEvtMsg => handleUserBroadcastCamStoppedEvtMsg(m)
@ -112,7 +115,7 @@ class RedisRecorderActor(
//case m: DeskShareNotifyViewersRTMP => handleDeskShareNotifyViewersRTMP(m) //case m: DeskShareNotifyViewersRTMP => handleDeskShareNotifyViewersRTMP(m)
// AudioCaptions // AudioCaptions
case m: TranscriptUpdatedEvtMsg => handleTranscriptUpdatedEvtMsg(m) //case m: TranscriptUpdatedEvtMsg => handleTranscriptUpdatedEvtMsg(m) // temporarily disabling due to issue https://github.com/bigbluebutton/bigbluebutton/issues/19701
// Meeting // Meeting
case m: RecordingStatusChangedEvtMsg => handleRecordingStatusChangedEvtMsg(m) case m: RecordingStatusChangedEvtMsg => handleRecordingStatusChangedEvtMsg(m)
@ -379,6 +382,18 @@ class RedisRecorderActor(
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "emojiStatus", msg.body.emoji) handleUserStatusChange(msg.header.meetingId, msg.body.userId, "emojiStatus", msg.body.emoji)
} }
private def handleUserAwayChangedEvtMsg(msg: UserAwayChangedEvtMsg) {
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "away", if (msg.body.away) "true" else "false")
}
private def handleUserRaiseHandChangedEvtMsg(msg: UserRaiseHandChangedEvtMsg) {
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "raiseHand", if (msg.body.raiseHand) "true" else "false")
}
private def handleUserReactionEmojiChangedEvtMsg(msg: UserReactionEmojiChangedEvtMsg) {
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "reactionEmoji", msg.body.reactionEmoji)
}
private def handleUserRoleChangedEvtMsg(msg: UserRoleChangedEvtMsg) { private def handleUserRoleChangedEvtMsg(msg: UserRoleChangedEvtMsg) {
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "role", msg.body.role) handleUserStatusChange(msg.header.meetingId, msg.body.userId, "role", msg.body.role)
} }
@ -521,6 +536,7 @@ class RedisRecorderActor(
} }
*/ */
/* temporarily disabling due to issue https://github.com/bigbluebutton/bigbluebutton/issues/19701
private def handleTranscriptUpdatedEvtMsg(msg: TranscriptUpdatedEvtMsg) { private def handleTranscriptUpdatedEvtMsg(msg: TranscriptUpdatedEvtMsg) {
val ev = new TranscriptUpdatedRecordEvent() val ev = new TranscriptUpdatedRecordEvent()
ev.setMeetingId(msg.header.meetingId) ev.setMeetingId(msg.header.meetingId)
@ -529,6 +545,7 @@ class RedisRecorderActor(
record(msg.header.meetingId, ev.toMap.asJava) record(msg.header.meetingId, ev.toMap.asJava)
} }
*/
private def handleStartExternalVideoEvtMsg(msg: StartExternalVideoEvtMsg) { private def handleStartExternalVideoEvtMsg(msg: StartExternalVideoEvtMsg) {
val ev = new StartExternalVideoRecordEvent() val ev = new StartExternalVideoRecordEvent()

View File

@ -51,7 +51,7 @@ object TestDataGen {
guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, pin = false, mobile = false, emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, pin = false, mobile = false,
locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242", locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242",
clientType = "unknown", pickExempted = false, userLeftFlag = UserLeftFlag(false, 0)) clientType = "unknown", userLeftFlag = UserLeftFlag(false, 0))
Users2x.add(liveMeeting.users2x, u) Users2x.add(liveMeeting.users2x, u)
u u
} }

View File

@ -51,6 +51,7 @@ postgres {
} }
numThreads = 1 numThreads = 1
maxConnections = 1 maxConnections = 1
queueSize = 20000
} }
@ -64,6 +65,7 @@ expire {
services { services {
bbbWebAPI = "https://192.168.23.33/bigbluebutton/api" bbbWebAPI = "https://192.168.23.33/bigbluebutton/api"
sharedSecret = "changeme" sharedSecret = "changeme"
checkSumAlgorithmForBreakouts = "sha256"
} }
eventBus { eventBus {

View File

@ -14,7 +14,7 @@ object Dependencies {
// Libraries // Libraries
val pekkoVersion = "1.0.1" val pekkoVersion = "1.0.1"
val pekkoHttpVersion = "1.0.0" val pekkoHttpVersion = "1.0.0"
val logback = "1.2.10" val logback = "1.2.13"
// Apache Commons // Apache Commons
val lang = "3.12.0" val lang = "3.12.0"

View File

@ -70,6 +70,9 @@ case class LockSettingsProps(
case class SystemProps( case class SystemProps(
html5InstanceId: Int, html5InstanceId: Int,
logoutUrl: String, logoutUrl: String,
customLogoURL: String,
bannerText: String,
bannerColor: String,
) )
case class GroupProps( case class GroupProps(

View File

@ -1,5 +1,12 @@
package org.bigbluebutton.common2.msgs package org.bigbluebutton.common2.msgs
object TranscriptionProviderErrorMsg { val NAME = "TranscriptionProviderErrorMsg" }
case class TranscriptionProviderErrorMsg(header: BbbClientMsgHeader, body: TranscriptionProviderErrorMsgBody) extends StandardMsg
case class TranscriptionProviderErrorMsgBody(
errorCode: String,
errorMessage: String,
)
// In messages // In messages
object UpdateTranscriptPubMsg { val NAME = "UpdateTranscriptPubMsg" } object UpdateTranscriptPubMsg { val NAME = "UpdateTranscriptPubMsg" }
case class UpdateTranscriptPubMsg(header: BbbClientMsgHeader, body: UpdateTranscriptPubMsgBody) extends StandardMsg case class UpdateTranscriptPubMsg(header: BbbClientMsgHeader, body: UpdateTranscriptPubMsgBody) extends StandardMsg
@ -14,6 +21,10 @@ case class UpdateTranscriptPubMsgBody(
) )
// Out messages // Out messages
object TranscriptionProviderErrorEvtMsg { val NAME = "TranscriptionProviderErrorEvtMsg" }
case class TranscriptionProviderErrorEvtMsg(header: BbbClientMsgHeader, body: TranscriptionProviderErrorEvtMsgBody) extends BbbCoreMsg
case class TranscriptionProviderErrorEvtMsgBody(errorCode: String, errorMessage: String)
object TranscriptUpdatedEvtMsg { val NAME = "TranscriptUpdatedEvtMsg" } object TranscriptUpdatedEvtMsg { val NAME = "TranscriptUpdatedEvtMsg" }
case class TranscriptUpdatedEvtMsg(header: BbbClientMsgHeader, body: TranscriptUpdatedEvtMsgBody) extends BbbCoreMsg case class TranscriptUpdatedEvtMsg(header: BbbClientMsgHeader, body: TranscriptUpdatedEvtMsgBody) extends BbbCoreMsg
case class TranscriptUpdatedEvtMsgBody(transcriptId: String, transcript: String, locale: String, result: Boolean) case class TranscriptUpdatedEvtMsgBody(transcriptId: String, transcript: String, locale: String, result: Boolean)

View File

@ -15,7 +15,7 @@ object GroupChatMessageType {
} }
case class GroupChatUser(id: String, name: String = "", role: String = "VIEWER") case class GroupChatUser(id: String, name: String = "", role: String = "VIEWER")
case class GroupChatMsgFromUser(correlationId: String, sender: GroupChatUser, chatEmphasizedText: Boolean = false, message: String) case class GroupChatMsgFromUser(correlationId: String, sender: GroupChatUser, message: String)
case class GroupChatMsgToUser(id: String, timestamp: Long, correlationId: String, sender: GroupChatUser, chatEmphasizedText: Boolean = false, message: String) case class GroupChatMsgToUser(id: String, timestamp: Long, correlationId: String, sender: GroupChatUser, chatEmphasizedText: Boolean = false, message: String)
case class GroupChatInfo(id: String, access: String, createdBy: GroupChatUser, users: Vector[GroupChatUser]) case class GroupChatInfo(id: String, access: String, createdBy: GroupChatUser, users: Vector[GroupChatUser])

View File

@ -107,7 +107,7 @@ case class PadTailEvtMsgBody(externalId: String, tail: String)
// client -> apps // client -> apps
object PadUpdatePubMsg { val NAME = "PadUpdatePubMsg" } object PadUpdatePubMsg { val NAME = "PadUpdatePubMsg" }
case class PadUpdatePubMsg(header: BbbClientMsgHeader, body: PadUpdatePubMsgBody) extends StandardMsg case class PadUpdatePubMsg(header: BbbClientMsgHeader, body: PadUpdatePubMsgBody) extends StandardMsg
case class PadUpdatePubMsgBody(externalId: String, text: String) case class PadUpdatePubMsgBody(externalId: String, text: String, transcript: Boolean)
// apps -> pads // apps -> pads
object PadUpdateCmdMsg { val NAME = "PadUpdateCmdMsg" } object PadUpdateCmdMsg { val NAME = "PadUpdateCmdMsg" }

View File

@ -5,12 +5,28 @@ package org.bigbluebutton.common2.msgs
/** /**
* Sent from graphql-actions to bbb-akka * Sent from graphql-actions to bbb-akka
*/ */
object DispatchPluginDataChannelMessageMsg { val NAME = "DispatchPluginDataChannelMessageMsg" } object PluginDataChannelDispatchMessageMsg { val NAME = "PluginDataChannelDispatchMessageMsg" }
case class DispatchPluginDataChannelMessageMsg(header: BbbClientMsgHeader, body: DispatchPluginDataChannelMessageMsgBody) extends StandardMsg case class PluginDataChannelDispatchMessageMsg(header: BbbClientMsgHeader, body: PluginDataChannelDispatchMessageMsgBody) extends StandardMsg
case class DispatchPluginDataChannelMessageMsgBody( case class PluginDataChannelDispatchMessageMsgBody(
pluginName: String, pluginName: String,
dataChannel: String, dataChannel: String,
payloadJson: String, payloadJson: String,
toRoles: List[String], toRoles: List[String],
toUserIds: List[String], toUserIds: List[String],
) )
object PluginDataChannelDeleteMessageMsg { val NAME = "PluginDataChannelDeleteMessageMsg" }
case class PluginDataChannelDeleteMessageMsg(header: BbbClientMsgHeader, body: PluginDataChannelDeleteMessageMsgBody) extends StandardMsg
case class PluginDataChannelDeleteMessageMsgBody(
pluginName: String,
dataChannel: String,
messageId: String
)
object PluginDataChannelResetMsg { val NAME = "PluginDataChannelResetMsg" }
case class PluginDataChannelResetMsg(header: BbbClientMsgHeader, body: PluginDataChannelResetMsgBody) extends StandardMsg
case class PluginDataChannelResetMsgBody(
pluginName: String,
dataChannel: String
)

View File

@ -17,7 +17,7 @@ case class MakePresentationDownloadReqMsgBody(presId: String, allPages: Boolean,
object NewPresFileAvailableMsg { val NAME = "NewPresFileAvailableMsg" } object NewPresFileAvailableMsg { val NAME = "NewPresFileAvailableMsg" }
case class NewPresFileAvailableMsg(header: BbbClientMsgHeader, body: NewPresFileAvailableMsgBody) extends StandardMsg case class NewPresFileAvailableMsg(header: BbbClientMsgHeader, body: NewPresFileAvailableMsgBody) extends StandardMsg
case class NewPresFileAvailableMsgBody(annotatedFileURI: String, originalFileURI: String, convertedFileURI: String, case class NewPresFileAvailableMsgBody(annotatedFileURI: String, originalFileURI: String, convertedFileURI: String,
presId: String, fileStateType: String) presId: String, fileStateType: String, fileName: String)
object PresAnnStatusMsg { val NAME = "PresAnnStatusMsg" } object PresAnnStatusMsg { val NAME = "PresAnnStatusMsg" }
case class PresAnnStatusMsg(header: BbbClientMsgHeader, body: PresAnnStatusMsgBody) extends StandardMsg case class PresAnnStatusMsg(header: BbbClientMsgHeader, body: PresAnnStatusMsgBody) extends StandardMsg

View File

@ -232,6 +232,26 @@ object DeletedRecordingSysMsg { val NAME = "DeletedRecordingSysMsg" }
case class DeletedRecordingSysMsg(header: BbbCoreBaseHeader, body: DeletedRecordingSysMsgBody) extends BbbCoreMsg case class DeletedRecordingSysMsg(header: BbbCoreBaseHeader, body: DeletedRecordingSysMsgBody) extends BbbCoreMsg
case class DeletedRecordingSysMsgBody(recordId: String) case class DeletedRecordingSysMsgBody(recordId: String)
/**
* Sent from akka-apps to graphql-middleware
*/
object CheckGraphqlMiddlewareAlivePingSysMsg { val NAME = "CheckGraphqlMiddlewareAlivePingSysMsg" }
case class CheckGraphqlMiddlewareAlivePingSysMsg(
header: BbbCoreHeaderWithMeetingId,
body: CheckGraphqlMiddlewareAlivePingSysMsgBody
) extends BbbCoreMsg
case class CheckGraphqlMiddlewareAlivePingSysMsgBody(middlewareUID: String)
/**
* Sent from graphql-middleware to akka-apps
*/
object CheckGraphqlMiddlewareAlivePongSysMsg { val NAME = "CheckGraphqlMiddlewareAlivePongSysMsg" }
case class CheckGraphqlMiddlewareAlivePongSysMsg(
header: BbbCoreBaseHeader,
body: CheckGraphqlMiddlewareAlivePongSysMsgBody
) extends BbbCoreMsg
case class CheckGraphqlMiddlewareAlivePongSysMsgBody(middlewareUID: String)
/** /**
* Sent from akka-apps to graphql-middleware * Sent from akka-apps to graphql-middleware
*/ */
@ -251,21 +271,21 @@ case class UserGraphqlReconnectionForcedEvtMsg(
header: BbbCoreBaseHeader, header: BbbCoreBaseHeader,
body: UserGraphqlReconnectionForcedEvtMsgBody body: UserGraphqlReconnectionForcedEvtMsgBody
) extends BbbCoreMsg ) extends BbbCoreMsg
case class UserGraphqlReconnectionForcedEvtMsgBody(sessionToken: String, browserConnectionId: String) case class UserGraphqlReconnectionForcedEvtMsgBody(middlewareUID: String, sessionToken: String, browserConnectionId: String)
object UserGraphqlConnectionEstablishedSysMsg { val NAME = "UserGraphqlConnectionEstablishedSysMsg" } object UserGraphqlConnectionEstablishedSysMsg { val NAME = "UserGraphqlConnectionEstablishedSysMsg" }
case class UserGraphqlConnectionEstablishedSysMsg( case class UserGraphqlConnectionEstablishedSysMsg(
header: BbbCoreBaseHeader, header: BbbCoreBaseHeader,
body: UserGraphqlConnectionEstablishedSysMsgBody body: UserGraphqlConnectionEstablishedSysMsgBody
) extends BbbCoreMsg ) extends BbbCoreMsg
case class UserGraphqlConnectionEstablishedSysMsgBody(sessionToken: String, browserConnectionId: String) case class UserGraphqlConnectionEstablishedSysMsgBody(middlewareUID: String, sessionToken: String, browserConnectionId: String)
object UserGraphqlConnectionClosedSysMsg { val NAME = "UserGraphqlConnectionClosedSysMsg" } object UserGraphqlConnectionClosedSysMsg { val NAME = "UserGraphqlConnectionClosedSysMsg" }
case class UserGraphqlConnectionClosedSysMsg( case class UserGraphqlConnectionClosedSysMsg(
header: BbbCoreBaseHeader, header: BbbCoreBaseHeader,
body: UserGraphqlConnectionClosedSysMsgBody body: UserGraphqlConnectionClosedSysMsgBody
) extends BbbCoreMsg ) extends BbbCoreMsg
case class UserGraphqlConnectionClosedSysMsgBody(sessionToken: String, browserConnectionId: String) case class UserGraphqlConnectionClosedSysMsgBody(middlewareUID: String, sessionToken: String, browserConnectionId: String)
/** /**
* Sent from akka-apps to bbb-web to inform a summary of the meeting activities * Sent from akka-apps to bbb-web to inform a summary of the meeting activities

View File

@ -19,7 +19,7 @@ case class StartTimerReqMsgBody()
object StopTimerReqMsg { val NAME = "StopTimerReqMsg" } object StopTimerReqMsg { val NAME = "StopTimerReqMsg" }
case class StopTimerReqMsg(header: BbbClientMsgHeader, body: StopTimerReqMsgBody) extends StandardMsg case class StopTimerReqMsg(header: BbbClientMsgHeader, body: StopTimerReqMsgBody) extends StandardMsg
case class StopTimerReqMsgBody(accumulated: Int) case class StopTimerReqMsgBody()
object SwitchTimerReqMsg { val NAME = "SwitchTimerReqMsg" } object SwitchTimerReqMsg { val NAME = "SwitchTimerReqMsg" }
case class SwitchTimerReqMsg(header: BbbClientMsgHeader, body: SwitchTimerReqMsgBody) extends StandardMsg case class SwitchTimerReqMsg(header: BbbClientMsgHeader, body: SwitchTimerReqMsgBody) extends StandardMsg

View File

@ -293,6 +293,20 @@ object ChangeUserMobileFlagReqMsg { val NAME = "ChangeUserMobileFlagReqMsg" }
case class ChangeUserMobileFlagReqMsg(header: BbbClientMsgHeader, body: ChangeUserMobileFlagReqMsgBody) extends StandardMsg case class ChangeUserMobileFlagReqMsg(header: BbbClientMsgHeader, body: ChangeUserMobileFlagReqMsgBody) extends StandardMsg
case class ChangeUserMobileFlagReqMsgBody(userId: String, mobile: Boolean) case class ChangeUserMobileFlagReqMsgBody(userId: String, mobile: Boolean)
/**
* Sent from client to inform the connection is alive.
*/
object UserConnectionAliveReqMsg { val NAME = "UserConnectionAliveReqMsg" }
case class UserConnectionAliveReqMsg(header: BbbClientMsgHeader, body: UserConnectionAliveReqMsgBody) extends StandardMsg
case class UserConnectionAliveReqMsgBody(userId: String)
/**
* Sent from client to inform the RTT (time it took to send the Alive and receive confirmation).
*/
object UserConnectionUpdateRttReqMsg { val NAME = "UserConnectionUpdateRttReqMsg" }
case class UserConnectionUpdateRttReqMsg(header: BbbClientMsgHeader, body: UserConnectionUpdateRttReqMsgBody) extends StandardMsg
case class UserConnectionUpdateRttReqMsgBody(userId: String, networkRttInMs: Double)
/** /**
* Sent to all clients about a user mobile flag. * Sent to all clients about a user mobile flag.
*/ */
@ -511,20 +525,6 @@ object UserActivitySignCmdMsg { val NAME = "UserActivitySignCmdMsg" }
case class UserActivitySignCmdMsg(header: BbbClientMsgHeader, body: UserActivitySignCmdMsgBody) extends StandardMsg case class UserActivitySignCmdMsg(header: BbbClientMsgHeader, body: UserActivitySignCmdMsgBody) extends StandardMsg
case class UserActivitySignCmdMsgBody(userId: String) case class UserActivitySignCmdMsgBody(userId: String)
/**
* Sent from client to randomly select a viewer
*/
object SelectRandomViewerReqMsg { val NAME = "SelectRandomViewerReqMsg" }
case class SelectRandomViewerReqMsg(header: BbbClientMsgHeader, body: SelectRandomViewerReqMsgBody) extends StandardMsg
case class SelectRandomViewerReqMsgBody(requestedBy: String)
/**
* Response to request for a random viewer
*/
object SelectRandomViewerRespMsg { val NAME = "SelectRandomViewerRespMsg" }
case class SelectRandomViewerRespMsg(header: BbbClientMsgHeader, body: SelectRandomViewerRespMsgBody) extends StandardMsg
case class SelectRandomViewerRespMsgBody(requestedBy: String, userIds: Vector[String], choice: String)
object SetUserSpeechLocaleReqMsg { val NAME = "SetUserSpeechLocaleReqMsg" } object SetUserSpeechLocaleReqMsg { val NAME = "SetUserSpeechLocaleReqMsg" }
case class SetUserSpeechLocaleReqMsg(header: BbbClientMsgHeader, body: SetUserSpeechLocaleReqMsgBody) extends StandardMsg case class SetUserSpeechLocaleReqMsg(header: BbbClientMsgHeader, body: SetUserSpeechLocaleReqMsgBody) extends StandardMsg
case class SetUserSpeechLocaleReqMsgBody(locale: String, provider: String) case class SetUserSpeechLocaleReqMsgBody(locale: String, provider: String)
@ -532,3 +532,11 @@ case class SetUserSpeechLocaleReqMsgBody(locale: String, provider: String)
object UserSpeechLocaleChangedEvtMsg { val NAME = "UserSpeechLocaleChangedEvtMsg" } object UserSpeechLocaleChangedEvtMsg { val NAME = "UserSpeechLocaleChangedEvtMsg" }
case class UserSpeechLocaleChangedEvtMsg(header: BbbClientMsgHeader, body: UserSpeechLocaleChangedEvtMsgBody) extends BbbCoreMsg case class UserSpeechLocaleChangedEvtMsg(header: BbbClientMsgHeader, body: UserSpeechLocaleChangedEvtMsgBody) extends BbbCoreMsg
case class UserSpeechLocaleChangedEvtMsgBody(locale: String, provider: String) case class UserSpeechLocaleChangedEvtMsgBody(locale: String, provider: String)
object SetUserSpeechOptionsReqMsg { val NAME = "SetUserSpeechOptionsReqMsg" }
case class SetUserSpeechOptionsReqMsg(header: BbbClientMsgHeader, body: SetUserSpeechOptionsReqMsgBody) extends StandardMsg
case class SetUserSpeechOptionsReqMsgBody(partialUtterances: Boolean, minUtteranceLength: Int)
object UserSpeechOptionsChangedEvtMsg { val NAME = "UserSpeechOptionsChangedEvtMsg" }
case class UserSpeechOptionsChangedEvtMsg(header: BbbClientMsgHeader, body: UserSpeechOptionsChangedEvtMsgBody) extends BbbCoreMsg
case class UserSpeechOptionsChangedEvtMsgBody(partialUtterances: Boolean, minUtteranceLength: Int)

View File

@ -22,6 +22,7 @@ case class ExportJob(
jobId: String, jobId: String,
jobType: String, jobType: String,
filename: String, filename: String,
serverSideFilename: String,
presId: String, presId: String,
presLocation: String, presLocation: String,
allPages: Boolean, allPages: Boolean,

View File

@ -103,10 +103,10 @@ homepage := Some(url("http://www.bigbluebutton.org"))
libraryDependencies ++= Seq( libraryDependencies ++= Seq(
"javax.validation" % "validation-api" % "2.0.1.Final", "javax.validation" % "validation-api" % "2.0.1.Final",
"org.springframework.boot" % "spring-boot-starter-validation" % "2.7.12", "org.springframework.boot" % "spring-boot-starter-validation" % "2.7.17",
"org.springframework.data" % "spring-data-commons" % "2.7.6", "org.springframework.data" % "spring-data-commons" % "2.7.6",
"org.apache.httpcomponents" % "httpclient" % "4.5.13", "org.apache.httpcomponents" % "httpclient" % "4.5.13",
"org.postgresql" % "postgresql" % "42.4.3", "org.postgresql" % "postgresql" % "42.7.2",
"org.hibernate" % "hibernate-core" % "5.6.1.Final", "org.hibernate" % "hibernate-core" % "5.6.1.Final",
"org.flywaydb" % "flyway-core" % "7.8.2", "org.flywaydb" % "flyway-core" % "7.8.2",
"com.zaxxer" % "HikariCP" % "4.0.3", "com.zaxxer" % "HikariCP" % "4.0.3",

View File

@ -75,6 +75,7 @@ public class MeetingService implements MessageListener {
*/ */
private final ConcurrentMap<String, Meeting> meetings; private final ConcurrentMap<String, Meeting> meetings;
private final ConcurrentMap<String, UserSession> sessions; private final ConcurrentMap<String, UserSession> sessions;
private final ConcurrentMap<String, UserSessionBasicData> removedSessions;
private RecordingService recordingService; private RecordingService recordingService;
private LearningDashboardService learningDashboardService; private LearningDashboardService learningDashboardService;
@ -88,6 +89,7 @@ public class MeetingService implements MessageListener {
private long usersTimeout; private long usersTimeout;
private long waitingGuestUsersTimeout; private long waitingGuestUsersTimeout;
private int sessionsCleanupDelayInMinutes;
private long enteredUsersTimeout; private long enteredUsersTimeout;
private ParamsProcessorUtil paramsProcessorUtil; private ParamsProcessorUtil paramsProcessorUtil;
@ -100,6 +102,7 @@ public class MeetingService implements MessageListener {
public MeetingService() { public MeetingService() {
meetings = new ConcurrentHashMap<String, Meeting>(8, 0.9f, 1); meetings = new ConcurrentHashMap<String, Meeting>(8, 0.9f, 1);
sessions = new ConcurrentHashMap<String, UserSession>(8, 0.9f, 1); sessions = new ConcurrentHashMap<String, UserSession>(8, 0.9f, 1);
removedSessions = new ConcurrentHashMap<String, UserSessionBasicData>(8, 0.9f, 1);
uploadAuthzTokens = new HashMap<String, PresentationUploadToken>(); uploadAuthzTokens = new HashMap<String, PresentationUploadToken>();
} }
@ -149,12 +152,16 @@ public class MeetingService implements MessageListener {
return null; return null;
} }
public UserSession getUserSessionWithAuthToken(String token) { public UserSession getUserSessionWithSessionToken(String token) {
return sessions.get(token); return sessions.get(token);
} }
public UserSessionBasicData getRemovedUserSessionWithSessionToken(String sessionToken) {
return removedSessions.get(sessionToken);
}
public Boolean getAllowRequestsWithoutSession(String token) { public Boolean getAllowRequestsWithoutSession(String token) {
UserSession us = getUserSessionWithAuthToken(token); UserSession us = getUserSessionWithSessionToken(token);
if (us == null) { if (us == null) {
return false; return false;
} else { } else {
@ -164,12 +171,22 @@ public class MeetingService implements MessageListener {
} }
} }
public UserSession removeUserSessionWithAuthToken(String token) { public void removeUserSessionWithSessionToken(String token) {
UserSession user = sessions.remove(token); log.debug("Removing token={}", token);
if (user != null) { UserSession us = getUserSessionWithSessionToken(token);
log.debug("Found user {} token={} to meeting {}", user.fullname, token, user.meetingID); if (us != null) {
log.debug("Found user {} token={} to meeting {}", us.fullname, token, us.meetingID);
UserSessionBasicData removedUser = new UserSessionBasicData();
removedUser.meetingId = us.meetingID;
removedUser.userId = us.internalUserId;
removedUser.sessionToken = us.authToken;
removedUser.role = us.role;
removedSessions.put(token, removedUser);
sessions.remove(token);
} else {
log.debug("Not found token={}", token);
} }
return user;
} }
/** /**
@ -295,17 +312,41 @@ public class MeetingService implements MessageListener {
notifier.sendUploadFileTooLargeMessage(presUploadToken, uploadedFileSize, maxUploadFileSize); notifier.sendUploadFileTooLargeMessage(presUploadToken, uploadedFileSize, maxUploadFileSize);
} }
private void removeUserSessions(String meetingId) { private void removeUserSessionsFromMeeting(String meetingId) {
Iterator<Map.Entry<String, UserSession>> iterator = sessions.entrySet().iterator(); for (String token : sessions.keySet()) {
while (iterator.hasNext()) { UserSession userSession = sessions.get(token);
Map.Entry<String, UserSession> entry = iterator.next();
UserSession userSession = entry.getValue();
if (userSession.meetingID.equals(meetingId)) { if (userSession.meetingID.equals(meetingId)) {
System.out.println(token + " = " + userSession.authToken);
removeUserSessionWithSessionToken(token);
}
}
scheduleRemovedSessionsCleanUp(meetingId);
}
private void scheduleRemovedSessionsCleanUp(String meetingId) {
Calendar cleanUpDelayCalendar = Calendar.getInstance();
cleanUpDelayCalendar.add(Calendar.MINUTE, sessionsCleanupDelayInMinutes);
log.debug("Sessions for meeting={} will be removed within {} minutes.", meetingId, sessionsCleanupDelayInMinutes);
new java.util.Timer().schedule(
new java.util.TimerTask() {
@Override
public void run() {
Iterator<Map.Entry<String, UserSessionBasicData>> iterator = removedSessions.entrySet().iterator();
while (iterator.hasNext()) {
Map.Entry<String, UserSessionBasicData> entry = iterator.next();
UserSessionBasicData removedUserSession = entry.getValue();
if (removedUserSession.meetingId.equals(meetingId)) {
log.debug("Removed user {} session for meeting {}.",removedUserSession.userId, removedUserSession.meetingId);
iterator.remove(); iterator.remove();
} }
} }
} }
}, cleanUpDelayCalendar.getTime()
);
}
private void destroyMeeting(String meetingId) { private void destroyMeeting(String meetingId) {
gw.destroyMeeting(new DestroyMeetingMessage(meetingId)); gw.destroyMeeting(new DestroyMeetingMessage(meetingId));
@ -411,8 +452,8 @@ public class MeetingService implements MessageListener {
m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(), m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(),
m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(), m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(),
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(), m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(),
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(), m.getLogoutUrl(), m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(), m.getLogoutUrl(), m.getCustomLogoURL(),
m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(), m.getBannerText(), m.getBannerColor(), m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl(), m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl(),
m.getOverrideClientSettings()); m.getOverrideClientSettings());
} }
@ -703,7 +744,7 @@ public class MeetingService implements MessageListener {
} }
destroyMeeting(m.getInternalId()); destroyMeeting(m.getInternalId());
meetings.remove(m.getInternalId()); meetings.remove(m.getInternalId());
removeUserSessions(m.getInternalId()); removeUserSessionsFromMeeting(m.getInternalId());
Map<String, Object> logData = new HashMap<>(); Map<String, Object> logData = new HashMap<>();
logData.put("meetingId", m.getInternalId()); logData.put("meetingId", m.getInternalId());
@ -1111,7 +1152,7 @@ public class MeetingService implements MessageListener {
user.setRole(message.role); user.setRole(message.role);
String sessionToken = getTokenByUserId(user.getInternalUserId()); String sessionToken = getTokenByUserId(user.getInternalUserId());
if (sessionToken != null) { if (sessionToken != null) {
UserSession userSession = getUserSessionWithAuthToken(sessionToken); UserSession userSession = getUserSessionWithSessionToken(sessionToken);
userSession.role = message.role; userSession.role = message.role;
sessions.replace(sessionToken, userSession); sessions.replace(sessionToken, userSession);
} }
@ -1184,6 +1225,10 @@ public class MeetingService implements MessageListener {
processGuestStatusChangedEventMsg((GuestStatusChangedEventMsg) message); processGuestStatusChangedEventMsg((GuestStatusChangedEventMsg) message);
} else if (message instanceof GuestPolicyChanged) { } else if (message instanceof GuestPolicyChanged) {
processGuestPolicyChanged((GuestPolicyChanged) message); processGuestPolicyChanged((GuestPolicyChanged) message);
} else if (message instanceof LockSettingsChanged) {
processLockSettingsChanged((LockSettingsChanged) message);
} else if (message instanceof WebcamsOnlyForModeratorChanged) {
processWebcamsOnlyForModeratorChanged((WebcamsOnlyForModeratorChanged) message);
} else if (message instanceof GuestLobbyMessageChanged) { } else if (message instanceof GuestLobbyMessageChanged) {
processGuestLobbyMessageChanged((GuestLobbyMessageChanged) message); processGuestLobbyMessageChanged((GuestLobbyMessageChanged) message);
} else if (message instanceof PrivateGuestLobbyMessageChanged) { } else if (message instanceof PrivateGuestLobbyMessageChanged) {
@ -1210,6 +1255,32 @@ public class MeetingService implements MessageListener {
} }
} }
public void processLockSettingsChanged(LockSettingsChanged msg) {
Meeting m = getMeeting(msg.meetingId);
if (m != null) {
m.setLockSettings(
new LockSettingsParams(
msg.disableCam,
msg.disableMic,
msg.disablePrivateChat,
msg.disablePublicChat,
msg.disableNotes,
msg.hideUserList,
msg.lockOnJoin,
msg.lockOnJoinConfigurable,
msg.hideViewersCursor,
msg.hideViewersAnnotation)
);
}
}
public void processWebcamsOnlyForModeratorChanged(WebcamsOnlyForModeratorChanged msg) {
Meeting m = getMeeting(msg.meetingId);
if (m != null) {
m.setWebcamsOnlyForModerator(msg.webcamsOnlyForModerator);
}
}
public void processPositionInWaitingQueueUpdated(PositionInWaitingQueueUpdated msg) { public void processPositionInWaitingQueueUpdated(PositionInWaitingQueueUpdated msg) {
Meeting m = getMeeting(msg.meetingId); Meeting m = getMeeting(msg.meetingId);
HashMap<String,String> guestUsers = msg.guests; HashMap<String,String> guestUsers = msg.guests;
@ -1333,6 +1404,10 @@ public class MeetingService implements MessageListener {
waitingGuestUsersTimeout = value; waitingGuestUsersTimeout = value;
} }
public void setSessionsCleanupDelayInMinutes(int value) {
sessionsCleanupDelayInMinutes = value;
}
public void setEnteredUsersTimeout(long value) { public void setEnteredUsersTimeout(long value) {
enteredUsersTimeout = value; enteredUsersTimeout = value;
} }

View File

@ -70,6 +70,8 @@ public class ParamsProcessorUtil {
private String defaultServerUrl; private String defaultServerUrl;
private int defaultNumDigitsForTelVoice; private int defaultNumDigitsForTelVoice;
private String defaultHTML5ClientUrl; private String defaultHTML5ClientUrl;
private String graphqlWebsocketUrl;
private String defaultGuestWaitURL; private String defaultGuestWaitURL;
private Boolean allowRequestsWithoutSession = false; private Boolean allowRequestsWithoutSession = false;
private Integer defaultHttpSessionTimeout = 14400; private Integer defaultHttpSessionTimeout = 14400;
@ -864,6 +866,10 @@ public class ParamsProcessorUtil {
return defaultHTML5ClientUrl; return defaultHTML5ClientUrl;
} }
public String getGraphqlWebsocketUrl() {
return graphqlWebsocketUrl;
}
public String getDefaultGuestWaitURL() { public String getDefaultGuestWaitURL() {
return defaultGuestWaitURL; return defaultGuestWaitURL;
} }
@ -1217,6 +1223,10 @@ public class ParamsProcessorUtil {
this.defaultHTML5ClientUrl = defaultHTML5ClientUrl; this.defaultHTML5ClientUrl = defaultHTML5ClientUrl;
} }
public void setGraphqlWebsocketUrl(String graphqlWebsocketUrl) {
this.graphqlWebsocketUrl = graphqlWebsocketUrl.replace("https://","wss://");
}
public void setDefaultGuestWaitURL(String url) { public void setDefaultGuestWaitURL(String url) {
this.defaultGuestWaitURL = url; this.defaultGuestWaitURL = url;
} }

View File

@ -110,7 +110,7 @@ public class Meeting {
private Integer endWhenNoModeratorDelayInMinutes = 1; private Integer endWhenNoModeratorDelayInMinutes = 1;
public final BreakoutRoomsParams breakoutRoomsParams; public final BreakoutRoomsParams breakoutRoomsParams;
public final LockSettingsParams lockSettingsParams; public LockSettingsParams lockSettingsParams;
public final Integer maxUserConcurrentAccesses; public final Integer maxUserConcurrentAccesses;
@ -475,6 +475,14 @@ public class Meeting {
return guestPolicy; return guestPolicy;
} }
public void setLockSettings(LockSettingsParams lockSettingsParams) {
this.lockSettingsParams = lockSettingsParams;
}
public void setWebcamsOnlyForModerator(Boolean webcamsOnlyForModerator) {
this.webcamsOnlyForModerator = webcamsOnlyForModerator;
}
public void setGuestLobbyMessage(String message) { public void setGuestLobbyMessage(String message) {
guestLobbyMessage = message; guestLobbyMessage = message;
} }

View File

@ -138,6 +138,10 @@ public class User {
return "MODERATOR".equalsIgnoreCase(this.role); return "MODERATOR".equalsIgnoreCase(this.role);
} }
public boolean isLocked() {
return this.locked;
}
public void setStatus(String key, String value){ public void setStatus(String key, String value){
this.status.put(key, value); this.status.put(key, value);
} }

View File

@ -0,0 +1,35 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.api.domain;
public class UserSessionBasicData {
public String sessionToken = null;
public String userId = null;
public String meetingId = null;
public String role = null;
public Boolean isModerator() {
return "MODERATOR".equalsIgnoreCase(this.role);
}
public String toString() {
return meetingId + " " + userId + " " + sessionToken;
}
}

View File

@ -0,0 +1,40 @@
package org.bigbluebutton.api.messaging.messages;
public class LockSettingsChanged implements IMessage {
public final String meetingId;
public final Boolean disableCam;
public final Boolean disableMic;
public final Boolean disablePrivateChat;
public final Boolean disablePublicChat;
public final Boolean disableNotes;
public final Boolean hideUserList;
public final Boolean lockOnJoin;
public final Boolean lockOnJoinConfigurable;
public final Boolean hideViewersCursor;
public final Boolean hideViewersAnnotation;
public LockSettingsChanged(String meetingId,
Boolean disableCam,
Boolean disableMic,
Boolean disablePrivateChat,
Boolean disablePublicChat,
Boolean disableNotes,
Boolean hideUserList,
Boolean lockOnJoin,
Boolean lockOnJoinConfigurable,
Boolean hideViewersCursor,
Boolean hideViewersAnnotation) {
this.meetingId = meetingId;
this.disableCam = disableCam;
this.disableMic = disableMic;
this.disablePrivateChat = disablePrivateChat;
this.disablePublicChat = disablePublicChat;
this.disableNotes = disableNotes;
this.hideUserList = hideUserList;
this.lockOnJoin = lockOnJoin;
this.lockOnJoinConfigurable = lockOnJoinConfigurable;
this.hideViewersCursor = hideViewersCursor;
this.hideViewersAnnotation = hideViewersAnnotation;
}
}

View File

@ -0,0 +1,11 @@
package org.bigbluebutton.api.messaging.messages;
public class WebcamsOnlyForModeratorChanged implements IMessage {
public final String meetingId;
public final Boolean webcamsOnlyForModerator;
public WebcamsOnlyForModeratorChanged(String meetingId, Boolean webcamsOnlyForModerator) {
this.meetingId = meetingId;
this.webcamsOnlyForModerator = webcamsOnlyForModerator;
}
}

View File

@ -22,7 +22,7 @@ public class GuestPolicyValidator implements ConstraintValidator<GuestPolicyCons
} }
MeetingService meetingService = ServiceUtils.getMeetingService(); MeetingService meetingService = ServiceUtils.getMeetingService();
UserSession userSession = meetingService.getUserSessionWithAuthToken(sessionToken); UserSession userSession = meetingService.getUserSessionWithSessionToken(sessionToken);
if(userSession == null || !userSession.guestStatus.equals(GuestPolicy.ALLOW)) { if(userSession == null || !userSession.guestStatus.equals(GuestPolicy.ALLOW)) {
return false; return false;

View File

@ -19,7 +19,7 @@ public class UserSessionValidator implements ConstraintValidator<UserSessionCons
return false; return false;
} }
UserSession userSession = ServiceUtils.getMeetingService().getUserSessionWithAuthToken(sessionToken); UserSession userSession = ServiceUtils.getMeetingService().getUserSessionWithSessionToken(sessionToken);
if(userSession == null) { if(userSession == null) {
return false; return false;

View File

@ -22,7 +22,7 @@ public class SessionService {
private void getUserSessionWithToken() { private void getUserSessionWithToken() {
if(sessionToken != null) { if(sessionToken != null) {
userSession = meetingService.getUserSessionWithAuthToken(sessionToken); userSession = meetingService.getUserSessionWithSessionToken(sessionToken);
} }
} }

View File

@ -14,6 +14,9 @@ import javax.validation.Validation;
import javax.validation.Validator; import javax.validation.Validator;
import javax.validation.ValidatorFactory; import javax.validation.ValidatorFactory;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URISyntaxException;
import java.net.URL;
import java.net.URLEncoder; import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.*; import java.util.*;
@ -76,6 +79,11 @@ public class ValidationService {
if(request == null) { if(request == null) {
violations.put("validationError", "Request not recognized"); violations.put("validationError", "Request not recognized");
} else if(params.containsKey("presentationUploadExternalUrl")) {
String urlToValidate = params.get("presentationUploadExternalUrl")[0];
if(!this.isValidURL(urlToValidate)) {
violations.put("validationError", "Param 'presentationUploadExternalUrl' is not a valid URL");
}
} else { } else {
request.populateFromParamsMap(params); request.populateFromParamsMap(params);
violations = performValidation(request); violations = performValidation(request);
@ -84,6 +92,15 @@ public class ValidationService {
return violations; return violations;
} }
boolean isValidURL(String url) {
try {
new URL(url).toURI();
return true;
} catch (MalformedURLException | URISyntaxException e) {
return false;
}
}
private Request initializeRequest(ApiCall apiCall, Map<String, String[]> params, String queryString) { private Request initializeRequest(ApiCall apiCall, Map<String, String[]> params, String queryString) {
Request request = null; Request request = null;
Checksum checksum; Checksum checksum;

Some files were not shown because too many files have changed in this diff Show More