Merge pull request #19310 from antobinary/merge-dec-7

chore: Merge BBB 3.0.0-alpha.1 into develop
This commit is contained in:
Anton Georgiev 2023-12-07 16:15:57 -05:00 committed by GitHub
commit ecaf828c77
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
856 changed files with 24500 additions and 21545 deletions

View File

@ -13,6 +13,7 @@ on:
paths-ignore:
- "docs/**"
- "**/*.md"
- "bigbluebutton-html5/public/locales/*.json"
permissions:
contents: read
concurrency:
@ -53,7 +54,7 @@ jobs:
build-list: bbb-playback bbb-playback-notes bbb-playback-podcast bbb-playback-presentation bbb-playback-screenshare bbb-playback-video bbb-record-core
- package: bbb-graphql-server
build-name: bbb-graphql-server
build-list: bbb-graphql-server bbb-graphql-middleware
build-list: bbb-graphql-server bbb-graphql-middleware bbb-graphql-actions-adapter-server
- package: bbb-etherpad
cache-files-list: bbb-etherpad.placeholder.sh
cache-urls-list: https://api.github.com/repos/mconf/ep_pad_ttl/commits https://api.github.com/repos/alangecker/bbb-etherpad-plugin/commits https://api.github.com/repos/mconf/ep_redis_publisher/commits https://api.github.com/repos/alangecker/bbb-etherpad-skin/commits
@ -72,7 +73,7 @@ jobs:
build-list: bbb-webrtc-sfu bbb-webrtc-recorder
cache-files-list: bbb-webrtc-sfu.placeholder.sh bbb-webrtc-recorder.placeholder.sh
- package: others
build-list: bbb-mkclean bbb-pads bbb-libreoffice-docker bbb-transcription-controller bigbluebutton
build-list: bbb-mkclean bbb-pads bbb-libreoffice-docker bbb-transcription-controller bigbluebutton bbb-livekit
steps:
- uses: actions/checkout@v3
- name: Merge branches
@ -252,7 +253,7 @@ jobs:
apt --purge -y remove apache2-bin
'
- name: Install BBB
timeout-minutes: 15
timeout-minutes: 25
run: |
sudo -i <<EOF
set -e
@ -263,6 +264,12 @@ jobs:
sed -i "s/\"minify\": true,/\"minify\": false,/" /usr/share/etherpad-lite/settings.json
bbb-conf --restart
EOF
- name: List systemctl services
timeout-minutes: 1
run: |
sudo -i <<EOF
systemctl --type=service --state=running,exited,failed --all --no-pager --no-legend
EOF
- name: Install test dependencies
working-directory: ./bigbluebutton-tests/playwright
run: |
@ -272,13 +279,18 @@ jobs:
npx playwright install
'
- name: Run tests
working-directory: ./bigbluebutton-tests/playwright
uses: nick-fields/retry@v2
with:
timeout_minutes: 25
max_attempts: 3
command: |
cd ./bigbluebutton-tests/playwright
npm run test-chromium-ci -- --shard ${{ matrix.shard }}
env:
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
ACTIONS_RUNNER_DEBUG: true
BBB_URL: https://bbb-ci.test/bigbluebutton/api
BBB_SECRET: bbbci
run: npm run test-chromium-ci -- --shard ${{ matrix.shard }}
- name: Run Firefox tests
working-directory: ./bigbluebutton-tests/playwright
if: |

View File

@ -12,7 +12,7 @@ stages:
# define which docker image to use for builds
default:
image: bigbluebutton/bbb-build:bbb28-2023-07-18
image: bigbluebutton/bbb-build:v3.0.x-release--2023-09-26-152524
# This stage uses git to find out since when each package has been unmodified.
# it then checks an API endpoint on the package server to find out for which of
@ -182,6 +182,11 @@ bbb-webrtc-recorder-build:
script:
- build/setup-inside-docker.sh bbb-webrtc-recorder
bbb-livekit:
extends: .build_job
script:
- build/setup-inside-docker.sh bbb-livekit
bbb-transcription-controller-build:
extends: .build_job
script:

View File

@ -9,7 +9,7 @@ We actively support BigBlueButton through the community forums and through secur
| 2.5.x (or earlier) | :x: |
| 2.6.x   | :white_check_mark: |
| 2.7.x   | :white_check_mark: |
| 2.8.x   | :x: |
| 3.0.x   | :x: |
We have released 2.7 to the community and are going to support both 2.6 and 2.7 together for the coming months (while we're actively developing the next release). Also, BigBlueButton 2.5 is now end of life.

View File

@ -1,7 +1,6 @@
package org.bigbluebutton.build
import sbt._
import Keys._
object Dependencies {
@ -37,6 +36,7 @@ object Dependencies {
val scalaTest = "3.2.11"
val mockito = "2.23.0"
val akkaTestKit = "2.6.0"
val jacksonDataFormat = "2.13.5"
}
object Compile {
@ -64,7 +64,11 @@ object Dependencies {
val slick = "com.typesafe.slick" %% "slick" % Versions.slick
val slickHikaricp = "com.typesafe.slick" %% "slick-hikaricp" % Versions.slick
val slickPg = "com.github.tminglei" %% "slick-pg" % Versions.slickPg
val slickPgSprayJson = "com.github.tminglei" %% "slick-pg_spray-json" % Versions.slickPg
val postgresql = "org.postgresql" % "postgresql" % Versions.postgresql
val jacksonDataFormat = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-yaml" % Versions.jacksonDataFormat
val snakeYaml = "org.yaml" % "snakeyaml"
}
object Test {
@ -101,5 +105,7 @@ object Dependencies {
Compile.slick,
Compile.slickHikaricp,
Compile.slickPg,
Compile.postgresql) ++ testing
Compile.slickPgSprayJson,
Compile.postgresql,
Compile.jacksonDataFormat) ++ testing
}

View File

@ -10,9 +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
import org.bigbluebutton.endpoint.redis.{ RedisRecorderActor, ExportAnnotationsActor }
import org.bigbluebutton.endpoint.redis.LearningDashboardActor
import org.bigbluebutton.endpoint.redis.{AppsRedisSubscriberActor, ExportAnnotationsActor, GraphqlActionsActor, LearningDashboardActor, RedisRecorderActor}
import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
import org.bigbluebutton.service.{HealthzService, MeetingInfoActor, MeetingInfoService}
@ -69,6 +67,12 @@ object Boot extends App with SystemConfiguration {
"LearningDashboardActor"
)
val graphqlActionsActor = system.actorOf(
GraphqlActionsActor.props(system),
"GraphqlActionsActor"
)
ClientSettings.loadClientSettingsFromFile()
recordingEventBus.subscribe(redisRecorderActor, outMessageChannel)
val incomingJsonMessageBus = new IncomingJsonMessageBus
@ -85,6 +89,9 @@ object Boot extends App with SystemConfiguration {
outBus2.subscribe(learningDashboardActor, outBbbMsgMsgChannel)
bbbMsgBus.subscribe(learningDashboardActor, analyticsChannel)
eventBus.subscribe(graphqlActionsActor, meetingManagerChannel)
bbbMsgBus.subscribe(graphqlActionsActor, analyticsChannel)
val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW, healthzService), "bigbluebutton-actor")
eventBus.subscribe(bbbActor, meetingManagerChannel)

View File

@ -0,0 +1,118 @@
package org.bigbluebutton
import org.bigbluebutton.common2.util.YamlUtil
import org.slf4j.LoggerFactory
import java.io.{ ByteArrayInputStream, File }
import scala.io.BufferedSource
import scala.util.{ Failure, Success, Try }
object ClientSettings extends SystemConfiguration {
var clientSettingsFromFile: Map[String, Object] = Map("" -> "")
val logger = LoggerFactory.getLogger(this.getClass)
def loadClientSettingsFromFile() = {
val clientSettingsFile = scala.io.Source.fromFile(clientSettingsPath, "UTF-8")
val clientSettingsFileOverrideToCheck = new File(clientSettingsPathOverride)
val clientSettingsFileOverride = if (clientSettingsFileOverrideToCheck.exists())
scala.io.Source.fromFile(
clientSettingsPathOverride,
"UTF-8"
)
else new BufferedSource(new ByteArrayInputStream(Array[Byte]()))
clientSettingsFromFile =
common2.util.YamlUtil.mergeImmutableMaps(
common2.util.YamlUtil.toMap[Object](clientSettingsFile.mkString) match {
case Success(value) => value
case Failure(exception) =>
println("Error while fetching client Settings: ", exception)
Map[String, Object]()
},
common2.util.YamlUtil.toMap[Object](clientSettingsFileOverride.mkString) match {
case Success(value) => value
case Failure(exception) =>
println("Error while fetching client override Settings: ", exception)
Map[String, Object]()
}
)
}
def getClientSettingsWithOverride(clientSettingsOverrideJson: String): Map[String, Object] = {
if (clientSettingsOverrideJson.nonEmpty) {
val scalaMapClientOverride = common2.util.JsonUtil.toMap[Object](clientSettingsOverrideJson)
scalaMapClientOverride match {
case Success(clientSettingsOverrideAsMap) => YamlUtil.mergeImmutableMaps(clientSettingsFromFile, clientSettingsOverrideAsMap)
case Failure(msg) =>
logger.debug("No valid JSON override of client configuration in create call: {}", msg)
clientSettingsFromFile
}
} else clientSettingsFromFile
}
def getConfigPropertyValueByPath(map: Map[String, Any], path: String): Option[Any] = {
val keys = path.split("\\.")
def getRecursive(map: Map[String, Any], keys: Seq[String]): Option[Any] = {
keys match {
case Seq(head, tail @ _*) =>
map.get(head) match {
case Some(innerMap: Map[String, Any]) => getRecursive(innerMap, tail)
case otherValue if tail.isEmpty => otherValue
case _ => None
}
case _ => None
}
}
getRecursive(map, keys)
}
def getPluginsFromConfig(config: Map[String, Any]): Map[String, Plugin] = {
var pluginsFromConfig: Map[String, Plugin] = Map()
val pluginsConfig = getConfigPropertyValueByPath(config, "public.plugins")
pluginsConfig match {
case Some(plugins: List[Map[String, Any]]) =>
for {
plugin <- plugins
} yield {
if (plugin.contains("name") && plugin.contains("url")) {
val pluginName = plugin("name").toString
val pluginUrl = plugin("url").toString
var pluginDataChannels: Map[String, DataChannel] = Map()
if (plugin.contains("dataChannels")) {
plugin("dataChannels") match {
case dataChannels: List[Map[String, Any]] =>
for {
dataChannel <- dataChannels
} yield {
if (dataChannel.contains("name") && dataChannel.contains("writePermission")) {
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")
}
}
}
case _ => logger.warn(s"Plugin $pluginName has an invalid dataChannels format")
}
}
pluginsFromConfig += (pluginName -> Plugin(pluginName, pluginUrl, pluginDataChannels))
}
}
case _ => logger.warn(s"Invalid plugins config found.")
}
pluginsFromConfig
}
case class DataChannel(name: String, writePermission: List[String])
case class Plugin(name: String, url: String, dataChannels: Map[String, DataChannel])
}

View File

@ -1,6 +1,9 @@
package org.bigbluebutton
import scala.util.Try
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import scala.util.{ Failure, Success, Try }
import com.typesafe.config.ConfigFactory
trait SystemConfiguration {
@ -77,6 +80,13 @@ trait SystemConfiguration {
lazy val analyticsIncludeChat = Try(config.getBoolean("analytics.includeChat")).getOrElse(true)
lazy val clientSettingsPath = Try(config.getString("client.clientSettingsFilePath")).getOrElse(
"/usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml"
)
lazy val clientSettingsPathOverride = Try(config.getString("client.clientSettingsOverrideFilePath")).getOrElse(
"/etc/bigbluebutton/bbb-html5.yml"
)
// Grab the "interface" parameter from the http config
val httpHost = config.getString("http.interface")
// Grab the "port" parameter from the http config

View File

@ -81,6 +81,8 @@ class BigBlueButtonActor(
case m: GetRunningMeetingsReqMsg => handleGetRunningMeetingsReqMsg(m)
case m: CheckAlivePingSysMsg => handleCheckAlivePingSysMsg(m)
case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m)
case _: UserGraphqlConnectionStablishedSysMsg => //Ignore
case _: UserGraphqlConnectionClosedSysMsg => //Ignore
case _ => log.warning("Cannot handle " + msg.envelope.name)
}
}

View File

