Merge pull request #19927 from antobinary/merge-dev-alpha5
chore: Merge 3.0.0-alpha.5 into develop
This commit is contained in:
commit
99dc23a6f8
2
.gitignore
vendored
2
.gitignore
vendored
@ -23,3 +23,5 @@ cache/*
|
||||
artifacts/*
|
||||
bbb-presentation-video.zip
|
||||
bbb-presentation-video
|
||||
bbb-graphql-actions-adapter-server/
|
||||
bigbluebutton-html5/public/locales/index.json
|
||||
|
@ -75,5 +75,6 @@ daemonUser in Linux := user
|
||||
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 reStart ++= Seq("-Dconfig.file=/etc/bigbluebutton/bbb-apps-akka.conf", "-Dlogback.configurationFile=conf/logback.xml")
|
||||
|
||||
debianPackageDependencies in Debian ++= Seq("java17-runtime-headless", "bash")
|
||||
|
@ -16,7 +16,7 @@ object Dependencies {
|
||||
val pekkoHttpVersion = "1.0.0"
|
||||
val gson = "2.8.9"
|
||||
val jackson = "2.13.5"
|
||||
val logback = "1.2.11"
|
||||
val logback = "1.2.13"
|
||||
val quicklens = "1.7.5"
|
||||
val spray = "1.3.6"
|
||||
|
||||
|
@ -10,7 +10,7 @@ import org.bigbluebutton.core.bus._
|
||||
import org.bigbluebutton.core.pubsub.senders.ReceivedJsonMsgHandlerActor
|
||||
import org.bigbluebutton.core2.AnalyticsActor
|
||||
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.service.{HealthzService, MeetingInfoActor, MeetingInfoService}
|
||||
|
||||
@ -67,9 +67,9 @@ object Boot extends App with SystemConfiguration {
|
||||
"LearningDashboardActor"
|
||||
)
|
||||
|
||||
val graphqlActionsActor = system.actorOf(
|
||||
GraphqlActionsActor.props(system),
|
||||
"GraphqlActionsActor"
|
||||
val graphqlConnectionsActor = system.actorOf(
|
||||
GraphqlConnectionsActor.props(system, eventBus, outGW),
|
||||
"GraphqlConnectionsActor"
|
||||
)
|
||||
|
||||
ClientSettings.loadClientSettingsFromFile()
|
||||
@ -89,8 +89,8 @@ object Boot extends App with SystemConfiguration {
|
||||
outBus2.subscribe(learningDashboardActor, outBbbMsgMsgChannel)
|
||||
bbbMsgBus.subscribe(learningDashboardActor, analyticsChannel)
|
||||
|
||||
eventBus.subscribe(graphqlActionsActor, meetingManagerChannel)
|
||||
bbbMsgBus.subscribe(graphqlActionsActor, analyticsChannel)
|
||||
eventBus.subscribe(graphqlConnectionsActor, meetingManagerChannel)
|
||||
bbbMsgBus.subscribe(graphqlConnectionsActor, analyticsChannel)
|
||||
|
||||
val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW, healthzService), "bigbluebutton-actor")
|
||||
eventBus.subscribe(bbbActor, meetingManagerChannel)
|
||||
|
@ -38,6 +38,9 @@ object ClientSettings extends SystemConfiguration {
|
||||
Map[String, Object]()
|
||||
}
|
||||
)
|
||||
|
||||
//Remove `:private` once it's used only by Meteor internal configs
|
||||
clientSettingsFromFile -= "private"
|
||||
}
|
||||
|
||||
def getClientSettingsWithOverride(clientSettingsOverrideJson: String): Map[String, Object] = {
|
||||
@ -52,6 +55,33 @@ object ClientSettings extends SystemConfiguration {
|
||||
} 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] = {
|
||||
val keys = path.split("\\.")
|
||||
|
||||
@ -90,13 +120,37 @@ object ClientSettings extends SystemConfiguration {
|
||||
for {
|
||||
dataChannel <- dataChannels
|
||||
} yield {
|
||||
if (dataChannel.contains("name") && dataChannel.contains("writePermission")) {
|
||||
if (dataChannel.contains("name")) {
|
||||
val channelName = dataChannel("name").toString
|
||||
val writePermission = dataChannel("writePermission")
|
||||
writePermission match {
|
||||
case wPerm: List[String] => pluginDataChannels += (channelName -> DataChannel(channelName, wPerm))
|
||||
case _ => logger.warn(s"Invalid writePermission for channel $channelName in plugin $pluginName")
|
||||
val writePermission = {
|
||||
if (dataChannel.contains("writePermission")) {
|
||||
dataChannel("writePermission") match {
|
||||
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")
|
||||
@ -112,7 +166,7 @@ object ClientSettings extends SystemConfiguration {
|
||||
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])
|
||||
|
||||
}
|
||||
|
@ -13,6 +13,7 @@ trait SystemConfiguration {
|
||||
lazy val bbbWebPort = Try(config.getInt("services.bbbWebPort")).getOrElse(8888)
|
||||
lazy val bbbWebAPI = Try(config.getString("services.bbbWebAPI")).getOrElse("localhost")
|
||||
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 bbbWebViewerPassword = Try(config.getString("services.viewerPassword")).getOrElse("changeme")
|
||||
lazy val keysExpiresInSec = Try(config.getInt("redis.keyExpiry")).getOrElse(14 * 86400) // 14 days
|
||||
|
@ -14,6 +14,7 @@ import org.bigbluebutton.SystemConfiguration
|
||||
import java.util.concurrent.TimeUnit
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.db.{ DatabaseConnection, MeetingDAO }
|
||||
import org.bigbluebutton.core.domain.MeetingEndReason
|
||||
import org.bigbluebutton.core.running.RunningMeeting
|
||||
import org.bigbluebutton.core.util.ColorPicker
|
||||
import org.bigbluebutton.core2.RunningMeetings
|
||||
@ -57,6 +58,9 @@ class BigBlueButtonActor(
|
||||
override def preStart() {
|
||||
bbbMsgBus.subscribe(self, meetingManagerChannel)
|
||||
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() {
|
||||
@ -83,6 +87,7 @@ class BigBlueButtonActor(
|
||||
case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m)
|
||||
case _: UserGraphqlConnectionEstablishedSysMsg => //Ignore
|
||||
case _: UserGraphqlConnectionClosedSysMsg => //Ignore
|
||||
case _: CheckGraphqlMiddlewareAlivePongSysMsg => //Ignore
|
||||
case _ => log.warning("Cannot handle " + msg.envelope.name)
|
||||
}
|
||||
}
|
||||
@ -189,9 +194,10 @@ class BigBlueButtonActor(
|
||||
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"
|
||||
// UserDAO.deleteAllFromMeeting(msg.meetingId)
|
||||
// UserDAO.softDeleteAllFromMeeting(msg.meetingId)
|
||||
// MeetingRecordingDAO.updateStopped(msg.meetingId, "")
|
||||
|
||||
//Remove ColorPicker idx of the meeting
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.bigbluebutton.core.api
|
||||
|
||||
import org.bigbluebutton.core.apps.users.UserEstablishedGraphqlConnectionInternalMsgHdlr
|
||||
import org.bigbluebutton.core.domain.{ BreakoutUser, BreakoutVoiceUser }
|
||||
import spray.json.JsObject
|
||||
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
|
||||
|
||||
/**
|
||||
* 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
|
||||
case class DeskShareStartedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage
|
||||
case class DeskShareStoppedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage
|
||||
|
@ -45,6 +45,14 @@ object TimerModel {
|
||||
}
|
||||
|
||||
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
|
||||
}
|
||||
|
||||
|
@ -47,19 +47,31 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
}).toMap
|
||||
|
||||
def addAnnotations(wbId: String, userId: String, annotations: Array[AnnotationVO], isPresenter: Boolean, isModerator: Boolean): Array[AnnotationVO] = {
|
||||
var annotationsAdded = Array[AnnotationVO]()
|
||||
val wb = getWhiteboard(wbId)
|
||||
|
||||
var annotationsAdded = Array[AnnotationVO]()
|
||||
var newAnnotationsMap = wb.annotationsMap
|
||||
|
||||
for (annotation <- annotations) {
|
||||
val oldAnnotation = wb.annotationsMap.get(annotation.id)
|
||||
if (!oldAnnotation.isEmpty) {
|
||||
val hasPermission = isPresenter || isModerator || oldAnnotation.get.userId == userId
|
||||
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)
|
||||
annotationsAdded :+= annotation
|
||||
PresAnnotationDAO.insertOrUpdate(newAnnotation, annotation)
|
||||
println(s"Updated annotation onpage [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
||||
annotationsAdded :+= newAnnotation
|
||||
PresAnnotationDAO.insertOrUpdate(newAnnotation, newAnnotation)
|
||||
println(s"Updated annotation on page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
||||
} else {
|
||||
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)
|
||||
println(s"Adding annotation to page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
|
||||
} 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)
|
||||
saveWhiteboard(newWb)
|
||||
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] = {
|
||||
val wb = getWhiteboard(wbId)
|
||||
wb.annotationsMap.values.toArray
|
||||
}
|
||||
|
||||
def deleteAnnotations(wbId: String, userId: String, annotationsIds: Array[String], isPresenter: Boolean, isModerator: Boolean): Array[String] = {
|
||||
var annotationsIdsRemoved = Array[String]()
|
||||
val wb = getWhiteboard(wbId)
|
||||
|
||||
var annotationsIdsRemoved = Array[String]()
|
||||
var newAnnotationsMap = wb.annotationsMap
|
||||
|
||||
for (annotationId <- annotationsIds) {
|
||||
val annotation = wb.annotationsMap.get(annotationId)
|
||||
|
||||
if (!annotation.isEmpty) {
|
||||
if (annotation.isDefined) {
|
||||
val hasPermission = isPresenter || isModerator || annotation.get.userId == userId
|
||||
if (hasPermission) {
|
||||
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
|
||||
} 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)
|
||||
saveWhiteboard(newWb)
|
||||
|
||||
// Update whiteboard and save
|
||||
val updatedWb = wb.copy(annotationsMap = newAnnotationsMap)
|
||||
saveWhiteboard(updatedWb)
|
||||
|
||||
annotationsIdsRemoved.map(PresAnnotationDAO.delete(wbId, userId, _))
|
||||
|
||||
@ -130,4 +169,4 @@ class WhiteboardModel extends SystemConfiguration {
|
||||
}
|
||||
|
||||
def getChangedModeOn(wbId: String): Long = getWhiteboard(wbId).changedModeOn
|
||||
}
|
||||
}
|
@ -4,6 +4,7 @@ import org.apache.pekko.actor.ActorContext
|
||||
|
||||
class AudioCaptionsApp2x(implicit val context: ActorContext)
|
||||
extends UpdateTranscriptPubMsgHdlr
|
||||
with TranscriptionProviderErrorMsgHdlr
|
||||
with AudioFloorChangedVoiceConfEvtMsgHdlr {
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
}
|
@ -1,12 +1,13 @@
|
||||
package org.bigbluebutton.core.apps.audiocaptions
|
||||
|
||||
import org.bigbluebutton.ClientSettings.getConfigPropertyValueByPathAsStringOrElse
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
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 java.sql.Timestamp
|
||||
import java.time.LocalDateTime
|
||||
import java.time.format.DateTimeFormatter
|
||||
|
||||
trait UpdateTranscriptPubMsgHdlr {
|
||||
this: AudioCaptionsApp2x =>
|
||||
@ -25,6 +26,17 @@ trait UpdateTranscriptPubMsgHdlr {
|
||||
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
|
||||
def editTranscript(
|
||||
userId: String,
|
||||
@ -80,6 +92,28 @@ trait UpdateTranscriptPubMsgHdlr {
|
||||
msg.body.locale,
|
||||
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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -4,6 +4,7 @@ import org.bigbluebutton.core.running.MeetingActor
|
||||
import java.net.URLEncoder
|
||||
import scala.collection.SortedSet
|
||||
import org.apache.commons.codec.digest.DigestUtils
|
||||
import org.bigbluebutton.SystemConfiguration
|
||||
|
||||
trait BreakoutApp2x extends BreakoutRoomCreatedMsgHdlr
|
||||
with BreakoutRoomsListMsgHdlr
|
||||
@ -26,7 +27,7 @@ trait BreakoutApp2x extends BreakoutRoomCreatedMsgHdlr
|
||||
|
||||
}
|
||||
|
||||
object BreakoutRoomsUtil {
|
||||
object BreakoutRoomsUtil extends SystemConfiguration {
|
||||
def createMeetingIds(id: String, index: Int): (String, String) = {
|
||||
val timeStamp = System.currentTimeMillis()
|
||||
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
|
||||
//
|
||||
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 = {
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.ClientSettings.{getConfigPropertyValueByPath, getConfigPropertyValueByPathAsIntOrElse}
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{BreakoutModel, PermissionCheck, RightsManagementTrait}
|
||||
import org.bigbluebutton.core.db.BreakoutRoomDAO
|
||||
@ -16,6 +17,10 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
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")) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "Breakout rooms is disabled for this meeting."
|
||||
@ -27,6 +32,15 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId,
|
||||
reason, outGW, liveMeeting)
|
||||
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 {
|
||||
state.breakout match {
|
||||
case Some(breakout) =>
|
||||
@ -54,8 +68,8 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
|
||||
val voiceConf = BreakoutRoomsUtil.createVoiceConfId(liveMeeting.props.voiceProp.voiceConf, i)
|
||||
|
||||
val breakout = BreakoutModel.create(parentId, internalId, externalId, room.name, room.sequence, room.shortName,
|
||||
room.isDefaultName, room.freeJoin, voiceConf, room.users, msg.body.captureNotes,
|
||||
msg.body.captureSlides, room.captureNotesFilename, room.captureSlidesFilename)
|
||||
room.isDefaultName, room.freeJoin, voiceConf, room.users, msg.body.captureNotes,
|
||||
msg.body.captureSlides, room.captureNotesFilename, room.captureSlidesFilename)
|
||||
|
||||
rooms = rooms + (breakout.id -> breakout)
|
||||
}
|
||||
|
@ -30,7 +30,7 @@ trait EjectUserFromBreakoutInternalMsgHdlr {
|
||||
)
|
||||
|
||||
//TODO inform reason
|
||||
UserDAO.delete(registeredUser.id)
|
||||
UserDAO.softDelete(registeredUser.id)
|
||||
|
||||
// send a system message to force disconnection
|
||||
Sender.sendDisconnectClientSysMsg(msg.breakoutId, registeredUser.id, msg.ejectedBy, msg.reasonCode, outGW)
|
||||
|
@ -18,8 +18,8 @@ trait SendMessageToBreakoutRoomInternalMsgHdlr {
|
||||
sender <- GroupChatApp.findGroupChatUser(SystemUser.ID, liveMeeting.users2x)
|
||||
chat <- state.groupChats.find(GroupChatApp.MAIN_PUBLIC_CHAT)
|
||||
} yield {
|
||||
val groupChatMsgFromUser = GroupChatMsgFromUser(sender.id, sender.copy(name = msg.senderName), true, msg.msg)
|
||||
val gcm = GroupChatApp.toGroupChatMessage(sender.copy(name = msg.senderName), groupChatMsgFromUser)
|
||||
val groupChatMsgFromUser = GroupChatMsgFromUser(sender.id, sender.copy(name = msg.senderName), msg.msg)
|
||||
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 event = buildGroupChatMessageBroadcastEvtMsg(
|
||||
|
@ -59,7 +59,7 @@ trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
|
||||
val newState = for {
|
||||
createdBy <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x)
|
||||
} 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 = {
|
||||
if (msg.body.access == GroupChatAccess.PRIVATE) {
|
||||
val cu = msg.body.users.toSet + msg.header.userId
|
||||
|
@ -20,10 +20,10 @@ object GroupChatApp {
|
||||
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 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 = {
|
||||
@ -80,8 +80,8 @@ object GroupChatApp {
|
||||
sender <- GroupChatApp.findGroupChatUser(userId, liveMeeting.users2x)
|
||||
chat <- state.groupChats.find(chatId)
|
||||
} yield {
|
||||
|
||||
val gcm1 = GroupChatApp.toGroupChatMessage(sender, msg)
|
||||
val emphasizedText = sender.role == Roles.MODERATOR_ROLE
|
||||
val gcm1 = GroupChatApp.toGroupChatMessage(sender, msg, emphasizedText)
|
||||
val gcs1 = GroupChatApp.addGroupChatMessage(liveMeeting.props.meetingProp.intId, chat, state.groupChats, gcm1)
|
||||
state.update(gcs1)
|
||||
}
|
||||
|
@ -1,5 +1,6 @@
|
||||
package org.bigbluebutton.core.apps.groupchats
|
||||
|
||||
import org.bigbluebutton.ClientSettings.{ getConfigPropertyValueByPath, getConfigPropertyValueByPathAsBooleanOrElse }
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.PermissionCheck
|
||||
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;
|
||||
|
||||
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 event = buildGroupChatMessageBroadcastEvtMsg(
|
||||
|
@ -6,6 +6,7 @@ import org.bigbluebutton.core.running.OutMsgRouter
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.db.LayoutDAO
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
|
||||
|
||||
trait BroadcastLayoutMsgHdlr extends RightsManagementTrait {
|
||||
this: LayoutApp2x =>
|
||||
@ -60,5 +61,18 @@ trait BroadcastLayoutMsgHdlr extends RightsManagementTrait {
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,7 +21,7 @@ trait PadUpdatePubMsgHdlr {
|
||||
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 {
|
||||
case Some(group) => broadcastEvent(group.groupId, msg.body.externalId, msg.body.text)
|
||||
case _ =>
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -7,9 +7,9 @@ import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ Roles, Users2x }
|
||||
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 meetingId = liveMeeting.props.meetingProp.intId
|
||||
|
@ -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
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -4,7 +4,9 @@ import org.apache.pekko.actor.ActorContext
|
||||
import org.apache.pekko.event.Logging
|
||||
|
||||
class PluginHdlrs(implicit val context: ActorContext)
|
||||
extends DispatchPluginDataChannelMessageMsgHdlr {
|
||||
extends PluginDataChannelDispatchMessageMsgHdlr
|
||||
with PluginDataChannelDeleteMessageMsgHdlr
|
||||
with PluginDataChannelResetMsgHdlr {
|
||||
|
||||
val log = Logging(context.system, getClass)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -4,7 +4,7 @@ import org.bigbluebutton.common2.domain.SimplePollResultOutVO
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.models.Polls
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.models.Users2x
|
||||
|
||||
trait RespondToPollReqMsgHdlr {
|
||||
@ -12,45 +12,12 @@ trait RespondToPollReqMsgHdlr {
|
||||
|
||||
def handle(msg: RespondToPollReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
|
||||
def broadcastPollUpdatedEvent(msg: RespondToPollReqMsg, 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 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) {
|
||||
if (!Polls.hasUserAlreadyResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls)) {
|
||||
for {
|
||||
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToPollReqMsg(msg.header.userId, msg.body.pollId,
|
||||
msg.body.questionId, msg.body.answerIds, liveMeeting)
|
||||
} yield {
|
||||
broadcastPollUpdatedEvent(msg, pollId, updatedPoll)
|
||||
PollHdlrHelpers.broadcastPollUpdatedEvent(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, updatedPoll)
|
||||
for {
|
||||
poll <- Polls.getPoll(pollId, liveMeeting.polls)
|
||||
} yield {
|
||||
@ -58,14 +25,14 @@ trait RespondToPollReqMsgHdlr {
|
||||
answerId <- msg.body.answerIds
|
||||
} yield {
|
||||
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 {
|
||||
presenter <- Users2x.findPresenter(liveMeeting.users2x)
|
||||
} 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 {
|
||||
|
@ -1,10 +1,11 @@
|
||||
package org.bigbluebutton.core.apps.polls
|
||||
|
||||
import org.bigbluebutton.ClientSettings.getConfigPropertyValueByPathAsIntOrElse
|
||||
import org.bigbluebutton.common2.domain.SimplePollResultOutVO
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.models.Polls
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core.models.Users2x
|
||||
|
||||
trait RespondToTypedPollReqMsgHdlr {
|
||||
@ -12,43 +13,60 @@ trait RespondToTypedPollReqMsgHdlr {
|
||||
|
||||
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) &&
|
||||
Polls.checkUserResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls) == false &&
|
||||
Polls.checkUserAddedQuestion(msg.body.pollId, msg.header.userId, liveMeeting.polls) == false) {
|
||||
for {
|
||||
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToTypedPollReqMsg(msg.header.userId, msg.body.pollId,
|
||||
msg.body.questionId, msg.body.answer, liveMeeting)
|
||||
} yield {
|
||||
broadcastPollUpdatedEvent(msg, pollId, updatedPoll)
|
||||
!Polls.hasUserAlreadyResponded(msg.body.pollId, msg.header.userId, liveMeeting.polls) &&
|
||||
!Polls.hasUserAlreadyAddedTypedAnswer(msg.body.pollId, msg.header.userId, liveMeeting.polls)) {
|
||||
|
||||
for {
|
||||
presenter <- Users2x.findPresenter(liveMeeting.users2x)
|
||||
} yield {
|
||||
broadcastUserRespondedToTypedPollRespMsg(msg, pollId, msg.body.answer, presenter.intId)
|
||||
//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 {
|
||||
(pollId: String, updatedPoll: SimplePollResultOutVO) <- Polls.handleRespondToTypedPollReqMsg(msg.header.userId, msg.body.pollId,
|
||||
msg.body.questionId, answer, liveMeeting)
|
||||
} yield {
|
||||
PollHdlrHelpers.broadcastPollUpdatedEvent(bus.outGW, liveMeeting.props.meetingProp.intId, msg.header.userId, pollId, updatedPoll)
|
||||
|
||||
for {
|
||||
presenter <- Users2x.findPresenter(liveMeeting.users2x)
|
||||
} yield {
|
||||
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 {
|
||||
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)
|
||||
}
|
||||
|
@ -46,7 +46,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
def buildNewPresFileAvailable(annotatedFileURI: String, originalFileURI: String, convertedFileURI: String,
|
||||
presId: String, fileStateType: String): NewPresFileAvailableMsg = {
|
||||
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)
|
||||
}
|
||||
@ -160,7 +160,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
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 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 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 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 annotationCount: Int = storeAnnotationPages.map(_.annotations.size).sum
|
||||
@ -252,11 +252,10 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
|
||||
liveMeeting.props.meetingProp.intId, m.body.presId
|
||||
)
|
||||
|
||||
//TODO let frontend choose the name in favor of internationalization
|
||||
if (m.body.fileStateType == "Annotated") {
|
||||
val presentationDownloadInfo = Map(
|
||||
"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, "")
|
||||
} 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))
|
||||
|
||||
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)
|
||||
|
||||
bus.outGW.send(job)
|
||||
|
@ -30,7 +30,10 @@ trait DeactivateTimerReqMsgHdlr extends RightsManagementTrait {
|
||||
val reason = "You need to be the presenter or moderator to deactivate timer"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} 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)
|
||||
broadcastEvent()
|
||||
}
|
||||
|
@ -31,7 +31,7 @@ trait StartTimerReqMsgHdlr extends RightsManagementTrait {
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
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)
|
||||
broadcastEvent()
|
||||
}
|
||||
|
@ -33,10 +33,9 @@ trait StopTimerReqMsgHdlr extends RightsManagementTrait {
|
||||
val reason = "You need to be the presenter or moderator to stop timer"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
TimerModel.setAccumulated(liveMeeting.timerModel, msg.body.accumulated)
|
||||
TimerModel.setRunning(liveMeeting.timerModel, false)
|
||||
TimerModel.setRunning(liveMeeting.timerModel, running = false)
|
||||
TimerDAO.update(liveMeeting.props.meetingProp.intId, liveMeeting.timerModel)
|
||||
broadcastEvent(msg.body.accumulated)
|
||||
broadcastEvent(TimerModel.getAccumulated(liveMeeting.timerModel))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -34,6 +34,7 @@ trait SwitchTimerReqMsgHdlr extends RightsManagementTrait {
|
||||
} else {
|
||||
if (TimerModel.getStopwatch(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
|
||||
if (msg.body.stopwatch) {
|
||||
TimerModel.setTrack(liveMeeting.timerModel, "noTrack")
|
||||
|
@ -9,7 +9,7 @@ import org.bigbluebutton.core.running.OutMsgRouter
|
||||
import org.bigbluebutton.core.running.MeetingActor
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
import org.bigbluebutton.core2.Permissions
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
|
||||
|
||||
trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
@ -237,6 +237,16 @@ trait ChangeLockSettingsInMeetingCmdMsgHdlr extends RightsManagementTrait {
|
||||
)
|
||||
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,6 +1,6 @@
|
||||
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.core.apps.RightsManagementTrait
|
||||
import org.bigbluebutton.core.models.{ UserState, Users2x }
|
||||
@ -31,13 +31,7 @@ trait ChangeUserReactionEmojiReqMsgHdlr extends RightsManagementTrait {
|
||||
}
|
||||
|
||||
//Get durationInSeconds from Client config
|
||||
val userReactionExpire =
|
||||
getConfigPropertyValueByPath(liveMeeting.clientSettings, "public.userReaction.expire") match {
|
||||
case Some(durationInSeconds: Int) => durationInSeconds
|
||||
case _ =>
|
||||
log.debug("Config `public.userReaction.expire` not found.")
|
||||
30
|
||||
}
|
||||
val userReactionExpire = getConfigPropertyValueByPathAsIntOrElse(liveMeeting.clientSettings, "public.userReaction.expire", 30)
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
|
||||
newUserState <- Users2x.setReactionEmoji(liveMeeting.users2x, user.intId, msg.body.reactionEmoji, userReactionExpire)
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -20,7 +20,7 @@ trait UserJoinMeetingAfterReconnectReqMsgHdlr extends HandlerHelpers with UserJo
|
||||
if (reconnectingUser.userLeftFlag.left) {
|
||||
log.info("Resetting flag that user left meeting. user {}", msg.body.userId)
|
||||
// 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)
|
||||
}
|
||||
state
|
||||
|
@ -71,7 +71,7 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
|
||||
|
||||
private def resetUserLeftFlag(msg: UserJoinMeetingReqMsg) = {
|
||||
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)
|
||||
}
|
||||
|
||||
|
@ -1,6 +1,7 @@
|
||||
package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.common2.msgs.UserLeaveReqMsg
|
||||
import org.bigbluebutton.core.api.{ UserClosedAllGraphqlConnectionsInternalMsg }
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x }
|
||||
import org.bigbluebutton.core.running.{ HandlerHelpers, MeetingActor, OutMsgRouter }
|
||||
@ -12,23 +13,33 @@ trait UserLeaveReqMsgHdlr extends HandlerHelpers {
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
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) =>
|
||||
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) {
|
||||
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.
|
||||
// An audit will remove this user if it hasn't rejoined after a certain period of time.
|
||||
// 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) {
|
||||
log.info("Setting user logged out flag. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
|
||||
if (loggedOut) {
|
||||
log.info("Setting user logged out flag. user {} meetingId={}", userId, meetingId)
|
||||
|
||||
for {
|
||||
ru <- RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers)
|
||||
ru <- RegisteredUsers.findWithUserId(userId, liveMeeting.registeredUsers)
|
||||
} yield {
|
||||
RegisteredUsers.setUserLoggedOutFlag(liveMeeting.registeredUsers, ru)
|
||||
Sender.sendForceUserGraphqlReconnectionSysMsg(liveMeeting.props.meetingProp.intId, ru.id, ru.sessionToken, "user_loggedout", outGW)
|
||||
@ -39,4 +50,5 @@ trait UserLeaveReqMsgHdlr extends HandlerHelpers {
|
||||
state
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -158,16 +158,18 @@ class UsersApp(
|
||||
with RegisterUserReqMsgHdlr
|
||||
with ChangeUserRoleCmdMsgHdlr
|
||||
with SetUserSpeechLocaleMsgHdlr
|
||||
with SetUserSpeechOptionsMsgHdlr
|
||||
with SyncGetUsersMeetingRespMsgHdlr
|
||||
with LogoutAndEndMeetingCmdMsgHdlr
|
||||
with SetRecordingStatusCmdMsgHdlr
|
||||
with RecordAndClearPreviousMarkersCmdMsgHdlr
|
||||
with SendRecordingTimerInternalMsgHdlr
|
||||
with GetRecordingStatusReqMsgHdlr
|
||||
with SelectRandomViewerReqMsgHdlr
|
||||
with AssignPresenterReqMsgHdlr
|
||||
with ChangeUserPinStateReqMsgHdlr
|
||||
with ChangeUserMobileFlagReqMsgHdlr
|
||||
with UserConnectionAliveReqMsgHdlr
|
||||
with UserConnectionUpdateRttReqMsgHdlr
|
||||
with ChangeUserReactionEmojiReqMsgHdlr
|
||||
with ChangeUserRaiseHandReqMsgHdlr
|
||||
with ChangeUserAwayReqMsgHdlr
|
||||
|
@ -58,7 +58,6 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
|
||||
avatar = "",
|
||||
color = userColor,
|
||||
clientType = if (isDialInUser) "dial-in-user" else "",
|
||||
pickExempted = false,
|
||||
userLeftFlag = UserLeftFlag(false, 0)
|
||||
)
|
||||
Users2x.add(liveMeeting.users2x, newUser)
|
||||
|
@ -40,7 +40,7 @@ trait UserLeftVoiceConfEvtMsgHdlr {
|
||||
UsersApp.guestWaitingLeft(liveMeeting, user.intId, outGW)
|
||||
}
|
||||
Users2x.remove(liveMeeting.users2x, user.intId)
|
||||
UserDAO.delete(user.intId)
|
||||
UserDAO.softDelete(user.intId)
|
||||
VoiceApp.removeUserFromVoiceConf(liveMeeting, outGW, msg.body.voiceUserId)
|
||||
}
|
||||
|
||||
|
@ -4,8 +4,9 @@ import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.PermissionCheck
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.db.MeetingUsersPoliciesDAO
|
||||
import org.bigbluebutton.core.models.{ RegisteredUsers, Roles, Users2x }
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
|
||||
|
||||
trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
|
||||
this: WebcamApp2x =>
|
||||
@ -76,6 +77,16 @@ trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
|
||||
}
|
||||
|
||||
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 _ =>
|
||||
}
|
||||
|
@ -52,11 +52,11 @@ trait SendWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
|
||||
)
|
||||
|
||||
if (isUserOneOfPermited || isUserAmongPresenters) {
|
||||
println("============= Printing Sanitized annotations ============")
|
||||
for (annotation <- msg.body.annotations) {
|
||||
printAnnotationInfo(annotation)
|
||||
}
|
||||
println("============= Printed Sanitized annotations ============")
|
||||
// println("============= Printing Sanitized annotations ============")
|
||||
// for (annotation <- msg.body.annotations) {
|
||||
// printAnnotationInfo(annotation)
|
||||
// }
|
||||
// println("============= Printed Sanitized annotations ============")
|
||||
val annotations = sendWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotations, liveMeeting, isUserAmongPresenters, isUserModerator)
|
||||
broadcastEvent(msg, msg.body.whiteboardId, annotations, msg.body.html5InstanceId)
|
||||
} else {
|
||||
|
@ -20,8 +20,16 @@ case class MeetingDbModel(
|
||||
presentationUploadExternalUrl: String,
|
||||
learningDashboardAccessToken: String,
|
||||
logoutUrl: String,
|
||||
customLogoUrl: Option[String],
|
||||
bannerText: Option[String],
|
||||
bannerColor: Option[String],
|
||||
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") {
|
||||
@ -38,8 +46,16 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
|
||||
presentationUploadExternalUrl,
|
||||
learningDashboardAccessToken,
|
||||
logoutUrl,
|
||||
customLogoUrl,
|
||||
bannerText,
|
||||
bannerColor,
|
||||
createdTime,
|
||||
durationInSeconds
|
||||
durationInSeconds,
|
||||
endWhenNoModerator,
|
||||
endWhenNoModeratorDelayInMinutes,
|
||||
endedAt,
|
||||
endedReasonCode,
|
||||
endedBy
|
||||
) <> (MeetingDbModel.tupled, MeetingDbModel.unapply)
|
||||
val meetingId = column[String]("meetingId", O.PrimaryKey)
|
||||
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 learningDashboardAccessToken = column[String]("learningDashboardAccessToken")
|
||||
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 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 {
|
||||
@ -74,28 +98,45 @@ object MeetingDAO {
|
||||
presentationUploadExternalUrl = meetingProps.meetingProp.presentationUploadExternalUrl,
|
||||
learningDashboardAccessToken = meetingProps.password.learningDashboardAccessToken,
|
||||
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,
|
||||
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 {
|
||||
case Success(rowsAffected) => {
|
||||
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in Meeting table!")
|
||||
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
|
||||
MeetingUsersPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp)
|
||||
MeetingLockSettingsDAO.insert(meetingProps.meetingProp.intId, meetingProps.lockSettingsProps)
|
||||
MeetingMetadataDAO.insert(meetingProps.meetingProp.intId, meetingProps.metadataProp)
|
||||
MeetingRecordingPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.recordProp)
|
||||
MeetingVoiceDAO.insert(meetingProps.meetingProp.intId, meetingProps.voiceProp)
|
||||
MeetingWelcomeDAO.insert(meetingProps.meetingProp.intId, meetingProps.welcomeProp)
|
||||
MeetingGroupDAO.insert(meetingProps.meetingProp.intId, meetingProps.groups)
|
||||
MeetingBreakoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.breakoutProps)
|
||||
TimerDAO.insert(meetingProps.meetingProp.intId)
|
||||
LayoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp.meetingLayout)
|
||||
MeetingClientSettingsDAO.insert(meetingProps.meetingProp.intId, JsonUtils.mapToJson(clientSettings))
|
||||
}
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting Meeting: $e")
|
||||
case Success(rowsAffected) => {
|
||||
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in Meeting table!")
|
||||
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
|
||||
MeetingUsersPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp)
|
||||
MeetingLockSettingsDAO.insert(meetingProps.meetingProp.intId, meetingProps.lockSettingsProps)
|
||||
MeetingMetadataDAO.insert(meetingProps.meetingProp.intId, meetingProps.metadataProp)
|
||||
MeetingRecordingPoliciesDAO.insert(meetingProps.meetingProp.intId, meetingProps.recordProp)
|
||||
MeetingVoiceDAO.insert(meetingProps.meetingProp.intId, meetingProps.voiceProp)
|
||||
MeetingWelcomeDAO.insert(meetingProps.meetingProp.intId, meetingProps.welcomeProp)
|
||||
MeetingGroupDAO.insert(meetingProps.meetingProp.intId, meetingProps.groups)
|
||||
MeetingBreakoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.breakoutProps)
|
||||
TimerDAO.insert(meetingProps.meetingProp.intId)
|
||||
LayoutDAO.insert(meetingProps.meetingProp.intId, meetingProps.usersProp.meetingLayout)
|
||||
MeetingClientSettingsDAO.insert(meetingProps.meetingProp.intId, JsonUtils.mapToJson(clientSettings))
|
||||
}
|
||||
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting Meeting: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def updateMeetingDurationByParentMeeting(parentMeetingId: String, newDurationInSeconds: Int) = {
|
||||
@ -110,9 +151,9 @@ object MeetingDAO {
|
||||
.map(u => u.durationInSeconds)
|
||||
.update(newDurationInSeconds)
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated durationInSeconds on Meeting table")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating durationInSeconds on Meeting: $e")
|
||||
}
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated durationInSeconds on Meeting table")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating durationInSeconds on Meeting: $e")
|
||||
}
|
||||
}
|
||||
|
||||
def delete(meetingId: String) = {
|
||||
@ -121,9 +162,55 @@ object MeetingDAO {
|
||||
.filter(_.meetingId === meetingId)
|
||||
.delete
|
||||
).onComplete {
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Meeting ${meetingId} deleted")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error deleting meeting ${meetingId}: $e")
|
||||
}
|
||||
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Meeting ${meetingId} deleted")
|
||||
case Failure(e) => DatabaseConnection.logger.debug(s"Error deleting meeting ${meetingId}: $e")
|
||||
}
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
@ -1,9 +1,13 @@
|
||||
package org.bigbluebutton.core.db
|
||||
|
||||
import PostgresProfile.api._
|
||||
import org.bigbluebutton.core.db.DatabaseConnection.{db, logger}
|
||||
import spray.json.JsValue
|
||||
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.concurrent.{Await, Future}
|
||||
import scala.util.{Failure, Success}
|
||||
import scala.concurrent.duration.Duration
|
||||
|
||||
object Permission {
|
||||
val allowedRoles = List("MODERATOR","VIEWER","PRESENTER")
|
||||
@ -19,6 +23,7 @@ case class PluginDataChannelMessageDbModel(
|
||||
toRoles: Option[List[String]],
|
||||
toUserIds: Option[List[String]],
|
||||
createdAt: java.sql.Timestamp,
|
||||
deletedAt: Option[java.sql.Timestamp],
|
||||
)
|
||||
|
||||
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 toUserIds = column[Option[List[String]]]("toUserIds")
|
||||
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 {
|
||||
@ -49,7 +55,8 @@ object PluginDataChannelMessageDAO {
|
||||
case filtered => Some(filtered)
|
||||
},
|
||||
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 {
|
||||
@ -57,4 +64,51 @@ object PluginDataChannelMessageDAO {
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -14,8 +14,8 @@ case class TimerDbModel(
|
||||
active: Boolean,
|
||||
time: Long,
|
||||
accumulated: Long,
|
||||
startedAt: Long,
|
||||
endedAt: Long,
|
||||
startedOn: Long,
|
||||
endedOn: Long,
|
||||
songTrack: String,
|
||||
)
|
||||
|
||||
@ -26,10 +26,10 @@ class TimerDbTableDef(tag: Tag) extends Table[TimerDbModel](tag, None, "timer")
|
||||
val active = column[Boolean]("active")
|
||||
val time = column[Long]("time")
|
||||
val accumulated = column[Long]("accumulated")
|
||||
val startedAt = column[Long]("startedAt")
|
||||
val endedAt = column[Long]("endedAt")
|
||||
val startedOn = column[Long]("startedOn")
|
||||
val endedOn = column[Long]("endedOn")
|
||||
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 {
|
||||
@ -43,8 +43,8 @@ object TimerDAO {
|
||||
active = false,
|
||||
time = 300000,
|
||||
accumulated = 0,
|
||||
startedAt = 0,
|
||||
endedAt = 0,
|
||||
startedOn = 0,
|
||||
endedOn = 0,
|
||||
songTrack = "noTrack",
|
||||
)
|
||||
)
|
||||
@ -58,7 +58,7 @@ object TimerDAO {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[TimerDbTableDef]
|
||||
.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))
|
||||
)
|
||||
).onComplete {
|
||||
|
@ -5,18 +5,22 @@ import scala.concurrent.ExecutionContext.Implicits.global
|
||||
import scala.util.{ Failure, Success }
|
||||
|
||||
case class UserConnectionStatusDbModel(
|
||||
userId: String,
|
||||
meetingId: String,
|
||||
connectionAliveAt: Option[java.sql.Timestamp]
|
||||
userId: String,
|
||||
meetingId: String,
|
||||
connectionAliveAt: Option[java.sql.Timestamp],
|
||||
userClientResponseAt: Option[java.sql.Timestamp],
|
||||
networkRttInMs: Option[Double]
|
||||
)
|
||||
|
||||
class UserConnectionStatusDbTableDef(tag: Tag) extends Table[UserConnectionStatusDbModel](tag, None, "user_connectionStatus") {
|
||||
override def * = (
|
||||
userId, meetingId, connectionAliveAt
|
||||
userId, meetingId, connectionAliveAt, userClientResponseAt, networkRttInMs
|
||||
) <> (UserConnectionStatusDbModel.tupled, UserConnectionStatusDbModel.unapply)
|
||||
val userId = column[String]("userId", O.PrimaryKey)
|
||||
val meetingId = column[String]("meetingId")
|
||||
val connectionAliveAt = column[Option[java.sql.Timestamp]]("connectionAliveAt")
|
||||
val userClientResponseAt = column[Option[java.sql.Timestamp]]("userClientResponseAt")
|
||||
val networkRttInMs = column[Option[Double]]("networkRttInMs")
|
||||
}
|
||||
|
||||
object UserConnectionStatusDAO {
|
||||
@ -27,7 +31,9 @@ object UserConnectionStatusDAO {
|
||||
UserConnectionStatusDbModel(
|
||||
userId = userId,
|
||||
meetingId = meetingId,
|
||||
connectionAliveAt = None
|
||||
connectionAliveAt = None,
|
||||
userClientResponseAt = None,
|
||||
networkRttInMs = None
|
||||
)
|
||||
)
|
||||
).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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -14,6 +14,7 @@ case class UserDbModel(
|
||||
avatar: String = "",
|
||||
color: String = "",
|
||||
sessionToken: String = "",
|
||||
authToken: String = "",
|
||||
authed: Boolean = false,
|
||||
joined: Boolean = false,
|
||||
joinErrorMessage: Option[String],
|
||||
@ -31,7 +32,7 @@ case class UserDbModel(
|
||||
|
||||
class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
|
||||
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 extId = column[String]("extId")
|
||||
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 color = column[String]("color")
|
||||
val sessionToken = column[String]("sessionToken")
|
||||
val authToken = column[String]("authToken")
|
||||
val authed = column[Boolean]("authed")
|
||||
val joined = column[Boolean]("joined")
|
||||
val joinErrorCode = column[Option[String]]("joinErrorCode")
|
||||
@ -60,6 +62,7 @@ object UserDAO {
|
||||
UserDbModel(
|
||||
userId = regUser.id,
|
||||
extId = regUser.externId,
|
||||
authToken = regUser.authToken,
|
||||
meetingId = meetingId,
|
||||
name = regUser.name,
|
||||
role = regUser.role,
|
||||
@ -132,7 +135,7 @@ object UserDAO {
|
||||
}
|
||||
|
||||
|
||||
def delete(intId: String) = {
|
||||
def softDelete(intId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserDbTableDef]
|
||||
.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(
|
||||
TableQuery[UserDbTableDef]
|
||||
.filter(_.meetingId === meetingId)
|
||||
|
@ -9,32 +9,35 @@ import scala.util.{Failure, Success }
|
||||
case class UserGraphqlConnectionDbModel (
|
||||
graphqlConnectionId: Option[Int],
|
||||
sessionToken: String,
|
||||
middlewareUID: String,
|
||||
middlewareConnectionId: String,
|
||||
stablishedAt: java.sql.Timestamp,
|
||||
establishedAt: java.sql.Timestamp,
|
||||
closedAt: Option[java.sql.Timestamp],
|
||||
)
|
||||
|
||||
class UserGraphqlConnectionDbTableDef(tag: Tag) extends Table[UserGraphqlConnectionDbModel](tag, None, "user_graphqlConnection") {
|
||||
override def * = (
|
||||
graphqlConnectionId, sessionToken, middlewareConnectionId, stablishedAt, closedAt
|
||||
graphqlConnectionId, sessionToken, middlewareUID, middlewareConnectionId, establishedAt, closedAt
|
||||
) <> (UserGraphqlConnectionDbModel.tupled, UserGraphqlConnectionDbModel.unapply)
|
||||
val graphqlConnectionId = column[Option[Int]]("graphqlConnectionId", O.PrimaryKey, O.AutoInc)
|
||||
val sessionToken = column[String]("sessionToken")
|
||||
val middlewareUID = column[String]("middlewareUID")
|
||||
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")
|
||||
}
|
||||
|
||||
|
||||
object UserGraphqlConnectionDAO {
|
||||
def insert(sessionToken: String, middlewareConnectionId: String) = {
|
||||
def insert(sessionToken: String, middlewareUID:String, middlewareConnectionId: String) = {
|
||||
DatabaseConnection.db.run(
|
||||
TableQuery[UserGraphqlConnectionDbTableDef].insertOrUpdate(
|
||||
UserGraphqlConnectionDbModel(
|
||||
graphqlConnectionId = None,
|
||||
sessionToken = sessionToken,
|
||||
middlewareUID = middlewareUID,
|
||||
middlewareConnectionId = middlewareConnectionId,
|
||||
stablishedAt = new java.sql.Timestamp(System.currentTimeMillis()),
|
||||
establishedAt = new java.sql.Timestamp(System.currentTimeMillis()),
|
||||
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(
|
||||
TableQuery[UserGraphqlConnectionDbTableDef]
|
||||
.filter(_.sessionToken === sessionToken)
|
||||
.filter(_.middlewareConnectionId === middlewareConnectionId)
|
||||
.filter(_.middlewareUID === middlewareUID)
|
||||
.filter(_.closedAt.isEmpty)
|
||||
.map(u => u.closedAt)
|
||||
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
|
||||
|
@ -26,11 +26,15 @@ case class UserStateDbModel(
|
||||
pinned: Boolean = false,
|
||||
locked: Boolean = false,
|
||||
speechLocale: String,
|
||||
inactivityWarningDisplay: Boolean = false,
|
||||
inactivityWarningTimeoutSecs: Option[Long],
|
||||
)
|
||||
|
||||
class UserStateDbTableDef(tag: Tag) extends Table[UserStateDbModel](tag, None, "user") {
|
||||
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 emoji = column[String]("emoji")
|
||||
val away = column[Boolean]("away")
|
||||
@ -50,6 +54,8 @@ class UserStateDbTableDef(tag: Tag) extends Table[UserStateDbModel](tag, None, "
|
||||
val pinned = column[Boolean]("pinned")
|
||||
val locked = column[Boolean]("locked")
|
||||
val speechLocale = column[String]("speechLocale")
|
||||
val inactivityWarningDisplay = column[Boolean]("inactivityWarningDisplay")
|
||||
val inactivityWarningTimeoutSecs = column[Option[Long]]("inactivityWarningTimeoutSecs")
|
||||
}
|
||||
|
||||
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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -42,4 +42,5 @@ object MeetingEndReason {
|
||||
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_MODERATOR = "ENDED_DUE_TO_NO_MODERATOR"
|
||||
val ENDED_DUE_TO_SERVICE_INTERRUPTION = "ENDED_DUE_TO_SERVICE_INTERRUPTION"
|
||||
}
|
||||
|
@ -7,12 +7,10 @@ import org.bigbluebutton.SystemConfiguration
|
||||
object AudioCaptions extends SystemConfiguration {
|
||||
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 = {
|
||||
val words = transcript.split("\\s+") // Split on whitespaces
|
||||
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
|
||||
transcript
|
||||
}
|
||||
|
||||
/*
|
||||
|
@ -104,7 +104,7 @@ object Polls {
|
||||
} yield {
|
||||
val pageId = if (poll.id.contains("deskshare")) "deskshare" else page.id
|
||||
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
|
||||
}
|
||||
}
|
||||
@ -243,7 +243,6 @@ object Polls {
|
||||
|
||||
private def handleRespondToTypedPoll(poll: SimplePollResultOutVO, requesterId: String, pollId: String, questionId: Int,
|
||||
answer: String, lm: LiveMeeting): Option[SimplePollResultOutVO] = {
|
||||
|
||||
addQuestionResponse(poll.id, questionId, answer, requesterId, lm.polls)
|
||||
for {
|
||||
updatedPoll <- getSimplePollResult(poll.id, lm.polls)
|
||||
@ -254,12 +253,13 @@ object Polls {
|
||||
|
||||
private def pollResultToWhiteboardShape(result: SimplePollResultOutVO): scala.collection.immutable.Map[String, Object] = {
|
||||
val shape = new scala.collection.mutable.HashMap[String, Object]()
|
||||
shape += "numRespondents" -> new Integer(result.numRespondents)
|
||||
shape += "numResponders" -> new Integer(result.numResponders)
|
||||
shape += "numRespondents" -> Integer.valueOf(result.numRespondents)
|
||||
shape += "numResponders" -> Integer.valueOf(result.numResponders)
|
||||
shape += "questionType" -> result.questionType
|
||||
shape += "questionText" -> result.questionText
|
||||
shape += "id" -> result.id
|
||||
shape += "questionText" -> result.questionText.getOrElse("")
|
||||
shape += "id" -> s"shape:poll-result-${result.id}"
|
||||
shape += "answers" -> result.answers
|
||||
shape += "type" -> "geo"
|
||||
shape.toMap
|
||||
}
|
||||
|
||||
@ -362,10 +362,10 @@ object Polls {
|
||||
pvo
|
||||
}
|
||||
|
||||
def checkUserResponded(pollId: String, userId: String, polls: Polls): Boolean = {
|
||||
def hasUserAlreadyResponded(pollId: String, userId: String, polls: Polls): Boolean = {
|
||||
polls.polls.get(pollId) match {
|
||||
case Some(p) => {
|
||||
if (p.getResponders().filter(p => p.userId == userId).length > 0) {
|
||||
if (p.getResponders().exists(p => p.userId == userId)) {
|
||||
true
|
||||
} else {
|
||||
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 {
|
||||
case Some(p) => {
|
||||
if (p.getTypedPollResponders().filter(responderId => responderId == userId).length > 0) {
|
||||
if (p.getTypedPollResponders().contains(userId)) {
|
||||
true
|
||||
} else {
|
||||
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) {
|
||||
polls.get(pollId) foreach {
|
||||
p =>
|
||||
|
@ -176,8 +176,8 @@ case class PresentationPod(id: String, currentPresenter: String,
|
||||
// 100D-checkedWidth is the maximum the page can be moved over
|
||||
val checkedWidth = Math.min(widthRatio, 100D) //if (widthRatio <= 100D) widthRatio else 100D
|
||||
val checkedHeight = Math.min(heightRatio, 100D)
|
||||
val checkedXOffset = Math.min(xOffset, 0D)
|
||||
val checkedYOffset = Math.min(yOffset, 0D)
|
||||
val checkedXOffset = xOffset
|
||||
val checkedYOffset = yOffset
|
||||
|
||||
for {
|
||||
pres <- presentations.get(presentationId)
|
||||
|
@ -91,7 +91,7 @@ object RegisteredUsers {
|
||||
// will fail and can't join.
|
||||
// ralam april 21, 2020
|
||||
val bannedUser = user.copy(banned = true)
|
||||
//UserDAO.insert(meetingId, bannedUser)
|
||||
UserDAO.insert(meetingId, bannedUser)
|
||||
users.save(bannedUser)
|
||||
} else {
|
||||
// If user hasn't been ejected, we allow user to join
|
||||
@ -122,7 +122,7 @@ object RegisteredUsers {
|
||||
u
|
||||
} else {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
@ -27,7 +27,7 @@ object Users2x {
|
||||
}
|
||||
|
||||
def remove(users: Users2x, intId: String): Option[UserState] = {
|
||||
//UserDAO.delete(intId)
|
||||
//UserDAO.softDelete(intId)
|
||||
users.remove(intId)
|
||||
}
|
||||
|
||||
@ -78,15 +78,6 @@ object Users2x {
|
||||
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] = {
|
||||
users.toVector.filter(u => u.role == Roles.VIEWER_ROLE)
|
||||
}
|
||||
@ -98,6 +89,19 @@ object Users2x {
|
||||
def updateLastUserActivity(users: Users2x, u: UserState): UserState = {
|
||||
val newUserState = modify(u)(_.lastActivityTime).setTo(System.currentTimeMillis())
|
||||
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
|
||||
}
|
||||
|
||||
@ -125,7 +129,7 @@ object Users2x {
|
||||
_ <- users.remove(intId)
|
||||
ejectedUser <- users.removeFromCache(intId)
|
||||
} yield {
|
||||
// UserDAO.delete(intId) --it will keep the user on Db
|
||||
// UserDAO.softDelete(intId) --it will keep the user on Db
|
||||
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] = {
|
||||
for {
|
||||
u <- findWithIntId(users, intId)
|
||||
@ -435,7 +429,6 @@ case class UserState(
|
||||
lastActivityTime: Long = System.currentTimeMillis(),
|
||||
lastInactivityInspect: Long = 0,
|
||||
clientType: String,
|
||||
pickExempted: Boolean,
|
||||
userLeftFlag: UserLeftFlag,
|
||||
speechLocale: String = ""
|
||||
)
|
||||
|
@ -111,10 +111,14 @@ class ReceivedJsonMsgHandlerActor(
|
||||
routeGenericMsg[ChangeUserPinStateReqMsg](envelope, jsonNode)
|
||||
case ChangeUserMobileFlagReqMsg.NAME =>
|
||||
routeGenericMsg[ChangeUserMobileFlagReqMsg](envelope, jsonNode)
|
||||
case UserConnectionAliveReqMsg.NAME =>
|
||||
routeGenericMsg[UserConnectionAliveReqMsg](envelope, jsonNode)
|
||||
case UserConnectionUpdateRttReqMsg.NAME =>
|
||||
routeGenericMsg[UserConnectionUpdateRttReqMsg](envelope, jsonNode)
|
||||
case SetUserSpeechLocaleReqMsg.NAME =>
|
||||
routeGenericMsg[SetUserSpeechLocaleReqMsg](envelope, jsonNode)
|
||||
case SelectRandomViewerReqMsg.NAME =>
|
||||
routeGenericMsg[SelectRandomViewerReqMsg](envelope, jsonNode)
|
||||
case SetUserSpeechOptionsReqMsg.NAME =>
|
||||
routeGenericMsg[SetUserSpeechOptionsReqMsg](envelope, jsonNode)
|
||||
|
||||
// Poll
|
||||
case StartCustomPollReqMsg.NAME =>
|
||||
@ -404,6 +408,8 @@ class ReceivedJsonMsgHandlerActor(
|
||||
// AudioCaptions
|
||||
case UpdateTranscriptPubMsg.NAME =>
|
||||
routeGenericMsg[UpdateTranscriptPubMsg](envelope, jsonNode)
|
||||
case TranscriptionProviderErrorMsg.NAME =>
|
||||
routeGenericMsg[TranscriptionProviderErrorMsg](envelope, jsonNode)
|
||||
|
||||
// GroupChats
|
||||
case GetGroupChatsReqMsg.NAME =>
|
||||
@ -416,8 +422,14 @@ class ReceivedJsonMsgHandlerActor(
|
||||
routeGenericMsg[CreateGroupChatReqMsg](envelope, jsonNode)
|
||||
|
||||
//Plugin
|
||||
case DispatchPluginDataChannelMessageMsg.NAME =>
|
||||
routeGenericMsg[DispatchPluginDataChannelMessageMsg](envelope, jsonNode)
|
||||
case PluginDataChannelDispatchMessageMsg.NAME =>
|
||||
routeGenericMsg[PluginDataChannelDispatchMessageMsg](envelope, jsonNode)
|
||||
|
||||
case PluginDataChannelDeleteMessageMsg.NAME =>
|
||||
routeGenericMsg[PluginDataChannelDeleteMessageMsg](envelope, jsonNode)
|
||||
|
||||
case PluginDataChannelResetMsg.NAME =>
|
||||
routeGenericMsg[PluginDataChannelResetMsg](envelope, jsonNode)
|
||||
|
||||
// ExternalVideo
|
||||
case StartExternalVideoPubMsg.NAME =>
|
||||
@ -456,6 +468,9 @@ class ReceivedJsonMsgHandlerActor(
|
||||
case UserGraphqlConnectionClosedSysMsg.NAME =>
|
||||
route[UserGraphqlConnectionClosedSysMsg](meetingManagerChannel, envelope, jsonNode)
|
||||
|
||||
case CheckGraphqlMiddlewareAlivePongSysMsg.NAME =>
|
||||
route[CheckGraphqlMiddlewareAlivePongSysMsg](meetingManagerChannel, envelope, jsonNode)
|
||||
|
||||
case _ =>
|
||||
log.error("Cannot route envelope name " + envelope.name)
|
||||
// do nothing
|
||||
|
@ -27,6 +27,10 @@ class StoreExportJobInRedisPresAnnEvent extends AbstractPresentationWithAnnotati
|
||||
|
||||
setEvent("StoreExportJobInRedisPresAnnEvent")
|
||||
|
||||
def setserverSideFilename(serverSideFilename: String) {
|
||||
eventMap.put(SERVER_SIDE_FILENAME, serverSideFilename)
|
||||
}
|
||||
|
||||
def setJobId(jobId: String) {
|
||||
eventMap.put(JOB_ID, jobId)
|
||||
}
|
||||
@ -68,6 +72,7 @@ object StoreExportJobInRedisPresAnnEvent {
|
||||
protected final val JOB_ID = "jobId"
|
||||
protected final val JOB_TYPE = "jobType"
|
||||
protected final val FILENAME = "filename"
|
||||
protected final val SERVER_SIDE_FILENAME = "serverSideFilename"
|
||||
protected final val PRES_ID = "presId"
|
||||
protected final val PRES_LOCATION = "presLocation"
|
||||
protected final val ALL_PAGES = "allPages"
|
||||
|
@ -7,7 +7,7 @@ import org.bigbluebutton.core.apps.groupchats.GroupChatApp
|
||||
import org.bigbluebutton.core.apps.users.UsersApp
|
||||
import org.bigbluebutton.core.apps.voice.VoiceApp
|
||||
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.models._
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
@ -73,7 +73,6 @@ trait HandlerHelpers extends SystemConfiguration {
|
||||
avatar = regUser.avatarURL,
|
||||
color = regUser.color,
|
||||
clientType = clientType,
|
||||
pickExempted = false,
|
||||
userLeftFlag = UserLeftFlag(false, 0)
|
||||
)
|
||||
}
|
||||
@ -206,6 +205,8 @@ trait HandlerHelpers extends SystemConfiguration {
|
||||
|
||||
val endedEvnt = buildMeetingEndedEvtMsg(liveMeeting.props.meetingProp.intId)
|
||||
outGW.send(endedEvnt)
|
||||
|
||||
MeetingDAO.setMeetingEnded(liveMeeting.props.meetingProp.intId, reason, userId)
|
||||
}
|
||||
|
||||
def destroyMeeting(eventBus: InternalEventBus, meetingId: String): Unit = {
|
||||
|
@ -76,6 +76,7 @@ class MeetingActor(
|
||||
|
||||
with UserJoinMeetingReqMsgHdlr
|
||||
with UserJoinMeetingAfterReconnectReqMsgHdlr
|
||||
with UserEstablishedGraphqlConnectionInternalMsgHdlr
|
||||
with UserConnectedToGlobalAudioMsgHdlr
|
||||
with UserDisconnectedFromGlobalAudioMsgHdlr
|
||||
with MuteAllExceptPresentersCmdMsgHdlr
|
||||
@ -266,8 +267,14 @@ class MeetingActor(
|
||||
// internal messages
|
||||
case msg: MonitorNumberOfUsersInternalMsg => handleMonitorNumberOfUsers(msg)
|
||||
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 =>
|
||||
if (!liveMeeting.props.meetingProp.isBreakout) {
|
||||
// Update users of meeting remaining time.
|
||||
@ -395,10 +402,12 @@ class MeetingActor(
|
||||
case m: UserReactionTimeExpiredCmdMsg => handleUserReactionTimeExpiredCmdMsg(m)
|
||||
case m: ClearAllUsersEmojiCmdMsg => handleClearAllUsersEmojiCmdMsg(m)
|
||||
case m: ClearAllUsersReactionCmdMsg => handleClearAllUsersReactionCmdMsg(m)
|
||||
case m: SelectRandomViewerReqMsg => usersApp.handleSelectRandomViewerReqMsg(m)
|
||||
case m: ChangeUserPinStateReqMsg => usersApp.handleChangeUserPinStateReqMsg(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: SetUserSpeechOptionsReqMsg => usersApp.handleSetUserSpeechOptionsReqMsg(m)
|
||||
|
||||
// Client requested to eject user
|
||||
case m: EjectUserFromMeetingCmdMsg =>
|
||||
@ -582,6 +591,7 @@ class MeetingActor(
|
||||
|
||||
// AudioCaptions
|
||||
case m: UpdateTranscriptPubMsg => audioCaptionsApp2x.handle(m, liveMeeting, msgBus)
|
||||
case m: TranscriptionProviderErrorMsg => audioCaptionsApp2x.handleTranscriptionProviderErrorMsg(m, liveMeeting, msgBus)
|
||||
|
||||
// GroupChat
|
||||
case m: CreateGroupChatReqMsg =>
|
||||
@ -594,7 +604,9 @@ class MeetingActor(
|
||||
updateUserLastActivity(m.body.msg.sender.id)
|
||||
|
||||
// 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
|
||||
case m: UserBroadcastCamStartMsg => webcamApp2x.handle(m, liveMeeting, msgBus)
|
||||
@ -973,6 +985,7 @@ class MeetingActor(
|
||||
|
||||
val secsToDisconnect = TimeUnit.MILLISECONDS.toSeconds(expiryTracker.userActivitySignResponseDelayInMs);
|
||||
Sender.sendUserInactivityInspectMsg(liveMeeting.props.meetingProp.intId, u.intId, secsToDisconnect, outGW)
|
||||
UserStateDAO.updateInactivityWarning(u.intId, inactivityWarningDisplay = true, secsToDisconnect)
|
||||
updateUserLastInactivityInspect(u.intId)
|
||||
}
|
||||
}
|
||||
|
@ -78,6 +78,8 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
|
||||
case m: AssignPresenterReqMsg => logMessage(msg)
|
||||
case m: ChangeUserPinStateReqMsg => logMessage(msg)
|
||||
case m: ChangeUserMobileFlagReqMsg => logMessage(msg)
|
||||
case m: UserConnectionAliveReqMsg => logMessage(msg)
|
||||
case m: UserConnectionUpdateRttReqMsg => logMessage(msg)
|
||||
case m: ScreenshareRtmpBroadcastStartedVoiceConfEvtMsg => logMessage(msg)
|
||||
case m: ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsg => logMessage(msg)
|
||||
case m: ScreenshareRtmpBroadcastStartedEvtMsg => logMessage(msg)
|
||||
|
@ -256,6 +256,16 @@ object MsgBuilder {
|
||||
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 = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(EjectAllFromVoiceConfMsg.NAME, routing)
|
||||
|
@ -69,8 +69,7 @@ trait FakeTestData {
|
||||
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,
|
||||
emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, locked = false, presenter = false,
|
||||
avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown",
|
||||
pickExempted = false, userLeftFlag = UserLeftFlag(false, 0))
|
||||
avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown", userLeftFlag = UserLeftFlag(false, 0))
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -73,6 +73,7 @@ class ExportAnnotationsActor(
|
||||
private def handleStoreExportJobInRedisSysMsg(msg: StoreExportJobInRedisSysMsg) {
|
||||
val ev = new StoreExportJobInRedisPresAnnEvent()
|
||||
|
||||
ev.setserverSideFilename(msg.body.exportJob.serverSideFilename)
|
||||
ev.setJobId(msg.body.exportJob.jobId)
|
||||
ev.setJobType(msg.body.exportJob.jobType)
|
||||
ev.setFilename(msg.body.exportJob.filename)
|
||||
|
@ -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)
|
||||
}
|
||||
|
||||
}
|
@ -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
|
||||
}
|
||||
|
||||
}
|
@ -20,6 +20,7 @@ case class Meeting(
|
||||
intId: String,
|
||||
extId: String,
|
||||
name: String,
|
||||
downloadSessionDataEnabled: Boolean,
|
||||
users: Map[String, User] = Map(),
|
||||
polls: Map[String, Poll] = Map(),
|
||||
screenshares: Vector[Screenshare] = Vector(),
|
||||
@ -585,6 +586,7 @@ class LearningDashboardActor(
|
||||
msg.body.props.meetingProp.intId,
|
||||
msg.body.props.meetingProp.extId,
|
||||
msg.body.props.meetingProp.name,
|
||||
downloadSessionDataEnabled = !msg.body.props.meetingProp.disabledFeatures.contains("learningDashboardDownloadSessionData"),
|
||||
)
|
||||
|
||||
meetings += (newMeeting.intId -> newMeeting)
|
||||
|
@ -85,6 +85,9 @@ class RedisRecorderActor(
|
||||
case m: UserLeftMeetingEvtMsg => handleUserLeftMeetingEvtMsg(m)
|
||||
case m: PresenterAssignedEvtMsg => handlePresenterAssignedEvtMsg(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: UserBroadcastCamStartedEvtMsg => handleUserBroadcastCamStartedEvtMsg(m)
|
||||
case m: UserBroadcastCamStoppedEvtMsg => handleUserBroadcastCamStoppedEvtMsg(m)
|
||||
@ -112,7 +115,7 @@ class RedisRecorderActor(
|
||||
//case m: DeskShareNotifyViewersRTMP => handleDeskShareNotifyViewersRTMP(m)
|
||||
|
||||
// AudioCaptions
|
||||
case m: TranscriptUpdatedEvtMsg => handleTranscriptUpdatedEvtMsg(m)
|
||||
//case m: TranscriptUpdatedEvtMsg => handleTranscriptUpdatedEvtMsg(m) // temporarily disabling due to issue https://github.com/bigbluebutton/bigbluebutton/issues/19701
|
||||
|
||||
// Meeting
|
||||
case m: RecordingStatusChangedEvtMsg => handleRecordingStatusChangedEvtMsg(m)
|
||||
@ -379,6 +382,18 @@ class RedisRecorderActor(
|
||||
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) {
|
||||
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) {
|
||||
val ev = new TranscriptUpdatedRecordEvent()
|
||||
ev.setMeetingId(msg.header.meetingId)
|
||||
@ -529,6 +545,7 @@ class RedisRecorderActor(
|
||||
|
||||
record(msg.header.meetingId, ev.toMap.asJava)
|
||||
}
|
||||
*/
|
||||
|
||||
private def handleStartExternalVideoEvtMsg(msg: StartExternalVideoEvtMsg) {
|
||||
val ev = new StartExternalVideoRecordEvent()
|
||||
|
@ -51,7 +51,7 @@ object TestDataGen {
|
||||
guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus,
|
||||
emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, pin = false, mobile = false,
|
||||
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)
|
||||
u
|
||||
}
|
||||
|
@ -51,6 +51,7 @@ postgres {
|
||||
}
|
||||
numThreads = 1
|
||||
maxConnections = 1
|
||||
queueSize = 20000
|
||||
}
|
||||
|
||||
|
||||
@ -64,6 +65,7 @@ expire {
|
||||
services {
|
||||
bbbWebAPI = "https://192.168.23.33/bigbluebutton/api"
|
||||
sharedSecret = "changeme"
|
||||
checkSumAlgorithmForBreakouts = "sha256"
|
||||
}
|
||||
|
||||
eventBus {
|
||||
|
@ -14,7 +14,7 @@ object Dependencies {
|
||||
// Libraries
|
||||
val pekkoVersion = "1.0.1"
|
||||
val pekkoHttpVersion = "1.0.0"
|
||||
val logback = "1.2.10"
|
||||
val logback = "1.2.13"
|
||||
|
||||
// Apache Commons
|
||||
val lang = "3.12.0"
|
||||
|
@ -70,6 +70,9 @@ case class LockSettingsProps(
|
||||
case class SystemProps(
|
||||
html5InstanceId: Int,
|
||||
logoutUrl: String,
|
||||
customLogoURL: String,
|
||||
bannerText: String,
|
||||
bannerColor: String,
|
||||
)
|
||||
|
||||
case class GroupProps(
|
||||
|
@ -1,5 +1,12 @@
|
||||
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
|
||||
object UpdateTranscriptPubMsg { val NAME = "UpdateTranscriptPubMsg" }
|
||||
case class UpdateTranscriptPubMsg(header: BbbClientMsgHeader, body: UpdateTranscriptPubMsgBody) extends StandardMsg
|
||||
@ -14,6 +21,10 @@ case class UpdateTranscriptPubMsgBody(
|
||||
)
|
||||
|
||||
// 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" }
|
||||
case class TranscriptUpdatedEvtMsg(header: BbbClientMsgHeader, body: TranscriptUpdatedEvtMsgBody) extends BbbCoreMsg
|
||||
case class TranscriptUpdatedEvtMsgBody(transcriptId: String, transcript: String, locale: String, result: Boolean)
|
||||
|
@ -15,7 +15,7 @@ object GroupChatMessageType {
|
||||
}
|
||||
|
||||
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 GroupChatInfo(id: String, access: String, createdBy: GroupChatUser, users: Vector[GroupChatUser])
|
||||
|
||||
|
@ -107,7 +107,7 @@ case class PadTailEvtMsgBody(externalId: String, tail: String)
|
||||
// client -> apps
|
||||
object PadUpdatePubMsg { val NAME = "PadUpdatePubMsg" }
|
||||
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
|
||||
object PadUpdateCmdMsg { val NAME = "PadUpdateCmdMsg" }
|
||||
|
@ -5,12 +5,28 @@ package org.bigbluebutton.common2.msgs
|
||||
/**
|
||||
* Sent from graphql-actions to bbb-akka
|
||||
*/
|
||||
object DispatchPluginDataChannelMessageMsg { val NAME = "DispatchPluginDataChannelMessageMsg" }
|
||||
case class DispatchPluginDataChannelMessageMsg(header: BbbClientMsgHeader, body: DispatchPluginDataChannelMessageMsgBody) extends StandardMsg
|
||||
case class DispatchPluginDataChannelMessageMsgBody(
|
||||
object PluginDataChannelDispatchMessageMsg { val NAME = "PluginDataChannelDispatchMessageMsg" }
|
||||
case class PluginDataChannelDispatchMessageMsg(header: BbbClientMsgHeader, body: PluginDataChannelDispatchMessageMsgBody) extends StandardMsg
|
||||
case class PluginDataChannelDispatchMessageMsgBody(
|
||||
pluginName: String,
|
||||
dataChannel: String,
|
||||
payloadJson: String,
|
||||
toRoles: 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
|
||||
)
|
||||
|
@ -17,7 +17,7 @@ case class MakePresentationDownloadReqMsgBody(presId: String, allPages: Boolean,
|
||||
object NewPresFileAvailableMsg { val NAME = "NewPresFileAvailableMsg" }
|
||||
case class NewPresFileAvailableMsg(header: BbbClientMsgHeader, body: NewPresFileAvailableMsgBody) extends StandardMsg
|
||||
case class NewPresFileAvailableMsgBody(annotatedFileURI: String, originalFileURI: String, convertedFileURI: String,
|
||||
presId: String, fileStateType: String)
|
||||
presId: String, fileStateType: String, fileName: String)
|
||||
|
||||
object PresAnnStatusMsg { val NAME = "PresAnnStatusMsg" }
|
||||
case class PresAnnStatusMsg(header: BbbClientMsgHeader, body: PresAnnStatusMsgBody) extends StandardMsg
|
||||
|
@ -232,6 +232,26 @@ object DeletedRecordingSysMsg { val NAME = "DeletedRecordingSysMsg" }
|
||||
case class DeletedRecordingSysMsg(header: BbbCoreBaseHeader, body: DeletedRecordingSysMsgBody) extends BbbCoreMsg
|
||||
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
|
||||
*/
|
||||
@ -251,21 +271,21 @@ case class UserGraphqlReconnectionForcedEvtMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: UserGraphqlReconnectionForcedEvtMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class UserGraphqlReconnectionForcedEvtMsgBody(sessionToken: String, browserConnectionId: String)
|
||||
case class UserGraphqlReconnectionForcedEvtMsgBody(middlewareUID: String, sessionToken: String, browserConnectionId: String)
|
||||
|
||||
object UserGraphqlConnectionEstablishedSysMsg { val NAME = "UserGraphqlConnectionEstablishedSysMsg" }
|
||||
case class UserGraphqlConnectionEstablishedSysMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: UserGraphqlConnectionEstablishedSysMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class UserGraphqlConnectionEstablishedSysMsgBody(sessionToken: String, browserConnectionId: String)
|
||||
case class UserGraphqlConnectionEstablishedSysMsgBody(middlewareUID: String, sessionToken: String, browserConnectionId: String)
|
||||
|
||||
object UserGraphqlConnectionClosedSysMsg { val NAME = "UserGraphqlConnectionClosedSysMsg" }
|
||||
case class UserGraphqlConnectionClosedSysMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: UserGraphqlConnectionClosedSysMsgBody
|
||||
) 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
|
||||
|
@ -19,7 +19,7 @@ case class StartTimerReqMsgBody()
|
||||
|
||||
object StopTimerReqMsg { val NAME = "StopTimerReqMsg" }
|
||||
case class StopTimerReqMsg(header: BbbClientMsgHeader, body: StopTimerReqMsgBody) extends StandardMsg
|
||||
case class StopTimerReqMsgBody(accumulated: Int)
|
||||
case class StopTimerReqMsgBody()
|
||||
|
||||
object SwitchTimerReqMsg { val NAME = "SwitchTimerReqMsg" }
|
||||
case class SwitchTimerReqMsg(header: BbbClientMsgHeader, body: SwitchTimerReqMsgBody) extends StandardMsg
|
||||
|
@ -293,6 +293,20 @@ object ChangeUserMobileFlagReqMsg { val NAME = "ChangeUserMobileFlagReqMsg" }
|
||||
case class ChangeUserMobileFlagReqMsg(header: BbbClientMsgHeader, body: ChangeUserMobileFlagReqMsgBody) extends StandardMsg
|
||||
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.
|
||||
*/
|
||||
@ -511,20 +525,6 @@ object UserActivitySignCmdMsg { val NAME = "UserActivitySignCmdMsg" }
|
||||
case class UserActivitySignCmdMsg(header: BbbClientMsgHeader, body: UserActivitySignCmdMsgBody) extends StandardMsg
|
||||
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" }
|
||||
case class SetUserSpeechLocaleReqMsg(header: BbbClientMsgHeader, body: SetUserSpeechLocaleReqMsgBody) extends StandardMsg
|
||||
case class SetUserSpeechLocaleReqMsgBody(locale: String, provider: String)
|
||||
@ -532,3 +532,11 @@ case class SetUserSpeechLocaleReqMsgBody(locale: String, provider: String)
|
||||
object UserSpeechLocaleChangedEvtMsg { val NAME = "UserSpeechLocaleChangedEvtMsg" }
|
||||
case class UserSpeechLocaleChangedEvtMsg(header: BbbClientMsgHeader, body: UserSpeechLocaleChangedEvtMsgBody) extends BbbCoreMsg
|
||||
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)
|
||||
|
@ -22,6 +22,7 @@ case class ExportJob(
|
||||
jobId: String,
|
||||
jobType: String,
|
||||
filename: String,
|
||||
serverSideFilename: String,
|
||||
presId: String,
|
||||
presLocation: String,
|
||||
allPages: Boolean,
|
||||
|
@ -103,10 +103,10 @@ homepage := Some(url("http://www.bigbluebutton.org"))
|
||||
|
||||
libraryDependencies ++= Seq(
|
||||
"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.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.flywaydb" % "flyway-core" % "7.8.2",
|
||||
"com.zaxxer" % "HikariCP" % "4.0.3",
|
||||
|
@ -75,6 +75,7 @@ public class MeetingService implements MessageListener {
|
||||
*/
|
||||
private final ConcurrentMap<String, Meeting> meetings;
|
||||
private final ConcurrentMap<String, UserSession> sessions;
|
||||
private final ConcurrentMap<String, UserSessionBasicData> removedSessions;
|
||||
|
||||
private RecordingService recordingService;
|
||||
private LearningDashboardService learningDashboardService;
|
||||
@ -88,6 +89,7 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
private long usersTimeout;
|
||||
private long waitingGuestUsersTimeout;
|
||||
private int sessionsCleanupDelayInMinutes;
|
||||
private long enteredUsersTimeout;
|
||||
|
||||
private ParamsProcessorUtil paramsProcessorUtil;
|
||||
@ -100,6 +102,7 @@ public class MeetingService implements MessageListener {
|
||||
public MeetingService() {
|
||||
meetings = new ConcurrentHashMap<String, Meeting>(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>();
|
||||
}
|
||||
|
||||
@ -149,12 +152,16 @@ public class MeetingService implements MessageListener {
|
||||
return null;
|
||||
}
|
||||
|
||||
public UserSession getUserSessionWithAuthToken(String token) {
|
||||
public UserSession getUserSessionWithSessionToken(String token) {
|
||||
return sessions.get(token);
|
||||
}
|
||||
|
||||
public UserSessionBasicData getRemovedUserSessionWithSessionToken(String sessionToken) {
|
||||
return removedSessions.get(sessionToken);
|
||||
}
|
||||
|
||||
public Boolean getAllowRequestsWithoutSession(String token) {
|
||||
UserSession us = getUserSessionWithAuthToken(token);
|
||||
UserSession us = getUserSessionWithSessionToken(token);
|
||||
if (us == null) {
|
||||
return false;
|
||||
} else {
|
||||
@ -164,12 +171,22 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
}
|
||||
|
||||
public UserSession removeUserSessionWithAuthToken(String token) {
|
||||
UserSession user = sessions.remove(token);
|
||||
if (user != null) {
|
||||
log.debug("Found user {} token={} to meeting {}", user.fullname, token, user.meetingID);
|
||||
public void removeUserSessionWithSessionToken(String token) {
|
||||
log.debug("Removing token={}", token);
|
||||
UserSession us = getUserSessionWithSessionToken(token);
|
||||
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,16 +312,40 @@ public class MeetingService implements MessageListener {
|
||||
notifier.sendUploadFileTooLargeMessage(presUploadToken, uploadedFileSize, maxUploadFileSize);
|
||||
}
|
||||
|
||||
private void removeUserSessions(String meetingId) {
|
||||
Iterator<Map.Entry<String, UserSession>> iterator = sessions.entrySet().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Map.Entry<String, UserSession> entry = iterator.next();
|
||||
UserSession userSession = entry.getValue();
|
||||
|
||||
private void removeUserSessionsFromMeeting(String meetingId) {
|
||||
for (String token : sessions.keySet()) {
|
||||
UserSession userSession = sessions.get(token);
|
||||
if (userSession.meetingID.equals(meetingId)) {
|
||||
iterator.remove();
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}, cleanUpDelayCalendar.getTime()
|
||||
);
|
||||
}
|
||||
|
||||
private void destroyMeeting(String meetingId) {
|
||||
@ -411,8 +452,8 @@ public class MeetingService implements MessageListener {
|
||||
m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(),
|
||||
m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(),
|
||||
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(),
|
||||
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(), m.getLogoutUrl(),
|
||||
m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
|
||||
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(), m.getLogoutUrl(), m.getCustomLogoURL(),
|
||||
m.getBannerText(), m.getBannerColor(), m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
|
||||
m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl(),
|
||||
m.getOverrideClientSettings());
|
||||
}
|
||||
@ -703,7 +744,7 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
destroyMeeting(m.getInternalId());
|
||||
meetings.remove(m.getInternalId());
|
||||
removeUserSessions(m.getInternalId());
|
||||
removeUserSessionsFromMeeting(m.getInternalId());
|
||||
|
||||
Map<String, Object> logData = new HashMap<>();
|
||||
logData.put("meetingId", m.getInternalId());
|
||||
@ -1111,7 +1152,7 @@ public class MeetingService implements MessageListener {
|
||||
user.setRole(message.role);
|
||||
String sessionToken = getTokenByUserId(user.getInternalUserId());
|
||||
if (sessionToken != null) {
|
||||
UserSession userSession = getUserSessionWithAuthToken(sessionToken);
|
||||
UserSession userSession = getUserSessionWithSessionToken(sessionToken);
|
||||
userSession.role = message.role;
|
||||
sessions.replace(sessionToken, userSession);
|
||||
}
|
||||
@ -1184,6 +1225,10 @@ public class MeetingService implements MessageListener {
|
||||
processGuestStatusChangedEventMsg((GuestStatusChangedEventMsg) message);
|
||||
} else if (message instanceof GuestPolicyChanged) {
|
||||
processGuestPolicyChanged((GuestPolicyChanged) message);
|
||||
} else if (message instanceof LockSettingsChanged) {
|
||||
processLockSettingsChanged((LockSettingsChanged) message);
|
||||
} else if (message instanceof WebcamsOnlyForModeratorChanged) {
|
||||
processWebcamsOnlyForModeratorChanged((WebcamsOnlyForModeratorChanged) message);
|
||||
} else if (message instanceof GuestLobbyMessageChanged) {
|
||||
processGuestLobbyMessageChanged((GuestLobbyMessageChanged) message);
|
||||
} 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) {
|
||||
Meeting m = getMeeting(msg.meetingId);
|
||||
HashMap<String,String> guestUsers = msg.guests;
|
||||
@ -1333,6 +1404,10 @@ public class MeetingService implements MessageListener {
|
||||
waitingGuestUsersTimeout = value;
|
||||
}
|
||||
|
||||
public void setSessionsCleanupDelayInMinutes(int value) {
|
||||
sessionsCleanupDelayInMinutes = value;
|
||||
}
|
||||
|
||||
public void setEnteredUsersTimeout(long value) {
|
||||
enteredUsersTimeout = value;
|
||||
}
|
||||
|
@ -70,6 +70,8 @@ public class ParamsProcessorUtil {
|
||||
private String defaultServerUrl;
|
||||
private int defaultNumDigitsForTelVoice;
|
||||
private String defaultHTML5ClientUrl;
|
||||
|
||||
private String graphqlWebsocketUrl;
|
||||
private String defaultGuestWaitURL;
|
||||
private Boolean allowRequestsWithoutSession = false;
|
||||
private Integer defaultHttpSessionTimeout = 14400;
|
||||
@ -864,6 +866,10 @@ public class ParamsProcessorUtil {
|
||||
return defaultHTML5ClientUrl;
|
||||
}
|
||||
|
||||
public String getGraphqlWebsocketUrl() {
|
||||
return graphqlWebsocketUrl;
|
||||
}
|
||||
|
||||
public String getDefaultGuestWaitURL() {
|
||||
return defaultGuestWaitURL;
|
||||
}
|
||||
@ -1217,6 +1223,10 @@ public class ParamsProcessorUtil {
|
||||
this.defaultHTML5ClientUrl = defaultHTML5ClientUrl;
|
||||
}
|
||||
|
||||
public void setGraphqlWebsocketUrl(String graphqlWebsocketUrl) {
|
||||
this.graphqlWebsocketUrl = graphqlWebsocketUrl.replace("https://","wss://");
|
||||
}
|
||||
|
||||
public void setDefaultGuestWaitURL(String url) {
|
||||
this.defaultGuestWaitURL = url;
|
||||
}
|
||||
|
@ -110,7 +110,7 @@ public class Meeting {
|
||||
private Integer endWhenNoModeratorDelayInMinutes = 1;
|
||||
|
||||
public final BreakoutRoomsParams breakoutRoomsParams;
|
||||
public final LockSettingsParams lockSettingsParams;
|
||||
public LockSettingsParams lockSettingsParams;
|
||||
|
||||
public final Integer maxUserConcurrentAccesses;
|
||||
|
||||
@ -472,7 +472,15 @@ public class Meeting {
|
||||
}
|
||||
|
||||
public String getGuestPolicy() {
|
||||
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) {
|
||||
|
@ -137,6 +137,10 @@ public class User {
|
||||
public boolean isModerator() {
|
||||
return "MODERATOR".equalsIgnoreCase(this.role);
|
||||
}
|
||||
|
||||
public boolean isLocked() {
|
||||
return this.locked;
|
||||
}
|
||||
|
||||
public void setStatus(String key, String value){
|
||||
this.status.put(key, value);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -22,7 +22,7 @@ public class GuestPolicyValidator implements ConstraintValidator<GuestPolicyCons
|
||||
}
|
||||
|
||||
MeetingService meetingService = ServiceUtils.getMeetingService();
|
||||
UserSession userSession = meetingService.getUserSessionWithAuthToken(sessionToken);
|
||||
UserSession userSession = meetingService.getUserSessionWithSessionToken(sessionToken);
|
||||
|
||||
if(userSession == null || !userSession.guestStatus.equals(GuestPolicy.ALLOW)) {
|
||||
return false;
|
||||
|
@ -19,7 +19,7 @@ public class UserSessionValidator implements ConstraintValidator<UserSessionCons
|
||||
return false;
|
||||
}
|
||||
|
||||
UserSession userSession = ServiceUtils.getMeetingService().getUserSessionWithAuthToken(sessionToken);
|
||||
UserSession userSession = ServiceUtils.getMeetingService().getUserSessionWithSessionToken(sessionToken);
|
||||
|
||||
if(userSession == null) {
|
||||
return false;
|
||||
|
@ -22,7 +22,7 @@ public class SessionService {
|
||||
|
||||
private void getUserSessionWithToken() {
|
||||
if(sessionToken != null) {
|
||||
userSession = meetingService.getUserSessionWithAuthToken(sessionToken);
|
||||
userSession = meetingService.getUserSessionWithSessionToken(sessionToken);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -14,6 +14,9 @@ import javax.validation.Validation;
|
||||
import javax.validation.Validator;
|
||||
import javax.validation.ValidatorFactory;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.MalformedURLException;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.*;
|
||||
@ -76,6 +79,11 @@ public class ValidationService {
|
||||
|
||||
if(request == null) {
|
||||
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 {
|
||||
request.populateFromParamsMap(params);
|
||||
violations = performValidation(request);
|
||||
@ -84,6 +92,15 @@ public class ValidationService {
|
||||
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) {
|
||||
Request request = null;
|
||||
Checksum checksum;
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user