@ -113,6 +113,12 @@ case class EjectUserFromBreakoutInternalMsg(parentId: String, breakoutId: String
*/
case class CapturePresentationReqInternalMsg(userId: String, parentMeetingId: String, filename: String, allPages: Boolean = true) extends InMessage
/**
* Sent to the same meeting to force a new presenter to the Pod
* @param presenterId
*/
case class SetPresenterInDefaultPodInternalMsg(presenterId: String) extends InMessage
/**
* Sent by breakout room to parent meeting to obtain padId
* @param breakoutId

View File

@ -16,7 +16,7 @@ object TimerModel {
def reset(model: TimerModel) : Unit = {
model.accumulated = 0
model.startedAt = 0
model.startedAt = if (model.running) System.currentTimeMillis() else 0
model.endedAt = 0
}
@ -24,7 +24,7 @@ object TimerModel {
model.isActive = active
}
def getIsACtive(model: TimerModel): Boolean = {
def getIsActive(model: TimerModel): Boolean = {
model.isActive
}

View File

@ -2,8 +2,8 @@ package org.bigbluebutton.core.apps.audiocaptions
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.AudioCaptionDAO
import org.bigbluebutton.core.models.AudioCaptions
import org.bigbluebutton.core.db.CaptionDAO
import org.bigbluebutton.core.models.{AudioCaptions, Users2x}
import org.bigbluebutton.core.running.LiveMeeting
import java.sql.Timestamp
@ -66,7 +66,12 @@ trait UpdateTranscriptPubMsgHdlr {
val transcript = AudioCaptions.parseTranscript(msg.body.transcript)
AudioCaptionDAO.insertOrUpdateAudioCaption(msg.body.transcriptId, meetingId, msg.header.userId, transcript, new Timestamp(System.currentTimeMillis()))
for {
u <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
CaptionDAO.insertOrUpdateAudioCaption(msg.body.transcriptId, meetingId, msg.header.userId, transcript, u.speechLocale)
}
broadcastEvent(
msg.header.userId,

View File

@ -70,7 +70,7 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
breakout.freeJoin,
liveMeeting.props.voiceProp.dialNumber,
breakout.voiceConf,
msg.body.durationInMinutes * 60,
msg.body.durationInMinutes,
liveMeeting.props.password.moderatorPass,
liveMeeting.props.password.viewerPass,
presId, presSlide, msg.body.record,

View File

@ -4,7 +4,7 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.{ SendTimeRemainingAuditInternalMsg, UpdateBreakoutRoomTimeInternalMsg }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.db.BreakoutRoomDAO
import org.bigbluebutton.core.db.{ BreakoutRoomDAO, MeetingDAO }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
@ -78,6 +78,7 @@ trait UpdateBreakoutRoomsTimeMsgHdlr extends RightsManagementTrait {
log.debug("Updating {} minutes for breakout rooms time in meeting {}", msg.body.timeInMinutes, props.meetingProp.intId)
BreakoutRoomDAO.updateRoomsDuration(props.meetingProp.intId, newDurationInSeconds)
MeetingDAO.updateMeetingDurationByParentMeeting(props.meetingProp.intId, newDurationInSeconds)
breakoutModel.setTime(newDurationInSeconds)
}
}

View File

@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.pads
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.CaptionDAO
import org.bigbluebutton.core.models.Pads
import org.bigbluebutton.core.running.LiveMeeting
@ -40,6 +41,7 @@ trait PadPatchSysMsgHdlr {
broadcastEditCaptionHistoryEvent(msg.body.userId, msg.body.start, msg.body.end, group.name, locale, msg.body.text)
val tail = liveMeeting.captionModel.getTextTail(group.name)
broadcastPadTailEvent(group.externalId, tail)
CaptionDAO.insertOrUpdatePadCaption(liveMeeting.props.meetingProp.intId, locale, msg.body.userId, tail)
}
}
case _ =>

View File

@ -0,0 +1,54 @@
package org.bigbluebutton.core.apps.plugin
import org.bigbluebutton.ClientSettings
import org.bigbluebutton.common2.msgs._
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 DispatchPluginDataChannelMessageMsgHdlr extends HandlerHelpers {
def handle(msg: DispatchPluginDataChannelMessageMsg, 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 {
writePermission <- pluginsConfig(msg.body.pluginName).dataChannels(msg.body.dataChannel).writePermission
} yield {
writePermission.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 write in plugin: '${msg.body.pluginName}', data channel: '${msg.body.dataChannel}'.")
} else {
PluginDataChannelMessageDAO.insert(
meetingId,
msg.body.pluginName,
msg.body.dataChannel,
msg.header.userId,
msg.body.payloadJson,
msg.body.toRoles,
msg.body.toUserIds
)
}
}
}
}
}

View File

@ -0,0 +1,10 @@
package org.bigbluebutton.core.apps.plugin
import org.apache.pekko.actor.ActorContext
import org.apache.pekko.event.Logging
class PluginHdlrs(implicit val context: ActorContext)
extends DispatchPluginDataChannelMessageMsgHdlr {
val log = Logging(context.system, getClass)
}

View File

@ -6,7 +6,7 @@ import org.bigbluebutton.core.apps.groupchats.GroupChatApp
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.apps.presentationpod.PresentationSender
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.ChatMessageDAO
import org.bigbluebutton.core.db.{ ChatMessageDAO, PresPresentationDAO }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.util.RandomStringGenerator
@ -53,12 +53,13 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
def buildBroadcastNewPresFileAvailable(newPresFileAvailableMsg: NewPresFileAvailableMsg, liveMeeting: LiveMeeting): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, "not-used")
val envelope = BbbCoreEnvelope(PresentationPageConvertedEventMsg.NAME, routing)
val envelope = BbbCoreEnvelope(NewPresFileAvailableEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(NewPresFileAvailableEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
val body = NewPresFileAvailableEvtMsgBody(
annotatedFileURI = newPresFileAvailableMsg.body.annotatedFileURI,
originalFileURI = newPresFileAvailableMsg.body.originalFileURI,
convertedFileURI = newPresFileAvailableMsg.body.convertedFileURI, presId = newPresFileAvailableMsg.body.presId,
convertedFileURI = newPresFileAvailableMsg.body.convertedFileURI,
presId = newPresFileAvailableMsg.body.presId,
fileStateType = newPresFileAvailableMsg.body.fileStateType
)
val event = NewPresFileAvailableEvtMsg(header, body)
@ -85,11 +86,11 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
BbbCommonEnvCoreMsg(envelope, event)
}
def buildPresentationUploadTokenSysPubMsg(parentId: String, userId: String, presentationUploadToken: String, filename: String): BbbCommonEnvCoreMsg = {
def buildPresentationUploadTokenSysPubMsg(parentMeetingId: String, userId: String, presentationUploadToken: String, filename: String, presId: String): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PresentationUploadTokenSysPubMsg.NAME, routing)
val header = BbbClientMsgHeader(PresentationUploadTokenSysPubMsg.NAME, parentId, userId)
val body = PresentationUploadTokenSysPubMsgBody("DEFAULT_PRESENTATION_POD", presentationUploadToken, filename, parentId)
val header = BbbClientMsgHeader(PresentationUploadTokenSysPubMsg.NAME, parentMeetingId, userId)
val body = PresentationUploadTokenSysPubMsgBody("DEFAULT_PRESENTATION_POD", presentationUploadToken, filename, parentMeetingId, presId)
val event = PresentationUploadTokenSysPubMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
@ -133,9 +134,18 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
val currentPres: Option[PresentationInPod] = presentationPods.flatMap(_.getPresentation(presId)).headOption
if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationWithAnnotations")) {
if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationWithAnnotations")
&& m.body.fileStateType == "Annotated") {
val reason = "Annotated presentation download disabled for this meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
} else if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationOriginalFile")
&& m.body.fileStateType == "Original") {
val reason = "Original presentation download disabled for this meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
} else if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationConvertedToPdf")
&& m.body.fileStateType == "Converted") {
val reason = "Converted presentation download disabled for this meeting. (PDF format)"
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
} else if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, userId)) {
val reason = "No permission to download presentation."
PermissionCheck.ejectUserForFailedPermission(meetingId, userId, reason, bus.outGW, liveMeeting)
@ -196,9 +206,10 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
val filename = m.filename
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId)
val presentationId = PresentationPodsApp.generatePresentationId(m.filename)
// Informs bbb-web about the token so that when we use it to upload the presentation, it is able to look it up in the list of tokens
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(parentMeetingId, userId, presentationUploadToken, filename))
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(parentMeetingId, userId, presentationUploadToken, filename, presentationId))
if (liveMeeting.props.meetingProp.disabledFeatures.contains("importPresentationWithAnnotationsFromBreakoutRooms")) {
log.error(s"Capturing breakout rooms slides disabled in meeting ${meetingId}.")
@ -215,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 = new ExportJob(jobId, JobTypes.CAPTURE_PRESENTATION, filename, presId, presLocation, allPages, pagesRange, parentMeetingId, presentationUploadToken)
val exportJob: ExportJob = ExportJob(jobId, JobTypes.CAPTURE_PRESENTATION, 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
@ -248,8 +259,13 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
"filename" -> "annotated_slides.pdf"
)
ChatMessageDAO.insertSystemMsg(liveMeeting.props.meetingProp.intId, GroupChatApp.MAIN_PUBLIC_CHAT, "", GroupChatMessageType.PRESENTATION, presentationDownloadInfo, "")
} else if (m.body.fileStateType == "Converted") {
PresPresentationDAO.updateDownloadUri(m.body.presId, m.body.convertedFileURI)
} else if (m.body.fileStateType == "Original") {
PresPresentationDAO.updateDownloadUri(m.body.presId, m.body.originalFileURI)
}
PresPresentationDAO.updateExportToChatStatus(m.body.presId, "EXPORTED")
bus.outGW.send(buildBroadcastNewPresFileAvailable(m, liveMeeting))
}
@ -265,6 +281,7 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
}
def handle(m: PresAnnStatusMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
PresPresentationDAO.updateExportToChat(m.body.presId, m.body.status, m.body.pageNumber, m.body.error)
bus.outGW.send(buildBroadcastPresAnnStatusMsg(m, liveMeeting))
}
@ -274,8 +291,9 @@ trait MakePresentationDownloadReqMsgHdlr extends RightsManagementTrait {
val jobId: String = s"${m.body.breakoutId}-notes" // Used as the temporaryPresentationId upon upload
val filename = m.body.filename
val presentationUploadToken: String = PresentationPodsApp.generateToken("DEFAULT_PRESENTATION_POD", userId)
val presentationId = PresentationPodsApp.generatePresentationId(m.body.filename)
bus.outGW.send(buildPresentationUploadTokenSysPubMsg(m.body.parentMeetingId, userId, presentationUploadToken, filename))
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 job = buildStoreExportJobInRedisSysMsg(exportJob, liveMeeting)

View File

@ -41,21 +41,21 @@ trait PdfConversionInvalidErrorSysPubMsgHdlr {
pod <- PresentationPodsApp.getPresentationPod(state, msg.body.podId)
pres <- pod.getPresentation(msg.body.presentationId)
} yield {
val presWithError = PresentationInPod(pres.id, pres.name, pres.current, pres.pages, pres.downloadable, pres.removable,
pres.filenameConverted, pres.uploadCompleted, pres.numPages, msg.body.messageKey, errorDetails)
val presWithError = PresentationInPod(pres.id, pres.name, pres.default, pres.current, pres.pages, pres.downloadable,
pres.downloadFileExtension, pres.removable, pres.filenameConverted, pres.uploadCompleted, pres.numPages,
msg.body.messageKey, errorDetails)
var pods = state.presentationPodManager.addPod(pod)
pods = pods.addPresentationToPod(pod.id, presWithError)
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, presWithError)
state.update(pods)
}
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
broadcastEvent(msg)
newState match {
case Some(ns) => ns
case None =>
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
state
case None => state
}
}
}

View File

@ -8,12 +8,13 @@ import org.bigbluebutton.core.models.PresentationInPod
import org.bigbluebutton.core.running.LiveMeeting
trait PresentationConversionCompletedSysPubMsgHdlr {
this: PresentationPodHdlrs =>
def handle(
msg: PresentationConversionCompletedSysPubMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus
msg: PresentationConversionCompletedSysPubMsg,
state: MeetingState2x,
liveMeeting: LiveMeeting,
bus: MessageBus
): MeetingState2x = {
val meetingId = liveMeeting.props.meetingProp.intId
@ -24,7 +25,7 @@ trait PresentationConversionCompletedSysPubMsgHdlr {
pres <- pod.getPresentation(msg.body.presentation.id)
} yield {
val presVO = PresentationPodsApp.translatePresentationToPresentationVO(pres, temporaryPresentationId,
msg.body.presentation.isInitialPresentation, msg.body.presentation.filenameConverted)
msg.body.presentation.defaultPresentation, msg.body.presentation.filenameConverted)
PresentationSender.broadcastPresentationConversionCompletedEvtMsg(
bus,
meetingId,
@ -47,13 +48,13 @@ trait PresentationConversionCompletedSysPubMsgHdlr {
originalDownloadableExtension
)
val presWithConvertedName = PresentationInPod(pres.id, pres.name, pres.current, pres.pages,
pres.downloadable, pres.removable, msg.body.presentation.filenameConverted, uploadCompleted = true, numPages = pres.numPages, errorDetails = Map.empty)
val presWithConvertedName = PresentationInPod(pres.id, pres.name, default = msg.body.presentation.defaultPresentation,
pres.current, pres.pages, pres.downloadable, pres.downloadFileExtension, pres.removable, msg.body.presentation.filenameConverted,
uploadCompleted = true, numPages = pres.numPages, errorDetails = Map.empty)
var pods = state.presentationPodManager.addPod(pod)
pods = pods.addPresentationToPod(pod.id, presWithConvertedName)
PresPresentationDAO.insertOrUpdate(meetingId, presWithConvertedName)
PresPresentationDAO.updatePages(presWithConvertedName)
state.update(pods)
}

View File

@ -37,10 +37,6 @@ trait PresentationConversionUpdatePubMsgHdlr {
bus.outGW.send(msgEvent)
}
val pres = new PresentationInPod(msg.body.presentationId, msg.body.presName, false, Map.empty, false,
false, uploadCompleted = false, numPages = -1, errorDetails = Map.empty)
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, pres)
broadcastEvent(msg)
state
}

View File

@ -39,11 +39,9 @@ trait PresentationHasInvalidMimeTypeErrorPubMsgHdlr {
"fileExtension" -> msg.body.fileExtension
)
val pres = new PresentationInPod(msg.body.presentationId, msg.body.presentationName, false, Map.empty, false,
false, uploadCompleted = false, numPages = -1, errorMsgKey = msg.body.messageKey, errorDetails = errorDetails)
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, pres)
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
broadcastEvent(msg)
state
}
}

View File

@ -28,7 +28,9 @@ trait PresentationPageConversionStartedSysMsgHdlr {
podId = msg.body.podId,
presentationId = msg.body.presentationId,
current = msg.body.current,
default = msg.body.default,
presName = msg.body.presName,
presFilenameConverted = msg.body.presFilenameConverted,
downloadable = msg.body.downloadable,
removable = msg.body.removable,
authzToken = msg.body.authzToken,
@ -44,8 +46,8 @@ trait PresentationPageConversionStartedSysMsgHdlr {
val presentationId = msg.body.presentationId
val podId = msg.body.podId
val pres = new PresentationInPod(presentationId, msg.body.presName, msg.body.current, Map.empty, downloadable,
removable, uploadCompleted = false, numPages = msg.body.numPages, errorDetails = Map.empty)
val pres = new PresentationInPod(presentationId, msg.body.presName, msg.body.default, msg.body.current, Map.empty, downloadable,
"", removable, filenameConverted = msg.body.presFilenameConverted, uploadCompleted = false, numPages = msg.body.numPages, errorDetails = Map.empty)
val newState = for {
pod <- PresentationPodsApp.getPresentationPod(state, podId)
@ -59,6 +61,7 @@ trait PresentationPageConversionStartedSysMsgHdlr {
state.update(pods)
}
PresPresentationDAO.updateConversionStarted(liveMeeting.props.meetingProp.intId, pres)
broadcastEvent(msg)
newState match {

View File

@ -3,7 +3,7 @@ package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.domain.PresentationPageVO
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.PresPresentationDAO
import org.bigbluebutton.core.db.{ PresPageDAO, PresPresentationDAO }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ PresentationInPod, PresentationPage }
import org.bigbluebutton.core.running.LiveMeeting
@ -55,6 +55,7 @@ trait PresentationPageConvertedSysMsgHdlr {
msg.body.page.id,
msg.body.page.num,
msg.body.page.urls,
msg.body.page.content,
msg.body.page.current,
width = msg.body.page.width,
height = msg.body.page.height,
@ -69,7 +70,7 @@ trait PresentationPageConvertedSysMsgHdlr {
var pods = state.presentationPodManager.addPod(pod)
pods = pods.addPresentationToPod(pod.id, newPres)
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, newPres)
PresPageDAO.insert(pres.id, page)
state.update(pods)
}

View File

@ -41,21 +41,20 @@ trait PresentationPageCountErrorPubMsgHdlr {
pod <- PresentationPodsApp.getPresentationPod(state, msg.body.podId)
pres <- pod.getPresentation(msg.body.presentationId)
} yield {
val presWithError = PresentationInPod(pres.id, pres.name, pres.current, pres.pages, pres.downloadable, pres.removable,
pres.filenameConverted, pres.uploadCompleted, msg.body.numberOfPages, msg.body.messageKey, errorDetails)
val presWithError = PresentationInPod(pres.id, pres.name, pres.default, pres.current, pres.pages, pres.downloadable,
"", pres.removable, pres.filenameConverted, pres.uploadCompleted, msg.body.numberOfPages, msg.body.messageKey, errorDetails)
var pods = state.presentationPodManager.addPod(pod)
pods = pods.addPresentationToPod(pod.id, presWithError)
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, presWithError)
state.update(pods)
}
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
broadcastEvent(msg)
newState match {
case Some(ns) => ns
case None =>
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
state
case None => state
}
}
}

View File

@ -11,7 +11,7 @@ class PresentationPodHdlrs(implicit val context: ActorContext)
with PresentationConversionCompletedSysPubMsgHdlr
with PdfConversionInvalidErrorSysPubMsgHdlr
with SetCurrentPagePubMsgHdlr
with SetPresenterInPodReqMsgHdlr
with SetPresenterInDefaultPodInternalMsgHdlr
with RemovePresentationPubMsgHdlr
with SetPresentationDownloadablePubMsgHdlr
with PresentationConversionUpdatePubMsgHdlr

View File

@ -1,5 +1,6 @@
package org.bigbluebutton.core.apps.presentationpod
import org.apache.commons.codec.digest.DigestUtils
import org.bigbluebutton.common2.domain._
import org.bigbluebutton.core.domain._
import org.bigbluebutton.core.models._
@ -74,7 +75,7 @@ object PresentationPodsApp {
}
def translatePresentationToPresentationVO(pres: PresentationInPod, temporaryPresentationId: String,
isInitialPresentation: Boolean, filenameConverted: String): PresentationVO = {
defaultPresentation: Boolean, filenameConverted: String): PresentationVO = {
val pages = pres.pages.values.map { page =>
PageVO(
id = page.id,
@ -92,7 +93,7 @@ object PresentationPodsApp {
)
}
PresentationVO(pres.id, temporaryPresentationId, pres.name, pres.current, pages.toVector, pres.downloadable,
pres.removable, isInitialPresentation, filenameConverted)
pres.removable, defaultPresentation, filenameConverted)
}
def setCurrentPresentationInPod(state: MeetingState2x, podId: String, nextCurrentPresId: String): Option[PresentationPod] = {
@ -107,5 +108,11 @@ object PresentationPodsApp {
def generateToken(podId: String, userId: String): String = {
"PresUploadToken-" + RandomStringGenerator.randomAlphanumericString(8) + podId + "-" + userId
}
def generatePresentationId(presFilename: String) = {
val timestamp = System.currentTimeMillis
DigestUtils.sha1Hex(presFilename + RandomStringGenerator.randomAlphanumericString(8)) + "-" + timestamp
}
}

View File

@ -1,11 +1,14 @@
package org.bigbluebutton.core.apps.presentationpod
import org.apache.commons.codec.digest.DigestUtils
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.db.PresPresentationDAO
import org.bigbluebutton.core.util.RandomStringGenerator
trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
this: PresentationPodHdlrs =>
@ -13,13 +16,13 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
def handle(msg: PresentationUploadTokenReqMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def broadcastPresentationUploadTokenPassResp(msg: PresentationUploadTokenReqMsg, token: String): Unit = {
def broadcastPresentationUploadTokenPassResp(msg: PresentationUploadTokenReqMsg, token: String, presId: String): Unit = {
// send back to client
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(PresentationUploadTokenPassRespMsg.NAME, routing)
val header = BbbClientMsgHeader(PresentationUploadTokenPassRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = PresentationUploadTokenPassRespMsgBody(msg.body.podId, token, msg.body.filename, msg.body.temporaryPresentationId)
val body = PresentationUploadTokenPassRespMsgBody(msg.body.podId, token, msg.body.filename, msg.body.uploadTemporaryId, presId)
val event = PresentationUploadTokenPassRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
@ -37,13 +40,13 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
bus.outGW.send(msgEvent)
}
def broadcastPresentationUploadTokenSysPubMsg(msg: PresentationUploadTokenReqMsg, token: String): Unit = {
def broadcastPresentationUploadTokenSysPubMsg(msg: PresentationUploadTokenReqMsg, token: String, presId: String): Unit = {
// send to bbb-web
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PresentationUploadTokenSysPubMsg.NAME, routing)
val header = BbbClientMsgHeader(PresentationUploadTokenSysPubMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = PresentationUploadTokenSysPubMsgBody(msg.body.podId, token, msg.body.filename, liveMeeting.props.meetingProp.intId)
val body = PresentationUploadTokenSysPubMsgBody(msg.body.podId, token, msg.body.filename, liveMeeting.props.meetingProp.intId, presId)
val event = PresentationUploadTokenSysPubMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
@ -68,9 +71,10 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
log.info("handlePresentationUploadTokenReqMsg" + liveMeeting.props.meetingProp.intId +
" userId=" + msg.header.userId + " filename=" + msg.body.filename)
val meetingId = liveMeeting.props.meetingProp.intId
if (liveMeeting.props.meetingProp.disabledFeatures.contains("presentation")) {
broadcastPresentationUploadTokenFailResp(msg)
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "Presentation is disabled for this meeting"
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else if (filterPresentationMessage(liveMeeting.users2x, msg.header.userId) &&
@ -81,8 +85,19 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
} else {
if (userIsAllowedToUploadInPod(msg.body.podId, msg.header.userId)) {
val token = PresentationPodsApp.generateToken(msg.body.podId, msg.header.userId)
broadcastPresentationUploadTokenPassResp(msg, token)
broadcastPresentationUploadTokenSysPubMsg(msg, token)
val presentationId = PresentationPodsApp.generatePresentationId(msg.body.filename)
broadcastPresentationUploadTokenPassResp(msg, token, presentationId)
broadcastPresentationUploadTokenSysPubMsg(msg, token, presentationId)
PresPresentationDAO.insertToken(
meetingId,
msg.header.userId,
msg.body.uploadTemporaryId,
presentationId,
token,
msg.body.filename
)
} else {
broadcastPresentationUploadTokenFailResp(msg)
}

View File

@ -42,21 +42,20 @@ trait PresentationUploadedFileTimeoutErrorPubMsgHdlr {
pod <- PresentationPodsApp.getPresentationPod(state, msg.body.podId)
pres <- pod.getPresentation(msg.body.presentationId)
} yield {
val presWithError = PresentationInPod(pres.id, pres.name, pres.current, pres.pages, pres.downloadable, pres.removable,
pres.filenameConverted, pres.uploadCompleted, pres.numPages, msg.body.messageKey, errorDetails)
val presWithError = PresentationInPod(pres.id, pres.name, pres.default, pres.current, pres.pages, pres.downloadable,
"", pres.removable, pres.filenameConverted, pres.uploadCompleted, pres.numPages, msg.body.messageKey, errorDetails)
var pods = state.presentationPodManager.addPod(pod)
pods = pods.addPresentationToPod(pod.id, presWithError)
PresPresentationDAO.insertOrUpdate(msg.header.meetingId, presWithError)
state.update(pods)
}
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
broadcastEvent(msg)
newState match {
case Some(ns) => ns
case None =>
PresPresentationDAO.updateErrors(msg.body.presentationId, msg.body.messageKey, errorDetails)
state
case None => state
}
}
}

View File

@ -3,6 +3,7 @@ package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.PresPresentationDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
@ -38,6 +39,8 @@ trait RemovePresentationPubMsgHdlr extends RightsManagementTrait {
val podId = msg.body.podId
val presentationId = msg.body.presentationId
PresPresentationDAO.delete(presentationId)
val newState = for {
pod <- PresentationPodsApp.getPresentationPod(state, podId)
} yield {

View File

@ -3,6 +3,7 @@ package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.db.PresPresentationDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
@ -17,8 +18,21 @@ trait SetPresentationDownloadablePubMsgHdlr extends RightsManagementTrait {
val meetingId = liveMeeting.props.meetingProp.intId
if (filterPresentationMessage(liveMeeting.users2x, msg.header.userId) &&
permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
val reason = "No permission to remove presentation from meeting."
permissionFailed(
PermissionCheck.GUEST_LEVEL,
PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId
)) {
val reason = "No permission to make presentation downloadable for meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
state
} else if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationOriginalFile")
&& msg.body.fileStateType == "Original") {
val reason = "Download original presentation is disabled for meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
state
} else if (liveMeeting.props.meetingProp.disabledFeatures.contains("downloadPresentationConvertedToPdf")
&& msg.body.fileStateType == "Converted") {
val reason = "Download converted presentation is disabled for meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
state
} else {
@ -37,7 +51,9 @@ trait SetPresentationDownloadablePubMsgHdlr extends RightsManagementTrait {
PresentationSender.broadcastSetPresentationDownloadableEvtMsg(bus, meetingId, pod.id,
msg.header.userId, presentationId, downloadable, pres.name, downloadableExtension)
val pods = state.presentationPodManager.setPresentationDownloadableInPod(pod.id, presentationId, downloadable)
val pods = state.presentationPodManager.setPresentationDownloadableInPod(pod.id, presentationId, downloadable, downloadableExtension)
PresPresentationDAO.updateDownloadable(presentationId, downloadable, downloadableExtension)
state.update(pods)
}

View File

@ -0,0 +1,68 @@
package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.SetPresenterInDefaultPodInternalMsg
import org.bigbluebutton.core.apps.{ RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.models.{ PresentationPod, Users2x }
trait SetPresenterInDefaultPodInternalMsgHdlr {
this: PresentationPodHdlrs =>
def handleSetPresenterInDefaultPodInternalMsg(
msg: SetPresenterInDefaultPodInternalMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus
): MeetingState2x = {
// Swith presenter as default presenter pod has changed.
log.info("Presenter pod change will trigger a presenter change")
SetPresenterInPodActionHandler.handleAction(state, liveMeeting, bus.outGW, "", PresentationPod.DEFAULT_PRESENTATION_POD, msg.presenterId)
}
}
object SetPresenterInPodActionHandler extends RightsManagementTrait {
def handleAction(
state: MeetingState2x,
liveMeeting: LiveMeeting,
outGW: OutMsgRouter,
assignedBy: String,
podId: String,
newPresenterId: String
): MeetingState2x = {
def broadcastSetPresenterInPodRespMsg(podId: String, nextPresenterId: String, requesterId: String): Unit = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, requesterId
)
val envelope = BbbCoreEnvelope(SetPresenterInPodRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SetPresenterInPodRespMsg.NAME, liveMeeting.props.meetingProp.intId, requesterId)
val body = SetPresenterInPodRespMsgBody(podId, nextPresenterId)
val event = SetPresenterInPodRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
val newState = for {
user <- Users2x.findWithIntId(liveMeeting.users2x, newPresenterId)
pod <- PresentationPodsApp.getPresentationPod(state, podId)
} yield {
if (pod.currentPresenter != "") {
Users2x.removeUserFromPresenterGroup(liveMeeting.users2x, pod.currentPresenter)
liveMeeting.users2x.addOldPresenter(pod.currentPresenter)
}
Users2x.addUserToPresenterGroup(liveMeeting.users2x, newPresenterId)
val updatedPod = pod.setCurrentPresenter(newPresenterId)
broadcastSetPresenterInPodRespMsg(pod.id, newPresenterId, assignedBy)
val pods = state.presentationPodManager.addPod(updatedPod)
state.update(pods)
}
newState match {
case Some(ns) => ns
case None => state
}
}
}

View File

@ -1,78 +0,0 @@
package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.users.AssignPresenterActionHandler
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.models.{ PresentationPod, Users2x }
trait SetPresenterInPodReqMsgHdlr {
this: PresentationPodHdlrs =>
def handle(
msg: SetPresenterInPodReqMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus
): MeetingState2x = {
if (msg.body.podId == PresentationPod.DEFAULT_PRESENTATION_POD) {
// Swith presenter as default presenter pod has changed.
log.info("Presenter pod change will trigger a presenter change")
AssignPresenterActionHandler.handleAction(liveMeeting, bus.outGW, msg.header.userId, msg.body.nextPresenterId)
}
SetPresenterInPodActionHandler.handleAction(state, liveMeeting, bus.outGW, msg.header.userId, msg.body.podId, msg.body.nextPresenterId)
}
}
object SetPresenterInPodActionHandler extends RightsManagementTrait {
def handleAction(
state: MeetingState2x,
liveMeeting: LiveMeeting,
outGW: OutMsgRouter,
assignedBy: String,
podId: String,
newPresenterId: String
): MeetingState2x = {
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, assignedBy)) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to set presenter in presentation pod."
PermissionCheck.ejectUserForFailedPermission(meetingId, assignedBy, reason, outGW, liveMeeting)
state
} else {
def broadcastSetPresenterInPodRespMsg(podId: String, nextPresenterId: String, requesterId: String): Unit = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, requesterId
)
val envelope = BbbCoreEnvelope(SetPresenterInPodRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SetPresenterInPodRespMsg.NAME, liveMeeting.props.meetingProp.intId, requesterId)
val body = SetPresenterInPodRespMsgBody(podId, nextPresenterId)
val event = SetPresenterInPodRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
val newState = for {
user <- Users2x.findWithIntId(liveMeeting.users2x, newPresenterId)
pod <- PresentationPodsApp.getPresentationPod(state, podId)
} yield {
if (pod.currentPresenter != "") {
Users2x.removeUserFromPresenterGroup(liveMeeting.users2x, pod.currentPresenter)
liveMeeting.users2x.addOldPresenter(pod.currentPresenter)
}
Users2x.addUserToPresenterGroup(liveMeeting.users2x, newPresenterId)
val updatedPod = pod.setCurrentPresenter(newPresenterId)
broadcastSetPresenterInPodRespMsg(pod.id, newPresenterId, assignedBy)
val pods = state.presentationPodManager.addPod(updatedPod)
state.update(pods)
}
newState match {
case Some(ns) => ns
case None => state
}
}
}
}

View File

@ -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.reset(liveMeeting.timerModel) //Reset on switch Stopwatch/Timer
if (msg.body.stopwatch) {
TimerModel.setTrack(liveMeeting.timerModel, "noTrack")
}

View File

@ -2,8 +2,11 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.RightsManagementTrait
import org.bigbluebutton.core.models.{ UserState, Users2x }
import org.bigbluebutton.core.apps.groupchats.GroupChatApp
import org.bigbluebutton.core.db.ChatMessageDAO
import org.bigbluebutton.core.models.{ GroupChatFactory, GroupChatMessage, Roles, UserState, Users2x }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait {
@ -30,6 +33,8 @@ trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait {
outGW.send(msgEventChange)
}
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
newUserState <- Users2x.setUserAway(liveMeeting.users2x, user.intId, msg.body.away)
@ -44,6 +49,14 @@ trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait {
outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "none"))
}
val msgMeta = Map(
"away" -> msg.body.away
)
if (!(user.role == Roles.VIEWER_ROLE && user.locked && permissions.disablePubChat) && ((user.away && !msg.body.away) || (!user.away && msg.body.away))) {
ChatMessageDAO.insertSystemMsg(liveMeeting.props.meetingProp.intId, GroupChatApp.MAIN_PUBLIC_CHAT, "", GroupChatMessageType.USER_AWAY_STATUS_MSG, msgMeta, user.name)
}
broadcast(newUserState, msg.body.away)
}
}

View File

@ -60,7 +60,8 @@ trait RegisterUserReqMsgHdlr {
val regUser = RegisteredUsers.create(msg.body.intUserId, msg.body.extUserId,
msg.body.name, msg.body.role, msg.body.authToken, msg.body.sessionToken,
msg.body.avatarURL, ColorPicker.nextColor(liveMeeting.props.meetingProp.intId), msg.body.guest, msg.body.authed, guestStatus, msg.body.excludeFromDashboard, msg.body.customParameters, false)
msg.body.avatarURL, ColorPicker.nextColor(liveMeeting.props.meetingProp.intId), msg.body.guest, msg.body.authed,
guestStatus, msg.body.excludeFromDashboard, msg.body.enforceLayout, msg.body.customParameters, false)
checkUserConcurrentAccesses(regUser)
RegisteredUsers.add(liveMeeting.registeredUsers, regUser, liveMeeting.props.meetingProp.intId)

View File

@ -2,15 +2,14 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
import org.bigbluebutton.core.db.{ UserStateDAO }
import org.bigbluebutton.core.db.{ UserDAO, UserStateDAO }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ RegisteredUser, RegisteredUsers, Users2x, VoiceUsers }
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.MsgBuilder
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running._
import org.bigbluebutton.core2.message.senders._
trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
this: MeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
@ -18,41 +17,116 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
log.info("Received user joined meeting. user {} meetingId={}", msg.body.userId, msg.header.meetingId)
Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) match {
case Some(reconnectingUser) =>
if (reconnectingUser.userLeftFlag.left) {
case Some(user) => handleUserReconnecting(user, msg, state)
case None => handleUserJoining(msg, state)
}
}
private def handleUserJoining(msg: UserJoinMeetingReqMsg, state: MeetingState2x): MeetingState2x = {
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, liveMeeting.registeredUsers)
log.info(s"Number of registered users [${RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)}]")
regUser.fold {
handleFailedUserJoin(msg, "Invalid auth token.", EjectReasonCode.VALIDATE_TOKEN)
state
} { user =>
val validationResult = for {
_ <- checkIfUserGuestStatusIsAllowed(user)
_ <- checkIfUserIsBanned(user)
_ <- checkIfUserLoggedOut(user)
_ <- validateMaxParticipants(user)
} yield user
validationResult.fold(
reason => handleFailedUserJoin(msg, reason._1, reason._2),
validUser => handleSuccessfulUserJoin(msg, validUser)
)
}
}
private def handleSuccessfulUserJoin(msg: UserJoinMeetingReqMsg, regUser: RegisteredUser) = {
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
updateParentMeetingWithNewListOfUsers()
notifyPreviousUsersWithSameExtId(regUser)
clearCachedVoiceUser(regUser)
clearExpiredUserState(regUser)
invalidateUserGraphqlConnection(regUser)
newState
}
private def handleFailedUserJoin(msg: UserJoinMeetingReqMsg, failReason: String, failReasonCode: String) = {
log.info("Ignoring user {} attempt to join in meeting {}. Reason Code: {}, Reason Message: {}", msg.body.userId, msg.header.meetingId, failReasonCode, failReason)
UserDAO.updateJoinError(msg.body.userId, failReasonCode, failReason)
state
}
private def handleUserReconnecting(user: UserState, msg: UserJoinMeetingReqMsg, state: MeetingState2x): MeetingState2x = {
if (user.userLeftFlag.left) {
resetUserLeftFlag(msg)
}
state
}
private def resetUserLeftFlag(msg: UserJoinMeetingReqMsg) = {
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)
Users2x.resetUserLeftFlag(liveMeeting.users2x, msg.body.userId)
}
state
case None =>
// Check if maxParticipants has been reached
// User are able to reenter if he already joined previously with the same extId
val userHasJoinedAlready = RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers) match {
case Some(regUser: RegisteredUser) => RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers)
case None => false
private def validateMaxParticipants(regUser: RegisteredUser): Either[(String, String), Unit] = {
val userHasJoinedAlready = RegisteredUsers.checkUserExtIdHasJoined(regUser.externId, liveMeeting.registeredUsers)
val maxParticipants = liveMeeting.props.usersProp.maxUsers - 1
if (maxParticipants > 0 && //0 = no limit
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= maxParticipants &&
!userHasJoinedAlready) {
Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS))
} else {
Right(())
}
}
val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 &&
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
userHasJoinedAlready == false
if (!hasReachedMaxParticipants) {
val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.guestStatus != GuestStatus.ALLOW) {
Left(("User is not allowed to join", EjectReasonCode.PERMISSION_FAILED))
} else {
Right(())
}
}
private def checkIfUserIsBanned(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.banned) {
Left(("Banned user rejoining", EjectReasonCode.BANNED_USER_REJOINING))
} else {
Right(())
}
}
private def checkIfUserLoggedOut(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.loggedOut) {
Left(("User had logged out", EjectReasonCode.USER_LOGGED_OUT))
} else {
Right(())
}
}
private def updateParentMeetingWithNewListOfUsers() = {
if (liveMeeting.props.meetingProp.isBreakout) {
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
}
}
// Warn previous users that someone connected with same Id
for {
regUser <- RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId,
liveMeeting.registeredUsers)
} yield {
private def notifyPreviousUsersWithSameExtId(regUser: RegisteredUser) = {
RegisteredUsers.findAllWithExternUserId(regUser.externId, liveMeeting.registeredUsers)
.filter(u => u.id != regUser.id)
.filter(_.id != regUser.id)
.foreach { previousUser =>
sendUserConnectedNotification(previousUser, regUser, liveMeeting)
}
}
private def sendUserConnectedNotification(previousUser: RegisteredUser, newUser: RegisteredUser, liveMeeting: LiveMeeting) = {
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
previousUser.id,
liveMeeting.props.meetingProp.intId,
@ -60,22 +134,19 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
"promote",
"app.mobileAppModal.userConnectedWithSameId",
"Notification to warn that user connect again from other browser/device",
Vector(regUser.name)
Vector(newUser.name)
)
outGW.send(notifyUserEvent)
}
}
private def clearCachedVoiceUser(regUser: RegisteredUser) =
// fresh user joined (not due to reconnection). Clear (pop) the cached voice user
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, msg.body.userId)
UserStateDAO.updateExpired(msg.body.userId, false)
VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, regUser.id)
newState
} else {
log.info("Ignoring user {} attempt to join, once the meeting {} has reached max participants: {}", msg.body.userId, msg.header.meetingId, liveMeeting.props.usersProp.maxUsers)
state
}
}
}
}
private def clearExpiredUserState(regUser: RegisteredUser) =
UserStateDAO.updateExpired(regUser.id, false)
private def invalidateUserGraphqlConnection(regUser: RegisteredUser) =
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, regUser.id, regUser.sessionToken, "user_joined", outGW)
}

View File

@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs.UserLeaveReqMsg
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ RegisteredUsers, Users2x }
import org.bigbluebutton.core.running.{ HandlerHelpers, MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.Sender
trait UserLeaveReqMsgHdlr extends HandlerHelpers {
this: MeetingActor =>
@ -30,6 +31,7 @@ trait UserLeaveReqMsgHdlr extends HandlerHelpers {
ru <- RegisteredUsers.findWithUserId(msg.body.userId, liveMeeting.registeredUsers)
} yield {
RegisteredUsers.setUserLoggedOutFlag(liveMeeting.registeredUsers, ru)
Sender.sendInvalidateUserGraphqlConnectionSysMsg(liveMeeting.props.meetingProp.intId, ru.id, ru.sessionToken, "user_loggedout", outGW)
}
}
state

View File

@ -2,12 +2,14 @@ package org.bigbluebutton.core.apps.users
import org.apache.pekko.actor.ActorContext
import org.apache.pekko.event.Logging
import org.bigbluebutton.Boot.eventBus
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.{SetPresenterInDefaultPodInternalMsg}
import org.bigbluebutton.core.apps.ExternalVideoModel
import org.bigbluebutton.core.bus.InternalEventBus
import org.bigbluebutton.core.bus.{BigBlueButtonEvent, InternalEventBus}
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{LiveMeeting, OutMsgRouter}
import org.bigbluebutton.core2.message.senders.{MsgBuilder, Sender}
import org.bigbluebutton.core2.message.senders.{MsgBuilder}
import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x
import org.bigbluebutton.core.db.UserStateDAO
@ -67,8 +69,8 @@ object UsersApp {
moderator <- Users2x.findModerator(liveMeeting.users2x)
newPresenter <- Users2x.makePresenter(liveMeeting.users2x, moderator.intId)
} yield {
// println(s"automaticallyAssignPresenter: moderator=${moderator} newPresenter=${newPresenter.intId}");
sendPresenterAssigned(outGW, meetingId, newPresenter.intId, newPresenter.name, newPresenter.intId)
sendPresenterInPodReq(meetingId, newPresenter.intId)
}
}
@ -77,6 +79,10 @@ object UsersApp {
outGW.send(event)
}
def sendPresenterInPodReq(meetingId: String, newPresenterIntId: String): Unit = {
eventBus.publish(BigBlueButtonEvent(meetingId, SetPresenterInDefaultPodInternalMsg(newPresenterIntId)))
}
def sendUserLeftMeetingToAllClients(outGW: OutMsgRouter, meetingId: String,
userId: String, eject: Boolean = false, ejectedBy: String = "", reason: String = "", reasonCode: String = ""): Unit = {
// send a user left event for the clients to update

View File

@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.users
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.InternalEventBus
import org.bigbluebutton.core.db.UserDAO
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, OutMsgRouter }
@ -15,103 +16,79 @@ trait ValidateAuthTokenReqMsgHdlr extends HandlerHelpers {
val eventBus: InternalEventBus
def handleValidateAuthTokenReqMsg(msg: ValidateAuthTokenReqMsg, state: MeetingState2x): MeetingState2x = {
log.debug("RECEIVED ValidateAuthTokenReqMsg msg {}", msg)
log.debug(s"Received ValidateAuthTokenReqMsg msg $msg")
var failReason = "Invalid auth token."
var failReasonCode = EjectReasonCode.VALIDATE_TOKEN
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId, liveMeeting.registeredUsers)
log.info(s"Number of registered users [${RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers)}]")
log.info("Number of registered users [{}]", RegisteredUsers.numRegisteredUsers(liveMeeting.registeredUsers))
val regUser = RegisteredUsers.getRegisteredUserWithToken(msg.body.authToken, msg.body.userId,
liveMeeting.registeredUsers)
regUser match {
case Some(u) =>
// Check if maxParticipants has been reached
// User are able to reenter if he already joined previously with the same extId
val hasReachedMaxParticipants = liveMeeting.props.usersProp.maxUsers > 0 &&
regUser.fold {
sendFailedValidateAuthTokenRespMsg(msg, "Invalid auth token.", EjectReasonCode.VALIDATE_TOKEN)
} { user =>
val validationResult = for {
_ <- checkIfUserGuestStatusIsAllowed(user)
_ <- checkIfUserIsBanned(user)
_ <- checkIfUserLoggedOut(user)
_ <- validateMaxParticipants(user)
} yield user
validationResult.fold(
reason => sendFailedValidateAuthTokenRespMsg(msg, reason._1, reason._2),
validUser => sendSuccessfulValidateAuthTokenRespMsg(validUser)
)
}
state
}
private def validateMaxParticipants(user: RegisteredUser): Either[(String, String), Unit] = {
if (liveMeeting.props.usersProp.maxUsers > 0 &&
RegisteredUsers.numUniqueJoinedUsers(liveMeeting.registeredUsers) >= liveMeeting.props.usersProp.maxUsers &&
RegisteredUsers.checkUserExtIdHasJoined(u.externId, liveMeeting.registeredUsers) == false
// Check if banned user is rejoining.
// Fail validation if ejected user is rejoining.
// ralam april 21, 2020
if (u.guestStatus == GuestStatus.ALLOW && !u.banned && !u.loggedOut && !hasReachedMaxParticipants) {
userValidated(u, state)
RegisteredUsers.checkUserExtIdHasJoined(user.externId, liveMeeting.registeredUsers) == false) {
Left(("The maximum number of participants allowed for this meeting has been reached.", EjectReasonCode.MAX_PARTICIPANTS))
} else {
if (u.banned) {
failReason = "Banned user rejoining"
failReasonCode = EjectReasonCode.BANNED_USER_REJOINING
} else if (u.loggedOut) {
failReason = "User had logged out"
failReasonCode = EjectReasonCode.USER_LOGGED_OUT
} else if (hasReachedMaxParticipants) {
failReason = "The maximum number of participants allowed for this meeting has been reached."
failReasonCode = EjectReasonCode.MAX_PARTICIPANTS
}
validateTokenFailed(
outGW,
meetingId = liveMeeting.props.meetingProp.intId,
userId = msg.header.userId,
authToken = msg.body.authToken,
valid = false,
waitForApproval = false,
failReason,
failReasonCode,
state
)
}
case None =>
validateTokenFailed(
outGW,
meetingId = liveMeeting.props.meetingProp.intId,
userId = msg.header.userId,
authToken = msg.body.authToken,
valid = false,
waitForApproval = false,
failReason,
failReasonCode,
state
)
Right(())
}
}
def validateTokenFailed(
outGW: OutMsgRouter,
meetingId: String,
userId: String,
authToken: String,
valid: Boolean,
waitForApproval: Boolean,
reason: String,
reasonCode: String,
state: MeetingState2x
): MeetingState2x = {
val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, 0,
0, reasonCode, reason)
outGW.send(event)
// send a system message to force disconnection
// Comment out as meteor will disconnect the client. Requested by Tiago (ralam apr 28, 2020)
//Sender.sendDisconnectClientSysMsg(meetingId, userId, SystemUser.ID, reasonCode, outGW)
state
private def checkIfUserGuestStatusIsAllowed(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.guestStatus != GuestStatus.ALLOW) {
Left(("User is not allowed to join", EjectReasonCode.PERMISSION_FAILED))
} else {
Right(())
}
}
def sendValidateAuthTokenRespMsg(meetingId: String, userId: String, authToken: String,
valid: Boolean, waitForApproval: Boolean, registeredOn: Long, authTokenValidatedOn: Long,
reasonCode: String = EjectReasonCode.NOT_EJECT, reason: String = "User not ejected"): Unit = {
val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, userId, authToken, valid, waitForApproval, registeredOn,
authTokenValidatedOn, reasonCode, reason)
private def checkIfUserIsBanned(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.banned) {
Left(("Banned user rejoining", EjectReasonCode.BANNED_USER_REJOINING))
} else {
Right(())
}
}
private def checkIfUserLoggedOut(user: RegisteredUser): Either[(String, String), Unit] = {
if (user.loggedOut) {
Left(("User had logged out", EjectReasonCode.USER_LOGGED_OUT))
} else {
Right(())
}
}
private def sendFailedValidateAuthTokenRespMsg(msg: ValidateAuthTokenReqMsg, failReason: String, failReasonCode: String) = {
UserDAO.updateJoinError(msg.body.userId, failReasonCode, failReason)
val event = MsgBuilder.buildValidateAuthTokenRespMsg(liveMeeting.props.meetingProp.intId, msg.header.userId, msg.body.authToken, false, false, 0,
0, failReasonCode, failReason)
outGW.send(event)
}
def userValidated(user: RegisteredUser, state: MeetingState2x): MeetingState2x = {
def sendSuccessfulValidateAuthTokenRespMsg(user: RegisteredUser) = {
val meetingId = liveMeeting.props.meetingProp.intId
val updatedUser = RegisteredUsers.updateUserLastAuthTokenValidated(liveMeeting.registeredUsers, user)
sendValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, valid = true, waitForApproval = false, updatedUser.registeredOn, updatedUser.lastAuthTokenValidatedOn)
state
val event = MsgBuilder.buildValidateAuthTokenRespMsg(meetingId, updatedUser.id, updatedUser.authToken, true, false, updatedUser.registeredOn,
updatedUser.lastAuthTokenValidatedOn, EjectReasonCode.NOT_EJECT, "User not ejected")
outGW.send(event)
}
def sendAllUsersInMeeting(requesterId: String): Unit = {

View File

@ -33,8 +33,8 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
def registerUserInRegisteredUsers() = {
val regUser = RegisteredUsers.create(msg.body.intId, msg.body.voiceUserId,
msg.body.callerIdName, Roles.VIEWER_ROLE, "", "", "", userColor,
true, true, GuestStatus.WAIT, true, Map(), false)
msg.body.callerIdName, Roles.VIEWER_ROLE, msg.body.intId, "", "", userColor,
true, true, GuestStatus.WAIT, true, "", Map(), false)
RegisteredUsers.add(liveMeeting.registeredUsers, regUser, liveMeeting.props.meetingProp.intId)
}
@ -57,7 +57,7 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
locked = MeetingStatus2x.getPermissions(liveMeeting.status).lockOnJoin,
avatar = "",
color = userColor,
clientType = "",
clientType = if (isDialInUser) "dial-in-user" else "",
pickExempted = false,
userLeftFlag = UserLeftFlag(false, 0)
)

View File

@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.apps.users.UsersApp
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
import org.bigbluebutton.core.db.UserDAO
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
trait UserLeftVoiceConfEvtMsgHdlr {
@ -39,6 +40,7 @@ trait UserLeftVoiceConfEvtMsgHdlr {
UsersApp.guestWaitingLeft(liveMeeting, user.intId, outGW)
}
Users2x.remove(liveMeeting.users2x, user.intId)
UserDAO.delete(user.intId)
VoiceApp.removeUserFromVoiceConf(liveMeeting, outGW, msg.body.voiceUserId)
}

View File

@ -11,8 +11,10 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.running.{LiveMeeting, MeetingActor, OutMsgRouter}
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.apps.users.UsersApp
import org.bigbluebutton.core.db.{UserDAO, UserVoiceDAO}
import org.bigbluebutton.core.util.ColorPicker
import org.bigbluebutton.core.util.TimeUtil
import scala.collection.immutable.Map
import scala.concurrent.duration._
@ -323,6 +325,8 @@ object VoiceApp extends SystemConfiguration {
uuid
)
VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState)
UserVoiceDAO.update(voiceUserState)
UserDAO.updateVoiceUserJoined(voiceUserState)
broadcastEvent(voiceUserState)

View File

@ -1,7 +1,7 @@
package org.bigbluebutton.core.apps.voice
import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, MessageTypes, Routing, VoiceCallStateEvtMsg, VoiceCallStateEvtMsgBody, VoiceConfCallStateEvtMsg }
import org.bigbluebutton.core.models.{ VoiceUserState, VoiceUsers }
import org.bigbluebutton.core.db.{ UserVoiceConfStateDAO, UserVoiceDAO }
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
trait VoiceConfCallStateEvtMsgHdlr {
@ -38,5 +38,9 @@ trait VoiceConfCallStateEvtMsgHdlr {
val event = VoiceCallStateEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
if (msg.body.userId.nonEmpty) {
UserVoiceConfStateDAO.insertOrUpdate(msg.body.userId, msg.body.voiceConf, msg.body.callSession, msg.body.clientSession, msg.body.callState)
}
}
}

View File

@ -3,8 +3,9 @@ package org.bigbluebutton.core.apps.webcam
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.running.LiveMeeting
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
this: WebcamApp2x =>
@ -50,6 +51,8 @@ trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
case Some(value) => {
log.info(s"Change webcams only for moderator status. meetingId=${meetingId} value=${value}")
MeetingUsersPoliciesDAO.updateWebcamsOnlyForModerator(meetingId, msg.body.webcamsOnlyForModerator)
if (value) {
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
meetingId,

View File

@ -38,7 +38,16 @@ trait DeleteWhiteboardAnnotationsPubMsgHdlr extends RightsManagementTrait {
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
}
} else {
val deletedAnnotations = deleteWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, msg.body.annotationsIds, liveMeeting, isUserAmongPresenters, isUserModerator)
val annotationsIds = {
if (msg.body.annotationsIds.size > 0) {
msg.body.annotationsIds
} else {
getWhiteboardAnnotations(msg.body.whiteboardId, liveMeeting).map(a => a.id)
}
}
val deletedAnnotations = deleteWhiteboardAnnotations(msg.body.whiteboardId, msg.header.userId, annotationsIds, liveMeeting, isUserAmongPresenters, isUserModerator)
if (!deletedAnnotations.isEmpty) {
broadcastEvent(msg, deletedAnnotations)
}

View File

@ -1,43 +0,0 @@
package org.bigbluebutton.core.db
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class AudioCaptionDbModel(
transcriptId: String,
meetingId: String,
userId: String,
transcript: String,
createdAt: java.sql.Timestamp
)
class AudioCaptionTableDef(tag: Tag) extends Table[AudioCaptionDbModel](tag, None, "audio_caption") {
val transcriptId = column[String]("transcriptId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
val userId = column[String]("userId")
val transcript = column[String]("transcript")
val createdAt = column[java.sql.Timestamp]("createdAt")
def * = (transcriptId, meetingId, userId, transcript, createdAt) <> (AudioCaptionDbModel.tupled, AudioCaptionDbModel.unapply)
}
object AudioCaptionDAO {
def insertOrUpdateAudioCaption(transcriptId: String, meetingId: String, userId: String, transcript: String, createdAt: java.sql.Timestamp) = {
DatabaseConnection.db.run(
TableQuery[AudioCaptionTableDef].insertOrUpdate(
AudioCaptionDbModel(
transcriptId = transcriptId,
meetingId = meetingId,
userId = userId,
transcript = transcript,
createdAt = createdAt
)
)
).onComplete {
case Success(_) => DatabaseConnection.logger.debug(s"Upserted audio caption with ID $transcriptId on AudioCaption table")
case Failure(e) => DatabaseConnection.logger.debug(s"Error upserting audio caption on AudioCaption: $e")
}
}
}

View File

@ -175,10 +175,11 @@ object BreakoutRoomDAO {
}
}
def updateRoomsDuration(meetingId: String, newDurationInSeconds: Int) = {
def updateRoomsDuration(parentMeetingId: String, newDurationInSeconds: Int) = {
DatabaseConnection.db.run(
TableQuery[BreakoutRoomDbTableDef]
.filter(_.parentMeetingId === meetingId)
.filter(_.parentMeetingId === parentMeetingId)
.filter(_.endedAt.isEmpty)
.map(u => u.durationInSeconds)
.update(newDurationInSeconds)
).onComplete {

View File

@ -0,0 +1,104 @@
package org.bigbluebutton.core.db
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class CaptionDbModel(
captionId: String,
meetingId: String,
captionType: String,
userId: String,
lang: String,
captionText: String,
createdAt: java.sql.Timestamp
)
class CaptionTableDef(tag: Tag) extends Table[CaptionDbModel](tag, None, "caption") {
val captionId = column[String]("captionId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
val captionType = column[String]("captionType")
val userId = column[String]("userId")
val lang = column[String]("lang")
val captionText = column[String]("captionText")
val createdAt = column[java.sql.Timestamp]("createdAt")
def * = (captionId, meetingId, captionType, userId, lang, captionText, createdAt) <> (CaptionDbModel.tupled, CaptionDbModel.unapply)
}
object CaptionTypes {
val AUDIO_TRANSCRIPTION = "AUDIO_TRANSCRIPTION"
val TYPED = "TYPED"
}
object CaptionDAO {
def insertOrUpdateAudioCaption(captionId: String, meetingId: String, userId: String, transcript: String, lang: String) = {
DatabaseConnection.db.run(
TableQuery[CaptionTableDef].insertOrUpdate(
CaptionDbModel(
captionId = captionId,
meetingId = meetingId,
captionType = CaptionTypes.AUDIO_TRANSCRIPTION,
userId = userId,
lang = lang,
captionText = transcript,
createdAt = new java.sql.Timestamp(System.currentTimeMillis())
)
)
).onComplete {
case Success(_) => DatabaseConnection.logger.debug(s"Upserted caption with ID $captionId on Caption table")
case Failure(e) => DatabaseConnection.logger.debug(s"Error upserting caption on Caption: $e")
}
}
def insertOrUpdatePadCaption(meetingId: String, locale: String, userId: String, text: String) = {
val lines: Array[String] = text.split("\\r?\\n").filter(_.trim.nonEmpty)
val lastTwoLines = lines.takeRight(2)
val actions: Seq[DBIO[Int]] = lastTwoLines.map { line =>
sqlu"""
WITH upsert AS (
UPDATE caption
SET "captionText"=${line},
"createdAt" = current_timestamp
WHERE "captionId" in (
SELECT "captionId"
FROM (
SELECT "captionId", "captionText", "createdAt"
FROM caption
WHERE "meetingId" = ${meetingId}
AND lang = ${locale}
AND "captionType" = ${CaptionTypes.TYPED}
order by "createdAt" desc
limit 2
) a
WHERE ${line} != "captionText"
AND (${line} LIKE "captionText" || '%' or "captionText" LIKE ${line} || '%')
AND ABS(length("captionText") - length(${line})) < 25
ORDER BY "createdAt" desc
LIMIT 1
)
RETURNING *)
INSERT INTO caption ("captionId", "meetingId", "captionType", "userId", "lang", "captionText", "createdAt")
SELECT md5(random()::text || clock_timestamp()::text), ${meetingId}, 'TYPED', ${userId}, ${locale}, ${line}, current_timestamp
WHERE NOT EXISTS (SELECT * FROM upsert)
AND ${line} NOT IN (SELECT "captionText"
FROM caption
WHERE "meetingId" = ${meetingId}
AND lang = ${locale}
AND "captionType" = ${CaptionTypes.TYPED}
order by "createdAt" desc
limit 2
)"""
}
DatabaseConnection.db.run(DBIO.sequence(actions).transactionally).onComplete {
case Success(rowsAffected) =>
val total = rowsAffected.sum
DatabaseConnection.logger.debug(s"$total row(s) affected in caption table!")
case Failure(e) =>
DatabaseConnection.logger.error(s"Error executing action: ", e)
}
}
}

View File

@ -11,7 +11,7 @@ case class ChatMessageDbModel(
chatId: String,
meetingId: String,
correlationId: String,
createdTime: Long,
createdAt: java.sql.Timestamp,
chatEmphasizedText: Boolean,
message: String,
messageType: String,
@ -26,7 +26,7 @@ class ChatMessageDbTableDef(tag: Tag) extends Table[ChatMessageDbModel](tag, Non
val chatId = column[String]("chatId")
val meetingId = column[String]("meetingId")
val correlationId = column[String]("correlationId")
val createdTime = column[Long]("createdTime")
val createdAt = column[java.sql.Timestamp]("createdAt")
val chatEmphasizedText = column[Boolean]("chatEmphasizedText")
val message = column[String]("message")
val messageType = column[String]("messageType")
@ -37,7 +37,7 @@ class ChatMessageDbTableDef(tag: Tag) extends Table[ChatMessageDbModel](tag, Non
// val chat = foreignKey("chat_message_chat_fk", (chatId, meetingId), ChatTable.chats)(c => (c.chatId, c.meetingId), onDelete = ForeignKeyAction.Cascade)
// val sender = foreignKey("chat_message_sender_fk", senderId, UserTable.users)(_.userId, onDelete = ForeignKeyAction.SetNull)
override def * = (messageId, chatId, meetingId, correlationId, createdTime, chatEmphasizedText, message, messageType, messageMetadata, senderId, senderName, senderRole) <> (ChatMessageDbModel.tupled, ChatMessageDbModel.unapply)
override def * = (messageId, chatId, meetingId, correlationId, createdAt, chatEmphasizedText, message, messageType, messageMetadata, senderId, senderName, senderRole) <> (ChatMessageDbModel.tupled, ChatMessageDbModel.unapply)
}
object ChatMessageDAO {
@ -49,7 +49,7 @@ object ChatMessageDAO {
chatId = chatId,
meetingId = meetingId,
correlationId = groupChatMessage.correlationId,
createdTime = groupChatMessage.timestamp,
createdAt = new java.sql.Timestamp(System.currentTimeMillis()),
chatEmphasizedText = groupChatMessage.chatEmphasizedText,
message = groupChatMessage.message,
messageType = "default",
@ -78,11 +78,11 @@ object ChatMessageDAO {
chatId = chatId,
meetingId = meetingId,
correlationId = "",
createdTime = System.currentTimeMillis(),
createdAt = new java.sql.Timestamp(System.currentTimeMillis()),
chatEmphasizedText = false,
message = message,
messageType = messageType,
messageMetadata = Some(JsonUtils.mapToJson(messageMetadata)),
messageMetadata = Some(JsonUtils.mapToJson(messageMetadata).compactPrint),
senderId = None,
senderName = senderName,
senderRole = None

View File

@ -11,8 +11,9 @@ case class ChatUserDbModel(
chatId: String,
meetingId: String,
userId: String,
lastSeenAt: Long,
typingAt: Option[java.sql.Timestamp],
lastSeenAt: Option[java.sql.Timestamp],
startedTypingAt: Option[java.sql.Timestamp],
lastTypingAt: Option[java.sql.Timestamp],
visible: Boolean
)
@ -20,13 +21,14 @@ class ChatUserDbTableDef(tag: Tag) extends Table[ChatUserDbModel](tag, None, "ch
val chatId = column[String]("chatId", O.PrimaryKey)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val userId = column[String]("userId", O.PrimaryKey)
val lastSeenAt = column[Long]("lastSeenAt")
val typingAt = column[Option[java.sql.Timestamp]]("typingAt")
val lastSeenAt = column[Option[java.sql.Timestamp]]("lastSeenAt")
val startedTypingAt = column[Option[java.sql.Timestamp]]("startedTypingAt")
val lastTypingAt = column[Option[java.sql.Timestamp]]("lastTypingAt")
val visible = column[Boolean]("visible")
// val chat = foreignKey("chat_message_chat_fk", (chatId, meetingId), ChatTable.chats)(c => (c.chatId, c.meetingId), onDelete = ForeignKeyAction.Cascade)
// val sender = foreignKey("chat_message_sender_fk", senderId, UserTable.users)(_.userId, onDelete = ForeignKeyAction.SetNull)
override def * = (chatId, meetingId, userId, lastSeenAt, typingAt, visible) <> (ChatUserDbModel.tupled, ChatUserDbModel.unapply)
override def * = (chatId, meetingId, userId, lastSeenAt, startedTypingAt, lastTypingAt, visible) <> (ChatUserDbModel.tupled, ChatUserDbModel.unapply)
}
object ChatUserDAO {
@ -46,8 +48,9 @@ object ChatUserDAO {
userId = userId,
chatId = chatId,
meetingId = meetingId,
lastSeenAt = 0,
typingAt = None,
lastSeenAt = None,
startedTypingAt = None,
lastTypingAt = None,
visible = visible
)
)
@ -63,11 +66,11 @@ object ChatUserDAO {
.filter(_.meetingId === meetingId)
.filter(_.chatId === (if (chatId == "public") "MAIN-PUBLIC-GROUP-CHAT" else chatId))
.filter(_.userId === userId)
.map(u => (u.typingAt))
.map(u => (u.lastTypingAt))
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated typingAt on chat_user table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating typingAt on chat_user table: $e")
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated lastTypingAt on chat_user table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating lastTypingAt on chat_user table: $e")
}
}

View File

@ -0,0 +1,38 @@
package org.bigbluebutton.core.db
import PostgresProfile.api._
import slick.lifted.ProvenShape
import spray.json.JsValue
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
case class MeetingClientSettingsDbModel(
meetingId: String,
clientSettingsJson: JsValue,
)
class MeetingClientSettingsDbTableDef(tag: Tag) extends Table[MeetingClientSettingsDbModel](tag, "meeting_clientSettings") {
val meetingId = column[String]("meetingId", O.PrimaryKey)
val clientSettingsJson = column[JsValue]("clientSettingsJson")
override def * : ProvenShape[MeetingClientSettingsDbModel] = (meetingId, clientSettingsJson) <> (MeetingClientSettingsDbModel.tupled, MeetingClientSettingsDbModel.unapply)
}
object MeetingClientSettingsDAO {
def insert(meetingId: String, clientSettingsJson: JsValue) = {
DatabaseConnection.db.run(
TableQuery[MeetingClientSettingsDbTableDef].forceInsert(
MeetingClientSettingsDbModel(
meetingId = meetingId,
clientSettingsJson = clientSettingsJson
)
)
).onComplete {
case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in MeetingClientSettings table!")
}
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting MeetingClientSettings: $e")
}
}
}

View File

@ -20,7 +20,7 @@ case class MeetingDbModel(
presentationUploadExternalUrl: String,
learningDashboardAccessToken: String,
createdTime: Long,
duration: Int
durationInSeconds: Int
)
class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meeting") {
@ -37,7 +37,7 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
presentationUploadExternalUrl,
learningDashboardAccessToken,
createdTime,
duration
durationInSeconds
) <> (MeetingDbModel.tupled, MeetingDbModel.unapply)
val meetingId = column[String]("meetingId", O.PrimaryKey)
val extId = column[String]("extId")
@ -51,11 +51,11 @@ class MeetingDbTableDef(tag: Tag) extends Table[MeetingDbModel](tag, None, "meet
val presentationUploadExternalUrl = column[String]("presentationUploadExternalUrl")
val learningDashboardAccessToken = column[String]("learningDashboardAccessToken")
val createdTime = column[Long]("createdTime")
val duration = column[Int]("duration")
val durationInSeconds = column[Int]("durationInSeconds")
}
object MeetingDAO {
def insert(meetingProps: DefaultProps) = {
def insert(meetingProps: DefaultProps, clientSettings: Map[String, Object]) = {
DatabaseConnection.db.run(
TableQuery[MeetingDbTableDef].forceInsert(
MeetingDbModel(
@ -71,28 +71,46 @@ object MeetingDAO {
presentationUploadExternalUrl = meetingProps.meetingProp.presentationUploadExternalUrl,
learningDashboardAccessToken = meetingProps.password.learningDashboardAccessToken,
createdTime = meetingProps.durationProps.createdTime,
duration = meetingProps.durationProps.duration
durationInSeconds = meetingProps.durationProps.duration * 60
)
)
).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)
ChatDAO.insert(meetingProps.meetingProp.intId, GroupChatApp.createDefaultPublicGroupChat())
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) = {
val subqueryBreakoutRooms = TableQuery[BreakoutRoomDbTableDef]
.filter(_.parentMeetingId === parentMeetingId)
.filter(_.endedAt.isEmpty)
.map(_.externalId)
DatabaseConnection.db.run(
TableQuery[MeetingDbTableDef]
.filter(_.extId in subqueryBreakoutRooms)
.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")
}
}
def delete(meetingId: String) = {
DatabaseConnection.db.run(
TableQuery[MeetingDbTableDef]

View File

@ -18,7 +18,8 @@ case class MeetingLockSettingsDbModel(
hideUserList: Boolean,
lockOnJoin: Boolean,
lockOnJoinConfigurable: Boolean,
hideViewersCursor: Boolean
hideViewersCursor: Boolean,
hideViewersAnnotation: Boolean
)
class MeetingLockSettingsDbTableDef(tag: Tag) extends Table[MeetingLockSettingsDbModel](tag, "meeting_lockSettings") {
@ -32,10 +33,11 @@ class MeetingLockSettingsDbTableDef(tag: Tag) extends Table[MeetingLockSettingsD
val lockOnJoin = column[Boolean]("lockOnJoin")
val lockOnJoinConfigurable = column[Boolean]("lockOnJoinConfigurable")
val hideViewersCursor = column[Boolean]("hideViewersCursor")
val hideViewersAnnotation = column[Boolean]("hideViewersAnnotation")
// def fk_meetingId: ForeignKeyQuery[MeetingDbTableDef, MeetingDbModel] = foreignKey("fk_meetingId", meetingId, TableQuery[MeetingDbTableDef])(_.meetingId)
override def * : ProvenShape[MeetingLockSettingsDbModel] = (meetingId, disableCam, disableMic, disablePrivateChat, disablePublicChat, disableNotes, hideUserList, lockOnJoin, lockOnJoinConfigurable, hideViewersCursor) <> (MeetingLockSettingsDbModel.tupled, MeetingLockSettingsDbModel.unapply)
override def * : ProvenShape[MeetingLockSettingsDbModel] = (meetingId, disableCam, disableMic, disablePrivateChat, disablePublicChat, disableNotes, hideUserList, lockOnJoin, lockOnJoinConfigurable, hideViewersCursor, hideViewersAnnotation) <> (MeetingLockSettingsDbModel.tupled, MeetingLockSettingsDbModel.unapply)
}
object MeetingLockSettingsDAO {
@ -52,7 +54,8 @@ object MeetingLockSettingsDAO {
hideUserList = lockSettingsProps.hideUserList,
lockOnJoin = lockSettingsProps.lockOnJoin,
lockOnJoinConfigurable = lockSettingsProps.lockOnJoinConfigurable,
hideViewersCursor = lockSettingsProps.hideViewersCursor
hideViewersCursor = lockSettingsProps.hideViewersCursor,
hideViewersAnnotation = lockSettingsProps.hideViewersAnnotation,
)
)
).onComplete {
@ -76,7 +79,8 @@ object MeetingLockSettingsDAO {
hideUserList = permissions.hideUserList,
lockOnJoin = permissions.lockOnJoin,
lockOnJoinConfigurable = permissions.lockOnJoinConfigurable,
hideViewersCursor = permissions.hideViewersCursor
hideViewersCursor = permissions.hideViewersCursor,
hideViewersAnnotation = permissions.hideViewersAnnotation,
),
)
).onComplete {

View File

@ -73,8 +73,8 @@ object MeetingUsersPoliciesDAO {
.map(u => u.guestPolicy)
.update(policy.policy)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on meeting_usersPolicies table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating meeting_usersPolicies: $e")
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated guestPolicy on meeting_usersPolicies table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating guestPolicy on meeting_usersPolicies: $e")
}
}
@ -90,8 +90,20 @@ object MeetingUsersPoliciesDAO {
}
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on meeting_usersPolicies table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating meeting_usersPolicies: $e")
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated guestLobbyMessage on meeting_usersPolicies table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating guestLobbyMessage on meeting_usersPolicies: $e")
}
}
def updateWebcamsOnlyForModerator(meetingId: String, webcamsOnlyForModerator: Boolean) = {
DatabaseConnection.db.run(
TableQuery[MeetingUsersPoliciesDbTableDef]
.filter(_.meetingId === meetingId)
.map(u => u.webcamsOnlyForModerator)
.update(webcamsOnlyForModerator)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated webcamsOnlyForModerator on meeting_usersPolicies table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating webcamsOnlyForModerator on meeting_usersPolicies: $e")
}
}

View File

@ -0,0 +1,60 @@
package org.bigbluebutton.core.db
import PostgresProfile.api._
import spray.json.JsValue
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
object Permission {
val allowedRoles = List("MODERATOR","VIEWER","PRESENTER")
}
case class PluginDataChannelMessageDbModel(
meetingId: String,
pluginName: String,
dataChannel: String,
// messageId: Option[String] = None,
payloadJson: JsValue,
fromUserId: String,
toRoles: Option[List[String]],
toUserIds: Option[List[String]],
createdAt: java.sql.Timestamp,
)
class PluginDataChannelMessageDbTableDef(tag: Tag) extends Table[PluginDataChannelMessageDbModel](tag, None, "pluginDataChannelMessage") {
val meetingId = column[String]("meetingId", O.PrimaryKey)
val pluginName = column[String]("pluginName", O.PrimaryKey)
val dataChannel = column[String]("dataChannel", O.PrimaryKey)
// val messageId = column[Option[String]]("messageId", O.PrimaryKey) //// The messageId is generated by the database
val payloadJson = column[JsValue]("payloadJson")
val fromUserId = column[String]("fromUserId")
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)
}
object PluginDataChannelMessageDAO {
def insert(meetingId: String, pluginName: String, dataChannel: String, senderUserId: String, payloadJson: String, toRoles: List[String], toUserIds: List[String]) = {
DatabaseConnection.db.run(
TableQuery[PluginDataChannelMessageDbTableDef].forceInsert(
PluginDataChannelMessageDbModel(
meetingId = meetingId,
pluginName = pluginName,
dataChannel = dataChannel,
payloadJson = JsonUtils.stringToJson(payloadJson),
fromUserId = senderUserId,
toRoles = toRoles.map(_.toUpperCase).filter(Permission.allowedRoles.contains) match {
case Nil => None
case filtered => Some(filtered)
},
toUserIds = if(toUserIds.isEmpty) None else Some(toUserIds),
createdAt = new java.sql.Timestamp(System.currentTimeMillis())
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on PluginDataChannelMessage table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PluginDataChannelMessage: $e")
}
}
}

View File

@ -1,12 +1,16 @@
package org.bigbluebutton.core.db
import com.github.tminglei.slickpg._
import spray.json.{ JsArray, JsBoolean, JsNumber, JsObject, JsString, JsValue, JsonWriter }
import spray.json.{ _ }
import org.apache.pekko.http.scaladsl.model.ParsingException
import org.bigbluebutton.common2.domain.SimpleVoteOutVO
import spray.json.{ JsArray, JsBoolean, JsNumber, JsObject, JsString, JsValue, JsonWriter, _ }
import scala.util.{ Failure, Success, Try }
trait PostgresProfile extends ExPostgresProfile
with PgArraySupport {
// def pgjson = "jsonb" // jsonb support is in postgres 9.4.0 onward; for 9.3.x use "json"
with PgArraySupport
with PgSprayJsonSupport {
def pgjson = "jsonb" // jsonb support is in postgres 9.4.0 onward; for 9.3.x use "json"
// Add back `capabilities.insertOrUpdate` to enable native `upsert` support; for postgres 9.5+
override protected def computeCapabilities: Set[slick.basic.Capability] =
@ -14,7 +18,7 @@ trait PostgresProfile extends ExPostgresProfile
override val api = PgAPI
object PgAPI extends API with ArrayImplicits // with DateTimeImplicits
object PgAPI extends API with ArrayImplicits with SprayJsonImplicits // with DateTimeImplicits
{
implicit val strListTypeMapper = new SimpleArrayJdbcType[String]("text").to(_.toList)
}
@ -34,6 +38,8 @@ object JsonUtils {
case m: Map[_, _] => JsObject(m.asInstanceOf[Map[String, Any]].map { case (k, v) => k -> write(v) })
case l: List[_] => JsArray(l.map(write).toVector)
case a: Array[_] => JsArray(a.map(write).toVector)
case v: SimpleVoteOutVO => JsObject("id" -> JsNumber(v.id), "key" -> JsString(v.key), "numVotes" -> JsNumber(v.numVotes))
case null => JsNull
case _ => throw new IllegalArgumentException(s"Unsupported type: ${x.getClass.getName}")
// case _ => JsNull
}
@ -46,7 +52,16 @@ object JsonUtils {
}
def mapToJson(genericMap: Map[String, Any]) = {
genericMap.toJson.compactPrint
genericMap.toJson
}
def stringToJson(jsonString: String): JsValue = {
Try(jsonString.parseJson) match {
case Success(jsValue) => jsValue
case Failure(exception) =>
println(s"Failed to parse JSON string: $exception")
"{}".parseJson
}
}
}

View File

@ -36,7 +36,7 @@ object PresAnnotationDAO {
annotationId = annotation.id,
pageId = annotation.wbId,
userId = annotation.userId,
annotationInfo = JsonUtils.mapToJson(annotation.annotationInfo),
annotationInfo = JsonUtils.mapToJson(annotation.annotationInfo).compactPrint,
lastHistorySequence = sequence.getOrElse(0),
lastUpdatedAt = new java.sql.Timestamp(System.currentTimeMillis())
)

View File

@ -34,7 +34,7 @@ object PresAnnotationHistoryDAO {
annotationId = annotationDiff.id,
pageId = annotationDiff.wbId,
userId = annotationDiff.userId,
annotationInfo = JsonUtils.mapToJson(annotationDiff.annotationInfo)
annotationInfo = JsonUtils.mapToJson(annotationDiff.annotationInfo).compactPrint
)
)
}

View File

@ -1,9 +1,9 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.models.{ PresentationInPod, PresentationPage }
import org.bigbluebutton.core.models.PresentationInPod
import slick.jdbc.PostgresProfile.api._
import PostgresProfile.api._
import spray.json.JsValue
import spray.json._
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
@ -12,7 +12,8 @@ case class PresPageDbModel(
pageId: String,
presentationId: String,
num: Int,
urls: String,
urlsJson: JsValue,
content: String,
slideRevealed: Boolean,
current: Boolean,
xOffset: Double,
@ -25,14 +26,15 @@ case class PresPageDbModel(
viewBoxHeight: Double,
maxImageWidth: Int,
maxImageHeight: Int,
converted: Boolean
uploadCompleted: Boolean
)
class PresPageDbTableDef(tag: Tag) extends Table[PresPageDbModel](tag, None, "pres_page") {
val pageId = column[String]("pageId", O.PrimaryKey)
val presentationId = column[String]("presentationId")
val num = column[Int]("num")
val urls = column[String]("urls")
val urlsJson = column[JsValue]("urlsJson")
val content = column[String]("content")
val slideRevealed = column[Boolean]("slideRevealed")
val current = column[Boolean]("current")
val xOffset = column[Double]("xOffset")
@ -45,12 +47,46 @@ class PresPageDbTableDef(tag: Tag) extends Table[PresPageDbModel](tag, None, "pr
val viewBoxHeight = column[Double]("viewBoxHeight")
val maxImageWidth = column[Int]("maxImageWidth")
val maxImageHeight = column[Int]("maxImageHeight")
val converted = column[Boolean]("converted")
val uploadCompleted = column[Boolean]("uploadCompleted")
// val presentation = foreignKey("presentation_fk", presentationId, Presentations)(_.presentationId, onDelete = ForeignKeyAction.Cascade)
def * = (pageId, presentationId, num, urls, slideRevealed, current, xOffset, yOffset, widthRatio, heightRatio, width, height, viewBoxWidth, viewBoxHeight, maxImageWidth, maxImageHeight, converted) <> (PresPageDbModel.tupled, PresPageDbModel.unapply)
def * = (pageId, presentationId, num, urlsJson, content, slideRevealed, current, xOffset, yOffset, widthRatio, heightRatio, width, height, viewBoxWidth, viewBoxHeight, maxImageWidth, maxImageHeight, uploadCompleted) <> (PresPageDbModel.tupled, PresPageDbModel.unapply)
}
object PresPageDAO {
implicit val mapFormat: JsonWriter[Map[String, String]] = new JsonWriter[Map[String, String]] {
def write(m: Map[String, String]): JsValue = {
JsObject(m.map { case (k, v) => k -> JsString(v) })
}
}
def insert(presentationId: String, page: PresentationPage) = {
DatabaseConnection.db.run(
TableQuery[PresPageDbTableDef].insertOrUpdate(
PresPageDbModel(
pageId = page.id,
presentationId = presentationId,
num = page.num,
urlsJson = page.urls.toJson,
content = page.content,
slideRevealed = page.current,
current = page.current,
xOffset = page.xOffset,
yOffset = page.yOffset,
widthRatio = page.widthRatio,
heightRatio = page.heightRatio,
width = page.width,
height = page.height,
viewBoxWidth = 1,
viewBoxHeight = 1,
maxImageWidth = 1440,
maxImageHeight = 1080,
uploadCompleted = page.converted
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on PresentationPage table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PresentationPage: $e")
}
}
def setCurrentPage(presentation: PresentationInPod, pageId: String) = {
DatabaseConnection.db.run(

View File

@ -1,27 +1,70 @@
package org.bigbluebutton.core.db
import slick.jdbc.PostgresProfile.api._
import org.bigbluebutton.core.models.{ PresentationInPod }
import PostgresProfile.api._
import org.bigbluebutton.core.models.PresentationInPod
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{Failure, Success}
import spray.json._
case class PresPresentationDbModel(presentationId: String, meetingId: String, current: Boolean, downloadable: Boolean, removable: Boolean, uploadCompleted: Boolean, numPages: Int, errorMsgKey: String, errorDetails: String)
import scala.concurrent.Future
case class PresPresentationDbModel(
presentationId: String,
meetingId: String,
uploadUserId: Option[String],
uploadTemporaryId: Option[String],
uploadToken: Option[String],
name: String,
filenameConverted: String,
isDefault: Boolean,
current: Boolean,
downloadable: Boolean,
downloadFileExtension: Option[String],
downloadFileUri: Option[String],
removable: Boolean,
uploadInProgress: Boolean,
uploadCompleted: Boolean,
uploadErrorMsgKey: Option[String],
uploadErrorDetailsJson: Option[JsValue],
totalPages: Int,
exportToChatStatus: Option[String],
exportToChatCurrentPage: Option[Int],
exportToChatHasError: Option[Boolean]
)
class PresPresentationDbTableDef(tag: Tag) extends Table[PresPresentationDbModel](tag, None, "pres_presentation") {
val presentationId = column[String]("presentationId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
val uploadUserId = column[Option[String]]("uploadUserId")
val uploadTemporaryId = column[Option[String]]("uploadTemporaryId")
val uploadToken = column[Option[String]]("uploadToken")
val name = column[String]("name")
val filenameConverted = column[String]("filenameConverted")
val isDefault = column[Boolean]("isDefault")
val current = column[Boolean]("current")
val downloadable = column[Boolean]("downloadable")
val downloadFileExtension = column[Option[String]]("downloadFileExtension")
val downloadFileUri = column[Option[String]]("downloadFileUri")
val removable = column[Boolean]("removable")
val uploadInProgress = column[Boolean]("uploadInProgress")
val uploadCompleted = column[Boolean]("uploadCompleted")
val numPages = column[Int]("numPages")
val errorMsgKey = column[String]("errorMsgKey")
val errorDetails = column[String]("errorDetails")
val uploadErrorMsgKey = column[Option[String]]("uploadErrorMsgKey")
val uploadErrorDetailsJson = column[Option[JsValue]]("uploadErrorDetailsJson")
val totalPages = column[Int]("totalPages")
val exportToChatStatus = column[Option[String]]("exportToChatStatus")
val exportToChatCurrentPage = column[Option[Int]]("exportToChatCurrentPage")
val exportToChatHasError = column[Option[Boolean]]("exportToChatHasError")
// val meeting = foreignKey("meeting_fk", meetingId, Meetings)(_.meetingId, onDelete = ForeignKeyAction.Cascade)
def * = (presentationId, meetingId, current, downloadable, removable, uploadCompleted, numPages, errorMsgKey, errorDetails) <> (PresPresentationDbModel.tupled, PresPresentationDbModel.unapply)
def * = (
presentationId, meetingId, uploadUserId, uploadTemporaryId, uploadToken, name, filenameConverted, isDefault, current, downloadable, downloadFileExtension, downloadFileUri, removable,
uploadInProgress, uploadCompleted, uploadErrorMsgKey, uploadErrorDetailsJson, totalPages,
exportToChatStatus, exportToChatCurrentPage, exportToChatHasError
) <> (PresPresentationDbModel.tupled, PresPresentationDbModel.unapply)
}
object PresPresentationDAO {
@ -31,24 +74,107 @@ object PresPresentationDAO {
}
}
def insertOrUpdate(meetingId: String, presentation: PresentationInPod) = {
DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef].insertOrUpdate(
def insertToken(meetingId: String, userId: String, temporaryId: String, presentationId: String, uploadToken: String, filename: String) = {
val dbRun = DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef].forceInsert(
PresPresentationDbModel(
presentationId = presentation.id,
presentationId = presentationId,
meetingId = meetingId,
uploadUserId = Some(userId),
uploadTemporaryId = Some(temporaryId),
uploadToken = Some(uploadToken),
name = filename,
filenameConverted = "",
isDefault = false,
current = false, //Set after pages were inserted
downloadable = presentation.downloadable,
removable = presentation.removable,
uploadCompleted = presentation.uploadCompleted,
numPages = presentation.numPages,
errorMsgKey = presentation.errorMsgKey,
errorDetails = presentation.errorDetails.toJson.asJsObject.compactPrint
downloadable = false,
downloadFileExtension = None,
downloadFileUri = None,
removable = false,
uploadInProgress = false,
uploadCompleted = false,
totalPages = 0,
uploadErrorMsgKey = None,
uploadErrorDetailsJson = None,
exportToChatStatus = None,
exportToChatCurrentPage = None,
exportToChatHasError = None,
)
)
)
dbRun.onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on Presentation table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting Presentation: $e")
}
dbRun
}
def updateConversionStarted(meetingId: String, presentation: PresentationInPod) = {
val checkAndInsert = DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef]
.filter(_.presentationId === presentation.id).exists.result).flatMap { exists =>
if (!exists) {
insertToken(meetingId, "", "", presentation.id, "", presentation.name)
} else {
Future.successful(0)
}
}
checkAndInsert.flatMap { _ =>
DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef]
.filter(_.presentationId === presentation.id)
.map(p => (
p.name,
p.filenameConverted,
p.isDefault,
p.downloadable,
p.downloadFileExtension,
p.removable,
p.uploadInProgress,
p.uploadCompleted,
p.totalPages))
.update(
(presentation.name,
presentation.filenameConverted,
presentation.default,
presentation.downloadable,
presentation.downloadFileExtension match {
case "" => None
case downloadFileExtension => Some(downloadFileExtension)
},
presentation.removable,
!presentation.uploadCompleted,
presentation.uploadCompleted,
presentation.numPages
))
)
}.onComplete {
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated basicData on PresPresentation table")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating basicData on PresPresentation: $e")
}
}
def updatePages(presentation: PresentationInPod) = {
DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef]
.filter(_.presentationId === presentation.id)
.map(p => (p.downloadFileExtension, p.uploadInProgress, p.uploadCompleted, p.totalPages))
.update((
presentation.downloadFileExtension match {
case "" => None
case downloadFileExtension => Some(downloadFileExtension)
},
!presentation.uploadCompleted,
presentation.uploadCompleted,
presentation.numPages,
))
).onComplete {
case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on Presentation table!")
DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on PresPresentation table!")
DatabaseConnection.db.run(DBIO.sequence(
for {
@ -59,7 +185,8 @@ object PresPresentationDAO {
pageId = page._2.id,
presentationId = presentation.id,
num = page._2.num,
urls = page._2.urls.toJson.asJsObject.compactPrint,
urlsJson = page._2.urls.toJson,
content = page._2.content,
slideRevealed = page._2.current,
current = page._2.current,
xOffset = page._2.xOffset,
@ -72,22 +199,23 @@ object PresPresentationDAO {
viewBoxHeight = 1,
maxImageWidth = 1440,
maxImageHeight = 1080,
converted = page._2.converted
uploadCompleted = page._2.converted
)
)
}
).transactionally)
.onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on PresentationPage table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting PresentationPage: $e")
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on PresentationPage table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating PresentationPage: $e")
}
//Set current
if (presentation.current) {
setCurrentPres(presentation.id)
}
}
case Failure(e) => DatabaseConnection.logger.error(s"Error inserting Presentation: $e")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating user: $e")
}
}
@ -95,23 +223,83 @@ object PresPresentationDAO {
DatabaseConnection.db.run(
sqlu"""UPDATE pres_presentation SET
"current" = (case when "presentationId" = ${presentationId} then true else false end)
WHERE "meetingId" = (select "meetingId" from pres_presentation where "presentationId" = ${presentationId})"""
WHERE "meetingId" = (select "meetingId" from pres_presentation where "presentationId" = ${presentationId})
AND exists (select 1 from pres_page where "presentationId" = ${presentationId} AND "current" IS true)"""
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated current on PresPresentation table")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating current on PresPresentation: $e")
}
}
def updateDownloadable(presentationId: String, downloadable : Boolean, downloadableExtension: String) = {
DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef]
.filter(_.presentationId === presentationId)
.map(p => (p.downloadable, p.downloadFileExtension))
.update((downloadable, Some(downloadableExtension)))
).onComplete {
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated downloadable on PresPresentation table")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating downloadable on PresPresentation: $e")
}
}
def updateDownloadUri(presentationId: String, downloadFileUri: String) = {
DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef]
.filter(_.presentationId === presentationId)
.map(p => p.downloadFileUri)
.update(Some(downloadFileUri))
).onComplete {
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated originalFileURI on PresPresentation table")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating originalFileURI on PresPresentation: $e")
}
}
def updateErrors(presentationId: String, errorMsgKey: String, errorDetails: scala.collection.immutable.Map[String, String]) = {
DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef]
.filter(_.presentationId === presentationId)
.map(p => (p.errorMsgKey, p.errorDetails))
.update(errorMsgKey, errorDetails.toJson.asJsObject.compactPrint)
.map(p => (p.uploadErrorMsgKey, p.uploadErrorDetailsJson))
.update(Some(errorMsgKey), Some(errorDetails.toJson))
).onComplete {
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated errorMsgKey on PresPresentation table")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating errorMsgKey on PresPresentation: $e")
}
}
def updateExportToChat(presentationId: String, exportToChatStatus: String, exportToChatCurrentPage: Int, exportToChatHasError: Boolean) = {
DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef]
.filter(_.presentationId === presentationId)
.map(p => (p.exportToChatStatus, p.exportToChatCurrentPage, p.exportToChatHasError))
.update(Some(exportToChatStatus), Some(exportToChatCurrentPage), Some(exportToChatHasError))
).onComplete {
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated exportToChat on PresPresentation table")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating exportToChat on PresPresentation: $e")
}
}
def updateExportToChatStatus(presentationId: String, exportToChatStatus: String) = {
DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef]
.filter(_.presentationId === presentationId)
.map(p => p.exportToChatStatus)
.update(Some(exportToChatStatus))
).onComplete {
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) updated exportToChatStatus on PresPresentation table")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating exportToChatStatus on PresPresentation: $e")
}
}
def delete(presentationId: String) = {
DatabaseConnection.db.run(
TableQuery[PresPresentationDbTableDef]
.filter(_.presentationId === presentationId)
.delete
).onComplete {
case Success(rowAffected) => DatabaseConnection.logger.debug(s"$rowAffected row(s) deleted presentation on PresPresentation table")
case Failure(e) => DatabaseConnection.logger.error(s"Error deleting presentation on PresPresentation: $e")
}
}
}

View File

@ -1,7 +1,7 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.apps.TimerModel
import org.bigbluebutton.core.apps.TimerModel.{getAccumulated, getEndedAt, getIsACtive, getRunning, getStartedAt, getStopwatch, getTime, getTrack}
import org.bigbluebutton.core.apps.TimerModel.{getAccumulated, getEndedAt, getIsActive, getRunning, getStartedAt, getStopwatch, getTime, getTrack}
import slick.jdbc.PostgresProfile.api._
import scala.util.{Failure, Success}
@ -59,7 +59,7 @@ object TimerDAO {
TableQuery[TimerDbTableDef]
.filter(_.meetingId === meetingId)
.map(t => (t.stopwatch, t.running, t.active, t.time, t.accumulated, t.startedAt, t.endedAt, t.songTrack))
.update((getStopwatch(timerModel), getRunning(timerModel), getIsACtive(timerModel), getTime(timerModel), getAccumulated(timerModel), getStartedAt(timerModel), getEndedAt(timerModel), getTrack(timerModel))
.update((getStopwatch(timerModel), getRunning(timerModel), getIsActive(timerModel), getTime(timerModel), getAccumulated(timerModel), getStartedAt(timerModel), getEndedAt(timerModel), getTrack(timerModel))
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on Timer table!")

View File

@ -0,0 +1,38 @@
package org.bigbluebutton.core.db
import PostgresProfile.api._
import slick.lifted.ProvenShape
import spray.json.JsValue
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class UserClientSettingsDbModel(
userId: String,
meetingId: String,
userClientSettingsJson: JsValue
)
class UserClientSettingsDbTableDef(tag: Tag) extends Table[UserClientSettingsDbModel](tag, "user_clientSettings") {
val userId = column[String]("userId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
val userClientSettingsJson = column[JsValue]("userClientSettingsJson")
override def * : ProvenShape[UserClientSettingsDbModel] = (userId, meetingId, userClientSettingsJson) <> (UserClientSettingsDbModel.tupled, UserClientSettingsDbModel.unapply)
}
object UserClientSettingsDAO {
def insert(userId: String, meetingId: String) = {
DatabaseConnection.db.run(
TableQuery[UserClientSettingsDbTableDef].insertOrUpdate(
UserClientSettingsDbModel(
userId = userId,
meetingId = meetingId,
userClientSettingsJson = JsonUtils.stringToJson("{}")
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on UserClientSettings table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting UserClientSettings: $e")
}
}
}

View File

@ -19,7 +19,7 @@ class UserConnectionStatusDbTableDef(tag: Tag) extends Table[UserConnectionStatu
val connectionAliveAt = column[Option[java.sql.Timestamp]]("connectionAliveAt")
}
object UserConnectionStatusdDAO {
object UserConnectionStatusDAO {
def insert(meetingId: String, userId: String) = {
DatabaseConnection.db.run(

View File

@ -1,5 +1,5 @@
package org.bigbluebutton.core.db
import org.bigbluebutton.core.models.{RegisteredUser}
import org.bigbluebutton.core.models.{RegisteredUser, VoiceUserState}
import slick.jdbc.PostgresProfile.api._
import scala.concurrent.ExecutionContext.Implicits.global
@ -13,21 +13,25 @@ case class UserDbModel(
role: String,
avatar: String = "",
color: String = "",
sessionToken: String = "",
authed: Boolean = false,
joined: Boolean = false,
joinErrorMessage: Option[String],
joinErrorCode: Option[String],
banned: Boolean = false,
loggedOut: Boolean = false,
guest: Boolean,
guestStatus: String,
registeredOn: Long,
excludeFromDashboard: Boolean,
enforceLayout: Option[String],
)
class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
override def * = (
userId,extId,meetingId,name,role,avatar,color,authed,joined,banned,loggedOut,guest,guestStatus,registeredOn,excludeFromDashboard) <> (UserDbModel.tupled, UserDbModel.unapply)
userId,extId,meetingId,name,role,avatar,color, sessionToken, 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")
@ -35,14 +39,18 @@ class UserDbTableDef(tag: Tag) extends Table[UserDbModel](tag, None, "user") {
val role = column[String]("role")
val avatar = column[String]("avatar")
val color = column[String]("color")
val sessionToken = column[String]("sessionToken")
val authed = column[Boolean]("authed")
val joined = column[Boolean]("joined")
val joinErrorCode = column[Option[String]]("joinErrorCode")
val joinErrorMessage = column[Option[String]]("joinErrorMessage")
val banned = column[Boolean]("banned")
val loggedOut = column[Boolean]("loggedOut")
val guest = column[Boolean]("guest")
val guestStatus = column[String]("guestStatus")
val registeredOn = column[Long]("registeredOn")
val excludeFromDashboard = column[Boolean]("excludeFromDashboard")
val enforceLayout = column[Option[String]]("enforceLayout")
}
object UserDAO {
@ -57,23 +65,30 @@ object UserDAO {
role = regUser.role,
avatar = regUser.avatarURL,
color = regUser.color,
sessionToken = regUser.sessionToken,
authed = regUser.authed,
joined = regUser.joined,
joinErrorCode = None,
joinErrorMessage = None,
banned = regUser.banned,
loggedOut = regUser.loggedOut,
guest = regUser.guest,
guestStatus = regUser.guestStatus,
registeredOn = regUser.registeredOn,
excludeFromDashboard = regUser.excludeFromDashboard
excludeFromDashboard = regUser.excludeFromDashboard,
enforceLayout = regUser.enforceLayout match {
case "" => None
case enforceLayout: String => Some(enforceLayout)
}
)
)
).onComplete {
case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted in User table!")
ChatUserDAO.insertUserPublicChat(meetingId, regUser.id)
UserConnectionStatusdDAO.insert(meetingId, regUser.id)
UserConnectionStatusDAO.insert(meetingId, regUser.id)
UserCustomParameterDAO.insert(regUser.id, regUser.customParameters)
UserLocalSettingsDAO.insert(regUser.id, meetingId)
UserClientSettingsDAO.insert(regUser.id, meetingId)
ChatUserDAO.insertUserPublicChat(meetingId, regUser.id)
}
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting user: $e")
}
@ -91,18 +106,33 @@ object UserDAO {
}
}
def updateVoiceUserJoined(voiceUserState: VoiceUserState) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.userId === voiceUserState.intId)
.map(u => (u.guest, u.guestStatus, u.authed, u.joined))
.update((false, "ALLOW", true, true))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user voice table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating user voice: $e")
}
}
def updateJoinError(userId: String, joinErrorCode: String, joinErrorMessage: String) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.userId === userId)
.map(u => (u.joined, u.joinErrorCode, u.joinErrorMessage))
.update((false, Some(joinErrorCode), Some(joinErrorMessage)))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user (Joined) table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error updating user (Joined): $e")
}
}
def delete(intId: String) = {
// DatabaseConnection.db.run(
// TableQuery[UserDbTableDef]
// .filter(_.userId === intId)
// .delete
// ).onComplete {
// case Success(rowsAffected) => DatabaseConnection.logger.debug(s"User ${intId} deleted")
// case Failure(e) => DatabaseConnection.logger.error(s"Error deleting user ${intId}: $e")
// }
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.userId === intId)

View File

@ -0,0 +1,64 @@
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 UserGraphqlConnectionDbModel (
graphqlConnectionId: Option[Int],
sessionToken: String,
middlewareConnectionId: String,
stablishedAt: 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
) <> (UserGraphqlConnectionDbModel.tupled, UserGraphqlConnectionDbModel.unapply)
val graphqlConnectionId = column[Option[Int]]("graphqlConnectionId", O.PrimaryKey, O.AutoInc)
val sessionToken = column[String]("sessionToken")
val middlewareConnectionId = column[String]("middlewareConnectionId")
val stablishedAt = column[java.sql.Timestamp]("stablishedAt")
val closedAt = column[Option[java.sql.Timestamp]]("closedAt")
}
object UserGraphqlConnectionDAO {
def insert(sessionToken: String, middlewareConnectionId: String) = {
DatabaseConnection.db.run(
TableQuery[UserGraphqlConnectionDbTableDef].insertOrUpdate(
UserGraphqlConnectionDbModel(
graphqlConnectionId = None,
sessionToken = sessionToken,
middlewareConnectionId = middlewareConnectionId,
stablishedAt = new java.sql.Timestamp(System.currentTimeMillis()),
closedAt = None
)
)
).onComplete {
case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on user_graphqlConnection table!")
}
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting user_graphqlConnection: $e")
}
}
def updateClosed(sessionToken: String, middlewareConnectionId: String) = {
DatabaseConnection.db.run(
TableQuery[UserGraphqlConnectionDbTableDef]
.filter(_.sessionToken === sessionToken)
.filter(_.middlewareConnectionId === middlewareConnectionId)
.filter(_.closedAt.isEmpty)
.map(u => u.closedAt)
.update(Some(new java.sql.Timestamp(System.currentTimeMillis())))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user_graphqlConnection table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating user_graphqlConnection: $e")
}
}
}

View File

@ -1,38 +0,0 @@
package org.bigbluebutton.core.db
import slick.jdbc.PostgresProfile.api._
import slick.lifted.{ ProvenShape }
import scala.concurrent.ExecutionContext.Implicits.global
import scala.util.{ Failure, Success }
case class UserLocalSettingsDbModel(
userId: String,
meetingId: String,
// settingsJson: String
)
class UserLocalSettingsDbTableDef(tag: Tag) extends Table[UserLocalSettingsDbModel](tag, "user_localSettings") {
val userId = column[String]("userId", O.PrimaryKey)
val meetingId = column[String]("meetingId")
// val settingsJson = column[String]("settingsJson")
override def * : ProvenShape[UserLocalSettingsDbModel] = (userId, meetingId) <> (UserLocalSettingsDbModel.tupled, UserLocalSettingsDbModel.unapply)
}
object UserLocalSettingsDAO {
def insert(userId: String, meetingId: String) = {
DatabaseConnection.db.run(
TableQuery[UserLocalSettingsDbTableDef].insertOrUpdate(
UserLocalSettingsDbModel(
userId = userId,
meetingId = meetingId
// settingsJson = parameter._2
)
)
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on UserLocalSettings table!")
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting UserLocalSettings: $e")
}
}
}

View File

@ -9,17 +9,17 @@ import scala.util.{ Failure, Success }
case class UserReactionDbModel(
userId: String,
reactionEmoji: String,
duration: Int,
durationInSeconds: Int,
createdAt: java.sql.Timestamp
)
class UserReactionDbTableDef(tag: Tag) extends Table[UserReactionDbModel](tag, "user_reaction") {
val userId = column[String]("userId")
val reactionEmoji = column[String]("reactionEmoji")
val duration = column[Int]("duration")
val durationInSeconds = column[Int]("durationInSeconds")
val createdAt = column[java.sql.Timestamp]("createdAt")
override def * : ProvenShape[UserReactionDbModel] = (userId, reactionEmoji, duration, createdAt) <> (UserReactionDbModel.tupled, UserReactionDbModel.unapply)
override def * : ProvenShape[UserReactionDbModel] = (userId, reactionEmoji, durationInSeconds, createdAt) <> (UserReactionDbModel.tupled, UserReactionDbModel.unapply)
}
object UserReactionDAO {
@ -29,7 +29,7 @@ object UserReactionDAO {
UserReactionDbModel(
userId = userId,
reactionEmoji = reactionEmoji,
duration = 60,
durationInSeconds = 60,
createdAt = new java.sql.Timestamp(System.currentTimeMillis())
)
)

View File

@ -0,0 +1,49 @@
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 UserVoiceConfStateDbModel(
userId: String,
voiceConf: String,
voiceConfCallSession: String,
voiceConfClientSession: String,
voiceConfCallState: String,
)
class UserVoiceConfStateDbTableDef(tag: Tag) extends Table[UserVoiceConfStateDbModel](tag, None, "user_voice") {
override def * = (
userId, voiceConf, voiceConfCallSession, voiceConfClientSession, voiceConfCallState
) <> (UserVoiceConfStateDbModel.tupled, UserVoiceConfStateDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey)
val voiceConf = column[String]("voiceConf")
val voiceConfCallSession = column[String]("voiceConfCallSession")
val voiceConfClientSession = column[String]("voiceConfClientSession")
val voiceConfCallState = column[String]("voiceConfCallState")
}
object UserVoiceConfStateDAO {
def insertOrUpdate(userId: String, voiceConf: String, voiceConfCallSession: String, clientSession: String, callState: String) = {
DatabaseConnection.db.run(
TableQuery[UserVoiceConfStateDbTableDef].insertOrUpdate(
UserVoiceConfStateDbModel(
userId = userId,
voiceConf = voiceConf,
voiceConfCallSession = voiceConfCallSession,
voiceConfClientSession = clientSession,
voiceConfCallState = callState,
)
)
).onComplete {
case Success(rowsAffected) => {
DatabaseConnection.logger.debug(s"$rowsAffected row(s) inserted on user_voice table!")
}
case Failure(e) => DatabaseConnection.logger.debug(s"Error inserting voice: $e")
}
}
}

View File

@ -19,7 +19,6 @@ case class UserVoiceDbModel(
talking: Boolean,
floor: Boolean,
lastFloorTime: String,
voiceConf: String,
startTime: Option[Long],
endTime: Option[Long],
)
@ -27,7 +26,7 @@ case class UserVoiceDbModel(
class UserVoiceDbTableDef(tag: Tag) extends Table[UserVoiceDbModel](tag, None, "user_voice") {
override def * = (
userId, voiceUserId, callerName, callerNum, callingWith, joined, listenOnly,
muted, spoke, talking, floor, lastFloorTime, voiceConf, startTime, endTime
muted, spoke, talking, floor, lastFloorTime, startTime, endTime
) <> (UserVoiceDbModel.tupled, UserVoiceDbModel.unapply)
val userId = column[String]("userId", O.PrimaryKey)
val voiceUserId = column[String]("voiceUserId")
@ -42,6 +41,9 @@ class UserVoiceDbTableDef(tag: Tag) extends Table[UserVoiceDbModel](tag, None, "
val floor = column[Boolean]("floor")
val lastFloorTime = column[String]("lastFloorTime")
val voiceConf = column[String]("voiceConf")
val voiceConfCallSession = column[String]("voiceConfCallSession")
val voiceConfClientSession = column[String]("voiceConfClientSession")
val voiceConfCallState = column[String]("voiceConfCallState")
val startTime = column[Option[Long]]("startTime")
val endTime = column[Option[Long]]("endTime")
}
@ -64,7 +66,6 @@ object UserVoiceDAO {
talking = voiceUserState.talking,
floor = voiceUserState.floor,
lastFloorTime = voiceUserState.lastFloorTime,
voiceConf = "",
startTime = None,
endTime = None
)
@ -85,7 +86,7 @@ object UserVoiceDAO {
.update((voiceUserState.listenOnly, voiceUserState.muted, voiceUserState.floor, voiceUserState.lastFloorTime))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"$rowsAffected row(s) updated on user_voice table!")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating user: $e")
case Failure(e) => DatabaseConnection.logger.error(s"Error updating user_voice: $e")
}
}
@ -113,7 +114,19 @@ object UserVoiceDAO {
}
}
def deleteUser(userId: String) = {
def delete(intId: String) = {
DatabaseConnection.db.run(
TableQuery[UserDbTableDef]
.filter(_.userId === intId)
.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 deleteUserVoice(userId: String) = {
//Meteor sets this props instead of removing
// muted: false
// talking: false
@ -124,10 +137,11 @@ object UserVoiceDAO {
DatabaseConnection.db.run(
TableQuery[UserVoiceDbTableDef]
.filter(_.userId === userId)
.delete
.map(u => (u.muted, u.talking, u.listenOnly, u.joined, u.spoke, u.startTime, u.endTime))
.update((false, false, false, false, false, None, None))
).onComplete {
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Voice of user ${userId} deleted")
case Failure(e) => DatabaseConnection.logger.error(s"Error deleting voice: $e")
case Success(rowsAffected) => DatabaseConnection.logger.debug(s"Voice of user ${userId} deleted (joined=false)")
case Failure(e) => DatabaseConnection.logger.error(s"Error deleting voice user: $e")
}
}

View File

@ -1,9 +1,6 @@
package org.bigbluebutton.core.models
import org.bigbluebutton.common2.domain.PageVO
import org.bigbluebutton.core.models.PresentationInPod
import org.bigbluebutton.core.util.RandomStringGenerator
import org.bigbluebutton.common2.msgs.AnnotationVO
import org.bigbluebutton.core.db.{ PresPageDAO, PresPresentationDAO }
object PresentationPodFactory {
@ -25,6 +22,7 @@ case class PresentationPage(
id: String,
num: Int,
urls: Map[String, String],
content: String,
current: Boolean = false,
xOffset: Double = 0,
yOffset: Double = 0,
@ -64,9 +62,11 @@ object PresentationInPod {
case class PresentationInPod(
id: String,
name: String,
default: Boolean = false,
current: Boolean = false,
pages: scala.collection.immutable.Map[String, PresentationPage],
downloadable: Boolean,
downloadFileExtension: String = "",
removable: Boolean,
filenameConverted: String = "",
uploadCompleted: Boolean,
@ -118,18 +118,18 @@ case class PresentationPod(id: String, currentPresenter: String,
Some(tempPod)
}
def setPresentationDownloadable(presentationId: String, downloadable: Boolean): Option[PresentationPod] = {
def setPresentationDownloadable(presentationId: String, downloadable: Boolean, downloadFileExtension: String): Option[PresentationPod] = {
var tempPod: PresentationPod = this
presentations.values foreach (curPres => { // unset previous current presentation
if (curPres.id != presentationId) {
val newPres = curPres.copy(downloadable = downloadable)
val newPres = curPres.copy(downloadable = downloadable, downloadFileExtension = downloadFileExtension)
tempPod = tempPod.addPresentation(newPres)
}
})
presentations.get(presentationId) match { // set new current presentation
case Some(pres) =>
val cp = pres.copy(downloadable = downloadable)
val cp = pres.copy(downloadable = downloadable, downloadFileExtension = downloadFileExtension)
tempPod = tempPod.addPresentation(cp)
case None => None
}
@ -235,10 +235,10 @@ case class PresentationPodManager(presentationPods: collection.immutable.Map[Str
}
}
def setPresentationDownloadableInPod(podId: String, presentationId: String, downloadable: Boolean): PresentationPodManager = {
def setPresentationDownloadableInPod(podId: String, presentationId: String, downloadable: Boolean, downloadFileExtension: String): PresentationPodManager = {
val updatedManager = for {
pod <- getPod(podId)
podWithAdjustedDownloadablePresentation <- pod.setPresentationDownloadable(presentationId, downloadable)
podWithAdjustedDownloadablePresentation <- pod.setPresentationDownloadable(presentationId, downloadable, downloadFileExtension)
} yield {
updatePresentationPod(podWithAdjustedDownloadablePresentation)

View File

@ -7,7 +7,8 @@ import org.bigbluebutton.core.domain.BreakoutRoom2x
object RegisteredUsers {
def create(userId: String, extId: String, name: String, roles: String,
authToken: String, sessionToken: String, avatar: String, color: String, guest: Boolean, authenticated: Boolean,
guestStatus: String, excludeFromDashboard: Boolean, customParameters: Map[String, String], loggedOut: Boolean): RegisteredUser = {
guestStatus: String, excludeFromDashboard: Boolean, enforceLayout: String,
customParameters: Map[String, String], loggedOut: Boolean): RegisteredUser = {
new RegisteredUser(
userId,
extId,
@ -25,6 +26,7 @@ object RegisteredUsers {
0,
false,
false,
enforceLayout,
customParameters,
loggedOut,
)
@ -214,6 +216,7 @@ case class RegisteredUser(
lastAuthTokenValidatedOn: Long,
joined: Boolean,
banned: Boolean,
enforceLayout: String,
customParameters: Map[String,String],
loggedOut: Boolean,
lastBreakoutRoom: BreakoutRoom2x = null,

View File

@ -39,7 +39,7 @@ object VoiceUsers {
}
def removeWithIntId(users: VoiceUsers, intId: String): Option[VoiceUserState] = {
UserVoiceDAO.deleteUser(intId)
UserVoiceDAO.deleteUserVoice(intId)
users.remove(intId)
}

View File

@ -346,8 +346,6 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[CreateNewPresentationPodPubMsg](envelope, jsonNode)
case RemovePresentationPodPubMsg.NAME =>
routeGenericMsg[RemovePresentationPodPubMsg](envelope, jsonNode)
case SetPresenterInPodReqMsg.NAME =>
routeGenericMsg[SetPresenterInPodReqMsg](envelope, jsonNode)
// Caption
case EditCaptionHistoryPubMsg.NAME =>
@ -417,6 +415,10 @@ class ReceivedJsonMsgHandlerActor(
case CreateGroupChatReqMsg.NAME =>
routeGenericMsg[CreateGroupChatReqMsg](envelope, jsonNode)
//Plugin
case DispatchPluginDataChannelMessageMsg.NAME =>
routeGenericMsg[DispatchPluginDataChannelMessageMsg](envelope, jsonNode)
// ExternalVideo
case StartExternalVideoPubMsg.NAME =>
routeGenericMsg[StartExternalVideoPubMsg](envelope, jsonNode)
@ -447,6 +449,13 @@ class ReceivedJsonMsgHandlerActor(
case TimerEndedPubMsg.NAME =>
routeGenericMsg[TimerEndedPubMsg](envelope, jsonNode)
// Messages from Graphql Middleware
case UserGraphqlConnectionStablishedSysMsg.NAME =>
route[UserGraphqlConnectionStablishedSysMsg](meetingManagerChannel, envelope, jsonNode)
case UserGraphqlConnectionClosedSysMsg.NAME =>
route[UserGraphqlConnectionClosedSysMsg](meetingManagerChannel, envelope, jsonNode)
case _ =>
log.error("Cannot route envelope name " + envelope.name)
// do nothing

View File

@ -5,6 +5,8 @@ import org.bigbluebutton.core.apps._
import org.bigbluebutton.core.models._
import org.bigbluebutton.core2.MeetingStatus2x
import java.util
class LiveMeeting(
val props: DefaultProps,
val status: MeetingStatus2x,
@ -23,5 +25,6 @@ class LiveMeeting(
val webcams: Webcams,
val voiceUsers: VoiceUsers,
val users2x: Users2x,
val guestsWaiting: GuestsWaiting
val guestsWaiting: GuestsWaiting,
val clientSettings: Map[String, Object],
)

View File

@ -39,6 +39,7 @@ import org.bigbluebutton.common2.msgs
import scala.concurrent.duration._
import org.bigbluebutton.core.apps.layout.LayoutApp2x
import org.bigbluebutton.core.apps.meeting.{ SyncGetMeetingInfoRespMsgHdlr, ValidateConnAuthTokenSysMsgHdlr }
import org.bigbluebutton.core.apps.plugin.PluginHdlrs
import org.bigbluebutton.core.apps.users.ChangeLockSettingsInMeetingCmdMsgHdlr
import org.bigbluebutton.core.db.UserStateDAO
import org.bigbluebutton.core.models.VoiceUsers.{ findAllFreeswitchCallers, findAllListenOnlyVoiceUsers }
@ -137,6 +138,7 @@ class MeetingActor(
val webcamApp2x = new WebcamApp2x
val wbApp = new WhiteboardApp2x
val timerApp2x = new TimerApp2x
val pluginHdlrs = new PluginHdlrs
object ExpiryTrackerHelper extends MeetingExpiryTrackerHelper
@ -263,6 +265,7 @@ class MeetingActor(
//=======================================
// internal messages
case msg: MonitorNumberOfUsersInternalMsg => handleMonitorNumberOfUsers(msg)
case msg: SetPresenterInDefaultPodInternalMsg => state = presentationPodsApp.handleSetPresenterInDefaultPodInternalMsg(msg, state, liveMeeting, msgBus)
case msg: ExtendMeetingDuration => handleExtendMeetingDuration(msg)
case msg: SendTimeRemainingAuditInternalMsg =>
@ -529,7 +532,6 @@ class MeetingActor(
case m: PresentationConversionCompletedSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PdfConversionInvalidErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: SetCurrentPagePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: SetPresenterInPodReqMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: RemovePresentationPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: SetPresentationDownloadablePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationConversionUpdateSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
@ -591,6 +593,9 @@ class MeetingActor(
state = groupChatApp.handle(m, state, liveMeeting, msgBus)
updateUserLastActivity(m.body.msg.sender.id)
// Plugin
case m: DispatchPluginDataChannelMessageMsg => pluginHdlrs.handle(m, state, liveMeeting)
// Webcams
case m: UserBroadcastCamStartMsg => webcamApp2x.handle(m, liveMeeting, msgBus)
case m: UserBroadcastCamStopMsg => webcamApp2x.handle(m, liveMeeting, msgBus)
@ -867,6 +872,7 @@ class MeetingActor(
val hasModeratorLeftRecently = (TimeUtil.timeNowInMs() - state.expiryTracker.endWhenNoModeratorDelayInMs) < state.expiryTracker.lastModeratorLeftOnInMs
if (!hasModeratorLeftRecently) {
log.info("Meeting will end due option endWhenNoModerator is enabled and all moderators have left the meeting. meetingId=" + props.meetingProp.intId)
endAllBreakoutRooms(eventBus, liveMeeting, state, MeetingEndReason.ENDED_DUE_TO_NO_MODERATOR)
sendEndMeetingDueToExpiry(
MeetingEndReason.ENDED_DUE_TO_NO_MODERATOR,
eventBus, outGW, liveMeeting,

View File

@ -1,6 +1,7 @@
package org.bigbluebutton.core.running
import org.apache.pekko.actor.ActorContext
import org.bigbluebutton.ClientSettings
import org.bigbluebutton.common2.domain.DefaultProps
import org.bigbluebutton.core.apps._
import org.bigbluebutton.core.bus._
@ -34,6 +35,7 @@ class RunningMeeting(val props: DefaultProps, outGW: OutMessageGateway,
private val deskshareModel = new ScreenshareModel
private val audioCaptions = new AudioCaptions
private val timerModel = new TimerModel
val clientSettings: Map[String, Object] = ClientSettings.getClientSettingsWithOverride(props.overrideClientSettings)
// meetingModel.setGuestPolicy(props.usersProp.guestPolicy)
@ -41,7 +43,7 @@ class RunningMeeting(val props: DefaultProps, outGW: OutMessageGateway,
// easy to test.
private val liveMeeting = new LiveMeeting(props, meetingStatux2x, deskshareModel, audioCaptions, timerModel,
chatModel, externalVideoModel, layouts, pads, registeredUsers, polls2x, wbModel, presModel, captionModel,
webcams, voiceUsers, users2x, guestsWaiting)
webcams, voiceUsers, users2x, guestsWaiting, clientSettings)
GuestsWaiting.setGuestPolicy(
liveMeeting.props.meetingProp.intId,

View File

@ -10,7 +10,7 @@ object RunningMeetings {
def add(meetings: RunningMeetings, meeting: RunningMeeting): RunningMeeting = {
meetings.save(meeting)
MeetingDAO.insert(meeting.props)
MeetingDAO.insert(meeting.props, meeting.clientSettings)
meeting
}

View File

@ -1,8 +1,8 @@
package org.bigbluebutton.core2.message.senders
import org.bigbluebutton.common2.domain.DefaultProps
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, MessageTypes, Routing, ValidateConnAuthTokenSysRespMsg, ValidateConnAuthTokenSysRespMsgBody, NotifyAllInMeetingEvtMsg, NotifyAllInMeetingEvtMsgBody, NotifyRoleInMeetingEvtMsg, NotifyRoleInMeetingEvtMsgBody, NotifyUserInMeetingEvtMsg, NotifyUserInMeetingEvtMsgBody, _ }
import org.bigbluebutton.core.models.GuestWaiting
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, MessageTypes, NotifyAllInMeetingEvtMsg, NotifyAllInMeetingEvtMsgBody, NotifyRoleInMeetingEvtMsg, NotifyRoleInMeetingEvtMsgBody, NotifyUserInMeetingEvtMsg, NotifyUserInMeetingEvtMsgBody, Routing, ValidateConnAuthTokenSysRespMsg, ValidateConnAuthTokenSysRespMsgBody, _ }
import org.bigbluebutton.core.models.{ GuestWaiting, PresentationPod }
object MsgBuilder {
def buildGuestPolicyChangedEvtMsg(meetingId: String, userId: String, policy: String, setBy: String): BbbCommonEnvCoreMsg = {

View File

@ -56,7 +56,7 @@ object FakeUserGenerator {
val color = "#ff6242"
val ru = RegisteredUsers.create(userId = id, extId, name, role,
authToken, sessionToken, avatarURL, color, guest, authed, guestStatus = GuestStatus.ALLOW, false, Map(), false)
authToken, sessionToken, avatarURL, color, guest, authed, guestStatus = GuestStatus.ALLOW, false, "", Map(), false)
RegisteredUsers.add(users, ru, meetingId)
ru
}

View File

@ -0,0 +1,43 @@
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: UserGraphqlConnectionStablishedSysMsg => handleUserGraphqlConnectionStablishedSysMsg(m)
case m: UserGraphqlConnectionClosedSysMsg => handleUserGraphqlConnectionClosedSysMsg(m)
case _ => // message not to be handled.
}
}
private def handleUserGraphqlConnectionStablishedSysMsg(msg: UserGraphqlConnectionStablishedSysMsg) {
UserGraphqlConnectionDAO.insert(msg.body.sessionToken, msg.body.browserConnectionId)
}
private def handleUserGraphqlConnectionClosedSysMsg(msg: UserGraphqlConnectionClosedSysMsg) {
UserGraphqlConnectionDAO.updateClosed(msg.body.sessionToken, msg.body.browserConnectionId)
}
}

View File

@ -397,10 +397,33 @@ class LearningDashboardActor(
user <- findUserByIntId(meeting, msg.body.userId)
} yield {
if (msg.body.reactionEmoji != "none") {
//Ignore multiple Reactions to prevent flooding
val hasSameReactionInLast30Seconds = user.reactions.filter(r => {
System.currentTimeMillis() - r.sentOn < (30 * 1000) && r.name == msg.body.reactionEmoji
}).length > 0
if(!hasSameReactionInLast30Seconds) {
val updatedUser = user.copy(reactions = user.reactions :+ Emoji(msg.body.reactionEmoji))
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser))
meetings += (updatedMeeting.intId -> updatedMeeting)
//Convert Reactions to legacy Emoji (while LearningDashboard doesn't support Reactions)
val emoji = msg.body.reactionEmoji.codePointAt(0) match {
case 128515 => "happy"
case 128528 => "neutral"
case 128577 => "sad"
case 128077 => "thumbsUp"
case 128078 => "thumbsDown"
case 128079 => "applause"
case _ => "none"
}
if (emoji != "none") {
val updatedUserWithEmoji = updatedUser.copy(emojis = user.emojis :+ Emoji(emoji))
val updatedMeetingWithEmoji = meeting.copy(users = meeting.users + (updatedUserWithEmoji.userKey -> updatedUserWithEmoji))
meetings += (updatedMeeting.intId -> updatedMeetingWithEmoji)
}
}
}
}
}

View File

@ -13,9 +13,10 @@ object TestDataGen {
val sessionToken = RandomStringGenerator.randomAlphanumericString(16)
val avatarURL = "https://www." + RandomStringGenerator.randomAlphanumericString(32) + ".com/" +
RandomStringGenerator.randomAlphanumericString(10) + ".png"
val color = "#ff6242"
val ru = RegisteredUsers.create(userId = id, extId, name, role,
authToken, sessionToken, avatarURL, guest, authed, GuestStatus.ALLOW, false)
authToken, sessionToken, avatarURL, color, guest, authed, GuestStatus.ALLOW, false, "", Map(), false)
RegisteredUsers.add(users, ru, meetingId = "test")
ru

View File

@ -27,6 +27,11 @@ pekko {
}
}
client {
clientSettingsFilePath = "/usr/share/meteor/bundle/programs/server/assets/app/config/settings.yml"
clientSettingsOverrideFilePath = "/etc/bigbluebutton/bbb-html5.yml"
}
redis {
host="127.0.0.1"
port=6379

View File

@ -19,6 +19,7 @@ object Dependencies {
val sl4j = "1.7.32"
val pool = "2.11.1"
val codec = "1.15"
val jacksonDataFormat = "2.13.5"
// Redis
val lettuce = "6.1.5.RELEASE"
@ -40,6 +41,7 @@ object Dependencies {
val commonsCodec = "commons-codec" % "commons-codec" % Versions.codec
val lettuceCore = "io.lettuce" % "lettuce-core" % Versions.lettuce
val jacksonDataFormat = "com.fasterxml.jackson.dataformat" % "jackson-dataformat-yaml" % Versions.jacksonDataFormat
}
object Test {
@ -64,5 +66,6 @@ object Dependencies {
Compile.sl4jApi,
Compile.commonsCodec,
Compile.apachePool2,
Compile.lettuceCore) ++ testing
Compile.lettuceCore,
Compile.jacksonDataFormat) ++ testing
}

View File

@ -89,7 +89,8 @@ case class DefaultProps(
metadataProp: MetadataProp,
lockSettingsProps: LockSettingsProps,
systemProps: SystemProps,
groups: Vector[GroupProps]
groups: Vector[GroupProps],
overrideClientSettings: String
)
case class StartEndTimeStatus(startTime: Long, endTime: Long)

View File

@ -2,7 +2,7 @@ package org.bigbluebutton.common2.domain
case class PresentationVO(id: String, temporaryPresentationId: String, name: String, current: Boolean = false,
pages: Vector[PageVO], downloadable: Boolean, removable: Boolean,
isInitialPresentation: Boolean, filenameConverted: String)
defaultPresentation: Boolean, filenameConverted: String)
case class PageVO(id: String, num: Int, thumbUri: String = "",
txtUri: String, svgUri: String, current: Boolean = false, xOffset: Double = 0,
@ -15,6 +15,7 @@ case class PresentationPageConvertedVO(
id: String,
num: Int,
urls: Map[String, String],
content: String,
current: Boolean = false,
width: Double = 1440D,
height: Double = 1080D

View File

@ -11,6 +11,7 @@ object GroupChatMessageType {
val POLL = "poll"
val BREAKOUTROOM_MOD_MSG = "breakoutRoomModeratorMsg"
val PUBLIC_CHAT_HIST_CLEARED = "publicChatHistoryCleared"
val USER_AWAY_STATUS_MSG = "userAwayStatusMsg"
}
case class GroupChatUser(id: String, name: String = "", role: String = "VIEWER")

View File

@ -0,0 +1,16 @@
package org.bigbluebutton.common2.msgs
// In messages
/**
* Sent from graphql-actions to bbb-akka
*/
object DispatchPluginDataChannelMessageMsg { val NAME = "DispatchPluginDataChannelMessageMsg" }
case class DispatchPluginDataChannelMessageMsg(header: BbbClientMsgHeader, body: DispatchPluginDataChannelMessageMsgBody) extends StandardMsg
case class DispatchPluginDataChannelMessageMsgBody(
pluginName: String,
dataChannel: String,
payloadJson: String,
toRoles: List[String],
toUserIds: List[String],
)

View File

@ -13,7 +13,7 @@ case class RemovePresentationPodPubMsgBody(podId: String)
object PresentationUploadTokenReqMsg { val NAME = "PresentationUploadTokenReqMsg" }
case class PresentationUploadTokenReqMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenReqMsgBody) extends StandardMsg
case class PresentationUploadTokenReqMsgBody(podId: String, filename: String, temporaryPresentationId: String)
case class PresentationUploadTokenReqMsgBody(podId: String, filename: String, uploadTemporaryId: String)
object GetAllPresentationPodsReqMsg { val NAME = "GetAllPresentationPodsReqMsg" }
case class GetAllPresentationPodsReqMsg(header: BbbClientMsgHeader, body: GetAllPresentationPodsReqMsgBody) extends StandardMsg
@ -23,10 +23,6 @@ object SetCurrentPagePubMsg { val NAME = "SetCurrentPagePubMsg" }
case class SetCurrentPagePubMsg(header: BbbClientMsgHeader, body: SetCurrentPagePubMsgBody) extends StandardMsg
case class SetCurrentPagePubMsgBody(podId: String, presentationId: String, pageId: String)
object SetPresenterInPodReqMsg { val NAME = "SetPresenterInPodReqMsg" }
case class SetPresenterInPodReqMsg(header: BbbClientMsgHeader, body: SetPresenterInPodReqMsgBody) extends StandardMsg
case class SetPresenterInPodReqMsgBody(podId: String, nextPresenterId: String)
object RemovePresentationPubMsg { val NAME = "RemovePresentationPubMsg" }
case class RemovePresentationPubMsg(header: BbbClientMsgHeader, body: RemovePresentationPubMsgBody) extends StandardMsg
case class RemovePresentationPubMsgBody(podId: String, presentationId: String)
@ -139,7 +135,9 @@ case class PresentationPageConversionStartedSysMsgBody(
podId: String,
presentationId: String,
current: Boolean,
default: Boolean,
presName: String,
presFilenameConverted: String,
downloadable: Boolean,
removable: Boolean,
authzToken: String,
@ -221,7 +219,7 @@ case class PdfConversionInvalidErrorEvtMsgBody(podId: String, messageKey: String
object PresentationUploadTokenPassRespMsg { val NAME = "PresentationUploadTokenPassRespMsg" }
case class PresentationUploadTokenPassRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenPassRespMsgBody) extends StandardMsg
case class PresentationUploadTokenPassRespMsgBody(podId: String, authzToken: String, filename: String, temporaryPresentationId: String)
case class PresentationUploadTokenPassRespMsgBody(podId: String, authzToken: String, filename: String, temporaryPresentationId: String, presentationId: String)
object PresentationUploadTokenFailRespMsg { val NAME = "PresentationUploadTokenFailRespMsg" }
case class PresentationUploadTokenFailRespMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenFailRespMsgBody) extends StandardMsg
@ -359,5 +357,5 @@ case class SyncGetPresentationPodsRespMsgBody(pods: Vector[PresentationPodVO])
// ------------ akka-apps to bbb-common-web ------------
object PresentationUploadTokenSysPubMsg { val NAME = "PresentationUploadTokenSysPubMsg" }
case class PresentationUploadTokenSysPubMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenSysPubMsgBody) extends BbbCoreMsg
case class PresentationUploadTokenSysPubMsgBody(podId: String, authzToken: String, filename: String, meetingId: String)
case class PresentationUploadTokenSysPubMsgBody(podId: String, authzToken: String, filename: String, meetingId: String, presentationId: String)
// ------------ akka-apps to bbb-common-web ------------

View File

@ -242,6 +242,31 @@ case class InvalidateUserGraphqlConnectionSysMsg(
) extends BbbCoreMsg
case class InvalidateUserGraphqlConnectionSysMsgBody(meetingId: String, userId: String, sessionToken: String, reason: String)
/**
* Sent from graphql-middleware to akka-apps
*/
object UserGraphqlConnectionInvalidatedEvtMsg { val NAME = "UserGraphqlConnectionInvalidatedEvtMsg" }
case class UserGraphqlConnectionInvalidatedEvtMsg(
header: BbbCoreBaseHeader,
body: UserGraphqlConnectionInvalidatedEvtMsgBody
) extends BbbCoreMsg
case class UserGraphqlConnectionInvalidatedEvtMsgBody(sessionToken: String, browserConnectionId: String)
object UserGraphqlConnectionStablishedSysMsg { val NAME = "UserGraphqlConnectionStablishedSysMsg" }
case class UserGraphqlConnectionStablishedSysMsg(
header: BbbCoreBaseHeader,
body: UserGraphqlConnectionStablishedSysMsgBody
) extends BbbCoreMsg
case class UserGraphqlConnectionStablishedSysMsgBody(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)
/**
* Sent from akka-apps to bbb-web to inform a summary of the meeting activities
*/

View File

@ -8,7 +8,7 @@ case class RegisterUserReqMsg(
case class RegisterUserReqMsgBody(meetingId: String, intUserId: String, name: String, role: String,
extUserId: String, authToken: String, sessionToken: String, avatarURL: String,
guest: Boolean, authed: Boolean, guestStatus: String, excludeFromDashboard: Boolean,
customParameters: Map[String, String])
enforceLayout: String, customParameters: Map[String, String])
object UserRegisteredRespMsg { val NAME = "UserRegisteredRespMsg" }
case class UserRegisteredRespMsg(
@ -302,7 +302,7 @@ case class UserMobileFlagChangedEvtMsgBody(userId: String, mobile: Boolean)
object AssignPresenterReqMsg { val NAME = "AssignPresenterReqMsg" }
case class AssignPresenterReqMsg(header: BbbClientMsgHeader, body: AssignPresenterReqMsgBody) extends StandardMsg
case class AssignPresenterReqMsgBody(requesterId: String, newPresenterId: String, newPresenterName: String, assignedBy: String)
case class AssignPresenterReqMsgBody(assignedBy: String, newPresenterId: String)
/**
* Sent from client to change the video pin of the user in the meeting.

View File

@ -0,0 +1,39 @@
package org.bigbluebutton.common2.util
import com.fasterxml.jackson.databind.ObjectMapper
import com.fasterxml.jackson.dataformat.yaml.YAMLFactory
import com.fasterxml.jackson.module.scala.{ DefaultScalaModule, ScalaObjectMapper }
import scala.util.Try
object YamlUtil {
val mapper = new ObjectMapper(new YAMLFactory()) with ScalaObjectMapper
mapper.registerModule(DefaultScalaModule)
def mergeImmutableMaps(target: Map[String, Object], source: Map[String, Object]): Map[String, Object] = {
source.foldLeft(target) {
case (acc, (key, sourceValue)) if acc.contains(key) =>
val targetValue = acc(key)
val mergedValue = (targetValue, sourceValue) match {
case (targetMap: Map[String, Object], sourceMap: Map[String, Object]) =>
// If both source and target values are maps, recursively merge them.
mergeImmutableMaps(targetMap, sourceMap)
case _ =>
// If not, replace the target value with the source value.
sourceValue
}
acc.updated(key, mergedValue)
case (acc, (key, sourceValue)) =>
// If the key is not in the target map, add it.
acc + (key -> sourceValue)
}
}
def toMap[V](yaml: String)(implicit m: Manifest[V]): Try[Map[String, V]] = fromYaml[Map[String, V]](yaml)
def fromYaml[T](yaml: String)(implicit m: Manifest[T]): Try[T] = {
for {
result <- Try(mapper.readValue[T](yaml))
} yield result
}
}

View File

@ -122,13 +122,19 @@ public class MeetingService implements MessageListener {
public void registerUser(String meetingID, String internalUserId,
String fullname, String role, String externUserID,
String authToken, String sessionToken, String avatarURL, Boolean guest,
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby, Map<String, String> customParameters) {
handle(new RegisterUser(meetingID, internalUserId, fullname, role,
externUserID, authToken, sessionToken, avatarURL, guest, authed, guestStatus, excludeFromDashboard, leftGuestLobby, customParameters));
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby,
String enforceLayout, Map<String, String> customParameters) {
handle(
new RegisterUser(meetingID, internalUserId, fullname, role,
externUserID, authToken, sessionToken, avatarURL, guest, authed, guestStatus,
excludeFromDashboard, leftGuestLobby, enforceLayout, customParameters
)
);
Meeting m = getMeeting(meetingID);
if (m != null) {
RegisteredUser ruser = new RegisteredUser(authToken, internalUserId, guestStatus, excludeFromDashboard, leftGuestLobby);
RegisteredUser ruser = new RegisteredUser(authToken, internalUserId, guestStatus,
excludeFromDashboard, leftGuestLobby, enforceLayout);
m.userRegistered(ruser);
}
}
@ -407,7 +413,8 @@ public class MeetingService implements MessageListener {
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getAllowModsToEjectCameras(), m.getMeetingKeepEvents(),
m.breakoutRoomsParams, m.lockSettingsParams, m.getHtml5InstanceId(),
m.getGroups(), m.getDisabledFeatures(), m.getNotifyRecordingIsOn(),
m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl());
m.getPresentationUploadExternalDescription(), m.getPresentationUploadExternalUrl(),
m.getOverrideClientSettings());
}
private String formatPrettyDate(Long timestamp) {
@ -422,7 +429,7 @@ public class MeetingService implements MessageListener {
gw.registerUser(message.meetingID,
message.internalUserId, message.fullname, message.role,
message.externUserID, message.authToken, message.sessionToken, message.avatarURL, message.guest,
message.authed, message.guestStatus, message.excludeFromDashboard, message.customParameters);
message.authed, message.guestStatus, message.excludeFromDashboard, message.enforceLayout, message.customParameters);
}
public Meeting getMeeting(String meetingId) {
@ -733,7 +740,7 @@ public class MeetingService implements MessageListener {
uploadAuthzTokens.put(message.authzToken, message);
}
private void expirePresentationUploadToken(String usedToken) {
public void expirePresentationUploadToken(String usedToken) {
uploadAuthzTokens.remove(usedToken);
}

View File

@ -19,13 +19,10 @@
package org.bigbluebutton.api;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets;
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.util.*;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
@ -37,12 +34,6 @@ import com.google.gson.JsonObject;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.ClientProtocolException;
import org.apache.http.client.ResponseHandler;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.util.EntityUtils;
import org.bigbluebutton.api.domain.BreakoutRoomsParams;
import org.bigbluebutton.api.domain.LockSettingsParams;
import org.bigbluebutton.api.domain.Meeting;
@ -145,6 +136,7 @@ public class ParamsProcessorUtil {
private String bbbVersion = "";
private Boolean allowRevealOfBBBVersion = false;
private Boolean allowOverrideClientSettingsOnCreateCall = false;
private String formatConfNum(String s) {
if (s.length() > 5) {
@ -912,6 +904,10 @@ public class ParamsProcessorUtil {
return allowRevealOfBBBVersion;
}
public Boolean getAllowOverrideClientSettingsOnCreateCall() {
return allowOverrideClientSettingsOnCreateCall;
}
public String processWelcomeMessage(String message, Boolean isBreakout) {
String welcomeMessage = message;
if (StringUtils.isEmpty(message)) {
@ -1156,6 +1152,11 @@ public class ParamsProcessorUtil {
return true;
}
public boolean parentMeetingExists(String parentMeetingId) {
Meeting meeting = ServiceUtils.findMeetingFromMeetingID(parentMeetingId);
return meeting != null;
}
/*************************************************
* Setters
************************************************/
@ -1515,4 +1516,8 @@ public class ParamsProcessorUtil {
this.allowRevealOfBBBVersion = allowVersion;
}
public void setAllowOverrideClientSettingsOnCreateCall(Boolean allowOverrideClientSettingsOnCreateCall) {
this.allowOverrideClientSettingsOnCreateCall = allowOverrideClientSettingsOnCreateCall;
}
}

View File

@ -2,11 +2,14 @@ package org.bigbluebutton.api;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.UUID;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
public final class Util {
@ -20,6 +23,14 @@ public final class Util {
throw new IllegalStateException("Utility class");
}
public static String extractFilenameFromUrl(String preUploadedPresentation) throws MalformedURLException {
URL url = new URL(preUploadedPresentation);
String filename = FilenameUtils.getName(url.getPath());
String extension = FilenameUtils.getExtension(url.getPath());
if (extension == null || extension.isEmpty()) return null;
return filename;
}
public static boolean isMeetingIdValidFormat(String id) {
Matcher matcher = MEETING_ID_PATTERN.matcher(id);
if (matcher.matches()) {
@ -51,7 +62,11 @@ public final class Util {
}
public static String createNewFilename(String presId, String fileExt) {
if (!fileExt.isEmpty()) {
return presId + "." + fileExt;
} else {
return presId;
}
}
public static File createPresentationDir(String meetingId, String presentationDir, String presentationId) {

View File

@ -118,6 +118,8 @@ public class Meeting {
private Integer html5InstanceId;
private String overrideClientSettings = "";
public Meeting(Meeting.Builder builder) {
name = builder.name;
extMeetingId = builder.externalId;
@ -1131,4 +1133,12 @@ public class Meeting {
return new Meeting(this);
}
}
public String getOverrideClientSettings() {
return overrideClientSettings;
}
public void setOverrideClientSettings(String overrideClientConfigs) {
this.overrideClientSettings = overrideClientConfigs;
}
}

View File

@ -9,13 +9,16 @@ public class RegisteredUser {
private Boolean excludeFromDashboard;
private Long guestWaitedOn;
private Boolean leftGuestLobby;
private String enforceLayout;
public RegisteredUser(String authToken, String userId, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby) {
public RegisteredUser(String authToken, String userId, String guestStatus, Boolean excludeFromDashboard,
Boolean leftGuestLobby, String enforceLayout) {
this.authToken = authToken;
this.userId = userId;
this.guestStatus = guestStatus;
this.excludeFromDashboard = excludeFromDashboard;
this.leftGuestLobby = leftGuestLobby;
this.enforceLayout = enforceLayout;
Long currentTimeMillis = System.currentTimeMillis();
this.registeredOn = currentTimeMillis;
@ -30,6 +33,10 @@ public class RegisteredUser {
return guestStatus;
}
public void setLeftGuestLobby(boolean bool) {
this.leftGuestLobby = bool;
}
public Boolean getLeftGuestLobby() {
return leftGuestLobby;
}
@ -38,6 +45,14 @@ public class RegisteredUser {
this.excludeFromDashboard = excludeFromDashboard;
}
public String getEnforceLayout() {
return enforceLayout;
}
public void setEnforceLayout(String enforceLayout) {
this.enforceLayout = enforceLayout;
}
public Boolean getExcludeFromDashboard() {
return excludeFromDashboard;
}
@ -46,9 +61,6 @@ public class RegisteredUser {
this.guestWaitedOn = System.currentTimeMillis();
}
public void setLeftGuestLobby(boolean bool) {
this.leftGuestLobby = bool;
}
public Long getGuestWaitedOn() {
return this.guestWaitedOn;
}

View File

@ -41,6 +41,7 @@ public class UserSession {
public String welcome = null;
public String logoutUrl = null;
public String defaultLayout = "NOLAYOUT";
public String enforceLayout = "";
public String avatarURL;
public String guestStatus = GuestPolicy.ALLOW;
public String clientUrl = null;
@ -130,6 +131,10 @@ public class UserSession {
return defaultLayout;
}
public String getEnforceLayout() {
return enforceLayout;
}
public String getAvatarURL() {
return avatarURL;
}

View File

@ -2,13 +2,15 @@ package org.bigbluebutton.api.messaging.messages;
public class PresentationUploadToken implements IMessage {
public final String podId;
public final String presentationId;
public final String authzToken;
public final String filename;
public final String meetingId;
public PresentationUploadToken(String podId, String authzToken, String filename, String meetingId) {
public PresentationUploadToken(String podId, String authzToken, String filename, String meetingId, String presentationId) {
this.podId = podId;
this.authzToken = authzToken;
this.presentationId = presentationId;
this.filename = filename;
this.meetingId = meetingId;
}

View File

@ -18,12 +18,13 @@ public class RegisterUser implements IMessage {
public final String guestStatus;
public final Boolean excludeFromDashboard;
public final Boolean leftGuestLobby;
public final String enforceLayout;
public final Map<String, String> customParameters;
public RegisterUser(String meetingID, String internalUserId, String fullname, String role, String externUserID,
String authToken, String sessionToken, String avatarURL, Boolean guest,
Boolean authed, String guestStatus, Boolean excludeFromDashboard, Boolean leftGuestLobby,
Map<String, String> customParameters) {
String enforceLayout, Map<String, String> customParameters) {
this.meetingID = meetingID;
this.internalUserId = internalUserId;
this.fullname = fullname;
@ -37,6 +38,7 @@ public class RegisterUser implements IMessage {
this.guestStatus = guestStatus;
this.excludeFromDashboard = excludeFromDashboard;
this.leftGuestLobby = leftGuestLobby;
this.enforceLayout = enforceLayout;
this.customParameters = customParameters;
}
}

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